use of org.pentaho.di.trans.step.RowListener in project pentaho-kettle by pentaho.
the class TransGraph method sniff.
public void sniff(final boolean input, final boolean output, final boolean error) {
StepMeta stepMeta = getCurrentStep();
if (stepMeta == null || trans == null) {
return;
}
final StepInterface runThread = trans.findRunThread(stepMeta.getName());
if (runThread != null) {
List<Object[]> rows = new ArrayList<>();
final PreviewRowsDialog dialog = new PreviewRowsDialog(shell, trans, SWT.NONE, stepMeta.getName(), null, rows);
dialog.setDynamic(true);
// Add a row listener that sends the rows over to the dialog...
//
final RowListener rowListener = new RowListener() {
@Override
public void rowReadEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException {
if (input) {
try {
dialog.addDataRow(rowMeta, rowMeta.cloneRow(row));
} catch (KettleValueException e) {
throw new KettleStepException(e);
}
}
}
@Override
public void rowWrittenEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException {
if (output) {
try {
dialog.addDataRow(rowMeta, rowMeta.cloneRow(row));
} catch (KettleValueException e) {
throw new KettleStepException(e);
}
}
}
@Override
public void errorRowWrittenEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException {
if (error) {
try {
dialog.addDataRow(rowMeta, rowMeta.cloneRow(row));
} catch (KettleValueException e) {
throw new KettleStepException(e);
}
}
}
};
// When the dialog is closed, make sure to remove the listener!
//
dialog.addDialogClosedListener(new DialogClosedListener() {
@Override
public void dialogClosed() {
runThread.removeRowListener(rowListener);
}
});
// Open the dialog in a separate thread to make sure it doesn't block
//
getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
dialog.open();
}
});
runThread.addRowListener(rowListener);
}
}
use of org.pentaho.di.trans.step.RowListener in project pentaho-kettle by pentaho.
the class SniffStepServlet method doGet.
/**
*<div id="mindtouch">
* <h1>/kettle/sniffStep</h1>
* <a name="GET"></a>
* <h2>GET</h2>
* <p>Sniff metadata and data from the specified step of the specified transformation.</p>
*
* <p><b>Example Request:</b><br />
* <pre function="syntax.xml">
* GET /kettle/sniffStep?trans=dummy-trans&step=tf&xml=Y&lines=10
* </pre>
*
* </p>
* <h3>Parameters</h3>
* <table class="pentaho-table">
* <tbody>
* <tr>
* <th>name</th>
* <th>description</th>
* <th>type</th>
* </tr>
* <tr>
* <td>trans</td>
* <td>Name of the transformation containing required step.</td>
* <td>query</td>
* </tr>
* <tr>
* <td>stepName</td>
* <td>Name of the transformation step to collect data for.</td>
* <td>query</td>
* </tr>
* <tr>
* <td>copynr</td>
* <td>Copy number of the step to be used for collecting data. If not provided 0 is used.</td>
* <td>integer, optional</td>
* </tr>
* <tr>
* <td>type</td>
* <td>Type of the data to be collected (<code>input</code> or <code>output</code>).
* If not provided output data is collected.</td>
* <td>query, optional</td>
* </tr>
* <tr>
* <td>xml</td>
* <td>Boolean flag which defines output format <code>Y</code> forces XML output to be generated.
* HTML is returned otherwise.</td>
* <td>boolean, optional</td>
* </tr>
* <tr>
* <td>id</td>
* <td>Carte id of the transformation to be used for step lookup.</td>
* <td>query, optional</td>
* </tr>
* <tr>
* <td>lines</td>
* <td>Number of lines to collect and include into response. If not provided 0 lines will be collected.</td>
* <td>integer, optional</td>
* </tr>
* </tbody>
* </table>
*
* <h3>Response Body</h3>
*
* <table class="pentaho-table">
* <tbody>
* <tr>
* <td align="right">element:</td>
* <td>(custom)</td>
* </tr>
* <tr>
* <td align="right">media types:</td>
* <td>text/xml, text/html</td>
* </tr>
* </tbody>
* </table>
* <p>Response XML or HTML response containing data and metadata of the step.
* If an error occurs during method invocation <code>result</code> field of the response
* will contain <code>ERROR</code> status.</p>
*
* <p><b>Example Response:</b></p>
* <pre function="syntax.xml">
* <?xml version="1.0" encoding="UTF-8"?>
* <step-sniff>
* <row-meta>
* <value-meta><type>String</type>
* <storagetype>normal</storagetype>
* <name>Field1</name>
* <length>0</length>
* <precision>-1</precision>
* <origin>tf</origin>
* <comments/>
* <conversion_Mask/>
* <decimal_symbol>.</decimal_symbol>
* <grouping_symbol>,</grouping_symbol>
* <currency_symbol>$</currency_symbol>
* <trim_type>none</trim_type>
* <case_insensitive>N</case_insensitive>
* <sort_descending>N</sort_descending>
* <output_padding>N</output_padding>
* <date_format_lenient>Y</date_format_lenient>
* <date_format_locale>en_US</date_format_locale>
* <date_format_timezone>America/Bahia</date_format_timezone>
* <lenient_string_to_number>N</lenient_string_to_number>
* </value-meta>
* </row-meta>
* <nr_rows>10</nr_rows>
*
* <row-data><value-data>my-data</value-data>
* </row-data>
* <row-data><value-data>my-data </value-data>
* </row-data>
* <row-data><value-data>my-data</value-data>
* </row-data>
* <row-data><value-data>my-data</value-data>
* </row-data>
* <row-data><value-data>my-data</value-data>
* </row-data>
* <row-data><value-data>my-data</value-data>
* </row-data>
* <row-data><value-data>my-data</value-data>
* </row-data>
* <row-data><value-data>my-data</value-data>
* </row-data>
* <row-data><value-data>my-data</value-data>
* </row-data>
* <row-data><value-data>my-data</value-data>
* </row-data>
* </step-sniff>
* </pre>
*
* <h3>Status Codes</h3>
* <table class="pentaho-table">
* <tbody>
* <tr>
* <th>code</th>
* <th>description</th>
* </tr>
* <tr>
* <td>200</td>
* <td>Request was processed.</td>
* </tr>
* <tr>
* <td>500</td>
* <td>Internal server error occurs during request processing.</td>
* </tr>
* </tbody>
*</table>
*</div>
*/
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (isJettyMode() && !request.getContextPath().startsWith(CONTEXT_PATH)) {
return;
}
if (log.isDebug()) {
logDebug(BaseMessages.getString(PKG, "TransStatusServlet.Log.SniffStepRequested"));
}
String transName = request.getParameter("trans");
String id = request.getParameter("id");
String stepName = request.getParameter("step");
int copyNr = Const.toInt(request.getParameter("copynr"), 0);
final int nrLines = Const.toInt(request.getParameter("lines"), 0);
String type = Const.NVL(request.getParameter("type"), TYPE_OUTPUT);
boolean useXML = "Y".equalsIgnoreCase(request.getParameter("xml"));
response.setStatus(HttpServletResponse.SC_OK);
if (useXML) {
response.setContentType("text/xml");
response.setCharacterEncoding(Const.XML_ENCODING);
} else {
response.setContentType("text/html;charset=UTF-8");
}
PrintWriter out = response.getWriter();
// ID is optional...
//
Trans trans;
CarteObjectEntry entry;
if (Utils.isEmpty(id)) {
// get the first transformation that matches...
//
entry = getTransformationMap().getFirstCarteObjectEntry(transName);
if (entry == null) {
trans = null;
} else {
id = entry.getId();
trans = getTransformationMap().getTransformation(entry);
}
} else {
// Take the ID into account!
//
entry = new CarteObjectEntry(transName, id);
trans = getTransformationMap().getTransformation(entry);
}
if (trans != null) {
// Find the step to look at...
//
StepInterface step = null;
List<StepInterface> stepInterfaces = trans.findBaseSteps(stepName);
for (int i = 0; i < stepInterfaces.size(); i++) {
StepInterface look = stepInterfaces.get(i);
if (look.getCopy() == copyNr) {
step = look;
}
}
if (step != null) {
// Add a listener to the transformation step...
//
final boolean read = type.equalsIgnoreCase(TYPE_INPUT);
final boolean written = type.equalsIgnoreCase(TYPE_OUTPUT) || !read;
final MetaAndData metaData = new MetaAndData();
metaData.bufferRowMeta = null;
metaData.bufferRowData = new ArrayList<Object[]>();
RowListener rowListener = new RowListener() {
public void rowReadEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException {
if (read && metaData.bufferRowData.size() < nrLines) {
metaData.bufferRowMeta = rowMeta;
metaData.bufferRowData.add(row);
}
}
public void rowWrittenEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException {
if (written && metaData.bufferRowData.size() < nrLines) {
metaData.bufferRowMeta = rowMeta;
metaData.bufferRowData.add(row);
}
}
public void errorRowWrittenEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException {
}
};
step.addRowListener(rowListener);
//
while (metaData.bufferRowData.size() < nrLines && step.isRunning() && !trans.isFinished() && !trans.isStopped()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//
break;
}
}
// Remove the row listener
//
step.removeRowListener(rowListener);
//
if (useXML) {
// Send the result back as XML
//
response.setContentType("text/xml");
response.setCharacterEncoding(Const.XML_ENCODING);
out.print(XMLHandler.getXMLHeader(Const.XML_ENCODING));
out.println(XMLHandler.openTag(XML_TAG));
if (metaData.bufferRowMeta != null) {
// Row Meta data
//
out.println(metaData.bufferRowMeta.getMetaXML());
// Nr of lines
//
out.println(XMLHandler.addTagValue("nr_rows", metaData.bufferRowData.size()));
//
for (int i = 0; i < metaData.bufferRowData.size(); i++) {
Object[] rowData = metaData.bufferRowData.get(i);
out.println(metaData.bufferRowMeta.getDataXML(rowData));
}
}
out.println(XMLHandler.closeTag(XML_TAG));
} else {
response.setContentType("text/html;charset=UTF-8");
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>" + BaseMessages.getString(PKG, "SniffStepServlet.SniffResults") + "</TITLE>");
out.println("<META http-equiv=\"Refresh\" content=\"10;url=" + convertContextPath(CONTEXT_PATH) + "?name=" + URLEncoder.encode(transName, "UTF-8") + "&id=" + URLEncoder.encode(id, "UTF-8") + "\">");
out.println("<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<H1>" + Encode.forHtml(BaseMessages.getString(PKG, "SniffStepServlet.SniffResultsForStep", stepName)) + "</H1>");
try {
out.println("<table border=\"1\">");
if (metaData.bufferRowMeta != null) {
// Print a header row containing all the field names...
//
out.print("<tr><th>#</th>");
for (ValueMetaInterface valueMeta : metaData.bufferRowMeta.getValueMetaList()) {
out.print("<th>" + valueMeta.getName() + "</th>");
}
out.println("</tr>");
//
for (int r = 0; r < metaData.bufferRowData.size(); r++) {
Object[] rowData = metaData.bufferRowData.get(r);
out.print("<tr>");
out.println("<td>" + (r + 1) + "</td>");
for (int v = 0; v < metaData.bufferRowMeta.size(); v++) {
ValueMetaInterface valueMeta = metaData.bufferRowMeta.getValueMeta(v);
Object valueData = rowData[v];
out.println("<td>" + valueMeta.getString(valueData) + "</td>");
}
out.println("</tr>");
}
}
out.println("</table>");
out.println("<p>");
} catch (Exception ex) {
out.println("<p>");
out.println("<pre>");
out.println(Encode.forHtml(Const.getStackTracker(ex)));
out.println("</pre>");
}
out.println("<p>");
out.println("</BODY>");
out.println("</HTML>");
}
} else {
if (useXML) {
out.println(new WebResult(WebResult.STRING_ERROR, BaseMessages.getString(PKG, "SniffStepServlet.Log.CoundNotFindSpecStep", stepName)).getXML());
} else {
out.println("<H1>" + Encode.forHtml(BaseMessages.getString(PKG, "SniffStepServlet.Log.CoundNotFindSpecStep", stepName)) + "</H1>");
out.println("<a href=\"" + convertContextPath(GetStatusServlet.CONTEXT_PATH) + "\">" + BaseMessages.getString(PKG, "TransStatusServlet.BackToStatusPage") + "</a><p>");
}
}
} else {
if (useXML) {
out.println(new WebResult(WebResult.STRING_ERROR, BaseMessages.getString(PKG, "SniffStepServlet.Log.CoundNotFindSpecTrans", transName)).getXML());
} else {
out.println("<H1>" + Encode.forHtml(BaseMessages.getString(PKG, "SniffStepServlet.Log.CoundNotFindTrans", transName)) + "</H1>");
out.println("<a href=\"" + convertContextPath(GetStatusServlet.CONTEXT_PATH) + "\">" + BaseMessages.getString(PKG, "TransStatusServlet.BackToStatusPage") + "</a><p>");
}
}
}
use of org.pentaho.di.trans.step.RowListener in project pdi-dataservice-server-plugin by pentaho.
the class DataServiceExecutorTest method testExecuteQueryNoResults.
@Test
public void testExecuteQueryNoResults() throws Exception {
SQL sql = new SQL("SELECT * FROM " + DATA_SERVICE_NAME);
StepInterface serviceStep = serviceTrans.findRunThread(DATA_SERVICE_STEP);
StepInterface resultStep = genTrans.findRunThread(RESULT_STEP_NAME);
when(serviceTrans.getTransMeta().listParameters()).thenReturn(new String[0]);
PushDownOptimizationMeta optimization = mock(PushDownOptimizationMeta.class);
when(optimization.isEnabled()).thenReturn(true);
dataService.getPushDownOptimizationMeta().add(optimization);
IMetaStore metastore = mock(IMetaStore.class);
DataServiceExecutor executor = new DataServiceExecutor.Builder(sql, dataService, context).serviceTrans(serviceTrans).sqlTransGenerator(sqlTransGenerator).genTrans(genTrans).metastore(metastore).build();
ArgumentCaptor<String> objectIds = ArgumentCaptor.forClass(String.class);
verify(serviceTrans).setContainerObjectId(objectIds.capture());
when(serviceTrans.getContainerObjectId()).thenReturn(objectIds.getValue());
verify(genTrans).setContainerObjectId(objectIds.capture());
when(genTrans.getContainerObjectId()).thenReturn(objectIds.getValue());
verify(serviceTrans).setMetaStore(metastore);
verify(genTrans).setMetaStore(metastore);
RowProducer sqlTransRowProducer = mock(RowProducer.class);
when(genTrans.addRowProducer(INJECTOR_STEP_NAME, 0)).thenReturn(sqlTransRowProducer);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Start Execution
executor.executeQuery(new DataOutputStream(outputStream));
// Check header was written
assertThat(outputStream.size(), greaterThan(0));
outputStream.reset();
InOrder genTransStartup = inOrder(genTrans, resultStep);
InOrder serviceTransStartup = inOrder(optimization, serviceTrans, serviceStep);
ArgumentCaptor<RowListener> listenerArgumentCaptor = ArgumentCaptor.forClass(RowListener.class);
ArgumentCaptor<StepListener> resultStepListener = ArgumentCaptor.forClass(StepListener.class);
ArgumentCaptor<TransListener> transListenerCaptor = ArgumentCaptor.forClass(TransListener.class);
genTransStartup.verify(genTrans).addTransListener(transListenerCaptor.capture());
genTransStartup.verify(genTrans).addRowProducer(INJECTOR_STEP_NAME, 0);
genTransStartup.verify(resultStep).addStepListener(resultStepListener.capture());
genTransStartup.verify(resultStep).addRowListener(listenerArgumentCaptor.capture());
RowListener clientRowListener = listenerArgumentCaptor.getValue();
genTransStartup.verify(genTrans).startThreads();
serviceTransStartup.verify(optimization).activate(executor);
serviceTransStartup.verify(serviceStep).addRowListener(listenerArgumentCaptor.capture());
serviceTransStartup.verify(serviceTrans).startThreads();
// Verify linkage
RowListener serviceRowListener = listenerArgumentCaptor.getValue();
assertNotNull(serviceRowListener);
// Push row from service to sql Trans
RowMetaInterface rowMeta = genTrans.getTransMeta().getStepFields(RESULT_STEP_NAME);
doReturn(true).when(serviceTrans).isRunning();
resultStepListener.getValue().stepFinished(genTrans, resultStep.getStepMeta(), resultStep);
verify(serviceTrans).stopAll();
// Verify Service Trans finished
ArgumentCaptor<StepListener> serviceStepListener = ArgumentCaptor.forClass(StepListener.class);
verify(serviceStep).addStepListener(serviceStepListener.capture());
serviceStepListener.getValue().stepFinished(serviceTrans, serviceStep.getStepMeta(), serviceStep);
verify(sqlTransRowProducer).finished();
// finish transformation, so that the listener runs
transListenerCaptor.getValue().transFinished(genTrans);
InOrder writeRows = inOrder(rowMeta);
ArgumentCaptor<DataOutputStream> streamCaptor = ArgumentCaptor.forClass(DataOutputStream.class);
writeRows.verify(rowMeta).writeMeta(streamCaptor.capture());
DataOutputStream dataOutputStream = streamCaptor.getValue();
writeRows.verify(rowMeta, times(0)).writeData(same(dataOutputStream), argThat(arrayWithSize(1)));
writeRows.verifyNoMoreInteractions();
executor.waitUntilFinished();
verify(serviceTrans).waitUntilFinished();
verify(genTrans).waitUntilFinished();
}
use of org.pentaho.di.trans.step.RowListener in project pdi-dataservice-server-plugin by pentaho.
the class DataServiceExecutorTest method testExecuteQuery.
@Test
public void testExecuteQuery() throws Exception {
SQL sql = new SQL("SELECT * FROM " + DATA_SERVICE_NAME);
StepInterface serviceStep = serviceTrans.findRunThread(DATA_SERVICE_STEP);
StepInterface resultStep = genTrans.findRunThread(RESULT_STEP_NAME);
when(serviceTrans.getTransMeta().listParameters()).thenReturn(new String[0]);
PushDownOptimizationMeta optimization = mock(PushDownOptimizationMeta.class);
when(optimization.isEnabled()).thenReturn(true);
dataService.getPushDownOptimizationMeta().add(optimization);
IMetaStore metastore = mock(IMetaStore.class);
DataServiceExecutor executor = new DataServiceExecutor.Builder(sql, dataService, context).serviceTrans(serviceTrans).sqlTransGenerator(sqlTransGenerator).genTrans(genTrans).metastore(metastore).build();
ArgumentCaptor<String> objectIds = ArgumentCaptor.forClass(String.class);
verify(serviceTrans).setContainerObjectId(objectIds.capture());
when(serviceTrans.getContainerObjectId()).thenReturn(objectIds.getValue());
verify(genTrans).setContainerObjectId(objectIds.capture());
when(genTrans.getContainerObjectId()).thenReturn(objectIds.getValue());
verify(serviceTrans).setMetaStore(metastore);
verify(genTrans).setMetaStore(metastore);
RowProducer sqlTransRowProducer = mock(RowProducer.class);
when(genTrans.addRowProducer(INJECTOR_STEP_NAME, 0)).thenReturn(sqlTransRowProducer);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Start Execution
executor.executeQuery(new DataOutputStream(outputStream));
// Check header was written
assertThat(outputStream.size(), greaterThan(0));
outputStream.reset();
InOrder genTransStartup = inOrder(genTrans, resultStep);
InOrder serviceTransStartup = inOrder(optimization, serviceTrans, serviceStep);
ArgumentCaptor<RowListener> listenerArgumentCaptor = ArgumentCaptor.forClass(RowListener.class);
ArgumentCaptor<StepListener> resultStepListener = ArgumentCaptor.forClass(StepListener.class);
ArgumentCaptor<TransListener> transListenerCaptor = ArgumentCaptor.forClass(TransListener.class);
genTransStartup.verify(genTrans).addTransListener(transListenerCaptor.capture());
genTransStartup.verify(genTrans).addRowProducer(INJECTOR_STEP_NAME, 0);
genTransStartup.verify(resultStep).addStepListener(resultStepListener.capture());
genTransStartup.verify(resultStep).addRowListener(listenerArgumentCaptor.capture());
RowListener clientRowListener = listenerArgumentCaptor.getValue();
genTransStartup.verify(genTrans).startThreads();
serviceTransStartup.verify(optimization).activate(executor);
serviceTransStartup.verify(serviceStep).addRowListener(listenerArgumentCaptor.capture());
serviceTransStartup.verify(serviceTrans).startThreads();
// Verify linkage
RowListener serviceRowListener = listenerArgumentCaptor.getValue();
assertNotNull(serviceRowListener);
// Push row from service to sql Trans
RowMetaInterface rowMeta = genTrans.getTransMeta().getStepFields(RESULT_STEP_NAME);
Object[] data;
for (int i = 0; i < 50; i++) {
data = new Object[] { i };
Object[] dataClone = { i };
when(rowMeta.cloneRow(data)).thenReturn(dataClone);
serviceRowListener.rowWrittenEvent(rowMeta, data);
verify(sqlTransRowProducer).putRowWait(same(rowMeta), and(eq(dataClone), not(same(data))), any(Long.class), any(TimeUnit.class));
verify(rowMeta).cloneRow(data);
}
doReturn(true).when(serviceTrans).isRunning();
resultStepListener.getValue().stepFinished(genTrans, resultStep.getStepMeta(), resultStep);
verify(serviceTrans).stopAll();
// Verify Service Trans finished
ArgumentCaptor<StepListener> serviceStepListener = ArgumentCaptor.forClass(StepListener.class);
verify(serviceStep).addStepListener(serviceStepListener.capture());
serviceStepListener.getValue().stepFinished(serviceTrans, serviceStep.getStepMeta(), serviceStep);
verify(sqlTransRowProducer).finished();
// Push row from service to sql Trans
for (int i = 0; i < 50; i++) {
Object[] row = { i };
clientRowListener.rowWrittenEvent(rowMeta, row);
}
transListenerCaptor.getValue().transFinished(genTrans);
InOrder writeRows = inOrder(rowMeta);
ArgumentCaptor<DataOutputStream> streamCaptor = ArgumentCaptor.forClass(DataOutputStream.class);
writeRows.verify(rowMeta).writeMeta(streamCaptor.capture());
DataOutputStream dataOutputStream = streamCaptor.getValue();
writeRows.verify(rowMeta, times(50)).writeData(same(dataOutputStream), argThat(arrayWithSize(1)));
writeRows.verifyNoMoreInteractions();
executor.waitUntilFinished();
verify(serviceTrans).waitUntilFinished();
verify(genTrans).waitUntilFinished();
}
use of org.pentaho.di.trans.step.RowListener in project pdi-dataservice-server-plugin by pentaho.
the class StreamingServiceTransExecutorTest method testBufferAux.
private void testBufferAux(RowMetaInterface mockRowMeta, Object[] mockData, boolean checkLog) throws Exception {
ArgumentCaptor<RowListener> listenerArgumentCaptor = ArgumentCaptor.forClass(RowListener.class);
verify(serviceStep).addRowListener(listenerArgumentCaptor.capture());
verify(serviceTrans, times(1)).startThreads();
verify(serviceTrans, times(1)).findRunThread(MOCK_SERVICE_STEP_NAME);
verify(serviceStep, times(1)).addRowListener(any(RowAdapter.class));
// Verify linkage
RowListener serviceRowListener = listenerArgumentCaptor.getValue();
assertNotNull(serviceRowListener);
serviceRowListener.rowWrittenEvent(mockRowMeta, mockData);
if (checkLog) {
verify(log).logRowlevel(DataServiceConstants.PASSING_ALONG_ROW + mockRowMeta.getString(mockData));
}
verify(serviceTrans, never()).stopAll();
verify(serviceTrans, never()).waitUntilFinished();
verify(log, never()).logDebug(DataServiceConstants.STREAMING_TRANSFORMATION_STOPPED);
}
Aggregations