Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.eclipse.tracecompass.incubator.analysis.core.reports;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.internal.provisional.tmf.core.dataprovider.ITmfDataProviderDataFetcher;
import org.eclipse.tracecompass.tmf.core.config.ITmfConfiguration;
import org.eclipse.tracecompass.tmf.core.config.ITmfConfigurationSourceType;
import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor;
Expand All @@ -25,7 +26,7 @@
*
* @author Kaveh Shahedi
*/
public interface IReportDataProvider {
public interface IReportDataProvider extends ITmfDataProviderDataFetcher {

/**
* Type identifier for report providers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -24,7 +25,9 @@
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.incubator.internal.analysis.core.Activator;
import org.eclipse.tracecompass.internal.provisional.tmf.core.dataprovider.TmfDataProviderDataModel;
import org.eclipse.tracecompass.tmf.core.config.ITmfConfigParamDescriptor;
import org.eclipse.tracecompass.tmf.core.config.ITmfConfiguration;
import org.eclipse.tracecompass.tmf.core.config.ITmfConfigurationSourceType;
Expand All @@ -33,17 +36,22 @@
import org.eclipse.tracecompass.tmf.core.config.TmfConfigurationSourceType;
import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor;
import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderDescriptor.ProviderType;
import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderFactory;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfConfigurationException;
import org.eclipse.tracecompass.tmf.core.model.DataProviderCapabilities;
import org.eclipse.tracecompass.tmf.core.model.DataProviderDescriptor;
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel;
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;

/**
* Handler for image-specific report configurations
*
* @author Kaveh Shahedi
*/
public class ImageReportDataProvider implements IReportDataProvider {
public class ImageReportDataProvider implements IReportDataProvider, IDataProviderFactory {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A data provider should not be a data provider factory. Generally,, we have data provider factories that create data provider instances which provide data (tree, xy, states, reports data). In this implementation, the reports data provider is a configurator, factory and data provider. The factory's main responsibility to create data providers. It can have the capabilities of a configurator. Using the configurator the data provider factory will be able to create instances of data providers using configuration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are absolutely right. I was working around with this IDataProiderFactory to see how we can solve the manager.getFactory() issue in the DataProviderService class, but I forgot to remove it in the end from the class. I will fix it.


private static final String[] VALID_EXTENSIONS = { "png", "jpg", "jpeg", "svg" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
// //$NON-NLS-5$
Expand Down Expand Up @@ -251,4 +259,38 @@ public void validateConfiguration(@NonNull ITmfConfiguration configuration) thro

return getDescriptorFromConfig(trace, configuration);
}

@SuppressWarnings("restriction")
@Override
public TmfModelResponse<TmfDataProviderDataModel<?>> getData(ITmfTrace trace, IDataProviderDescriptor descriptor) throws Exception {
ITmfConfiguration configuration = descriptor.getConfiguration();
if (configuration == null) {
throw new TmfConfigurationException("Missing configuration for report"); //$NON-NLS-1$
}

// Get the image path from the configuration
String imagePath = (String) configuration.getParameters().get(PATH);
if (imagePath == null) {
throw new TmfConfigurationException("Image path not found in configuration"); //$NON-NLS-1$
}

File imageFile = new File(imagePath);
if (!imageFile.exists() || !imageFile.isFile()) {
throw new TmfConfigurationException("Image file not found"); //$NON-NLS-1$
}

String contentType = Files.probeContentType(imageFile.toPath());
if (contentType == null) {
contentType = "application/octet-stream"; //$NON-NLS-1$
}

TmfDataProviderDataModel<File> dataModel = new TmfDataProviderDataModel<>(imageFile, contentType, imageFile.getName());

return new TmfModelResponse<>(dataModel, ITmfResponse.Status.COMPLETED, configuration.getName());
}

@Override
public @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> createProvider(@NonNull ITmfTrace trace) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@
import org.eclipse.tracecompass.tmf.core.model.DataProviderDescriptor;
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel;
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.incubator.internal.analysis.core.Activator;
import org.eclipse.tracecompass.internal.provisional.tmf.core.dataprovider.TmfDataProviderDataModel;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
Expand Down Expand Up @@ -726,4 +728,28 @@ public void dispose() {
fTmfConfigurationHierarchy.clear();
}

@SuppressWarnings("restriction")
@Override
public TmfModelResponse<TmfDataProviderDataModel<?>> getData(ITmfTrace trace, IDataProviderDescriptor descriptor) throws Exception {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The factory should be responsible create data providers (see line 121 above which needs to be implemented). Then the instance of data provider that was created there needs to be this method getData().

if (descriptor.getType() != ProviderType.NONE) {
throw new TmfConfigurationException("The requested output is not a MIME report"); //$NON-NLS-1$
}

ITmfConfiguration configuration = descriptor.getConfiguration();
if (configuration == null) {
throw new TmfConfigurationException("Missing configuration for report"); //$NON-NLS-1$ "
}

ReportProviderType reportType = getReportType(configuration);
IReportDataProvider provider = ReportsDataProviderRegistry.getProvider(reportType);
if (provider == null) {
throw new TmfConfigurationException("No provider found for report type"); //$NON-NLS-1$
}

if (provider instanceof ReportsDataProviderFactory) {
throw new TmfConfigurationException("Cannot get data from the report factory"); //$NON-NLS-1$
}

return provider.getData(trace, descriptor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface DataProvider {
* The provider types.
*/
enum ProviderType {
TABLE, TREE_TIME_XY, TIME_GRAPH, DATA_TREE, NONE
TABLE, TREE_TIME_XY, TIME_GRAPH, DATA_TREE, MIME, NONE
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.EXP_UUID;
import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.FILTER_QUERY_PARAMETERS;
import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.FILTER_QUERY_PARAMETERS_EX;
import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.RPT;
import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.INDEX;
import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.INDEX_EX;
import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.INVALID_PARAMETERS;
Expand Down Expand Up @@ -133,6 +134,8 @@
import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.views.TreeModelWrapper;
import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.views.VirtualTableModelWrapper;
import org.eclipse.tracecompass.internal.analysis.timing.core.event.matching.EventMatchingLatencyAnalysis;
import org.eclipse.tracecompass.internal.provisional.tmf.core.dataprovider.ITmfDataProviderDataFetcher;
import org.eclipse.tracecompass.internal.provisional.tmf.core.dataprovider.TmfDataProviderDataModel;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.ITmfVirtualTableDataProvider;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.ITmfVirtualTableModel;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.table.IVirtualTableLine;
Expand Down Expand Up @@ -1101,6 +1104,78 @@ private Response getTree(UUID expUUID, String outputId, QueryParameters queryPar
}
}

/**
* Query the provider for a MIME report. Based on the given output, this
* endpoint will return a specific report, which can be an image, a
* textual data, a HTML, etc.
*
* @param expUUID
* desired experiment UUID
* @param outputId
* Output ID for the data provider to query
* @return {@link Response} with the corresponding report data
*/
@GET
@Path("/report/{outputId}")
@Tag(name = RPT)
@Produces(MediaType.WILDCARD)
@Operation(summary = "API to get a MIME report", responses = {
@ApiResponse(responseCode = "200", description = "Returns the report data"),
@ApiResponse(responseCode = "400", description = MISSING_PARAMETERS, content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "404", description = "Provider not found, missing subtype, or requested resource not found", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "500", description = "Error retrieving the report data", content = @Content(schema = @Schema(implementation = String.class))),
})
public Response getReport(
@Parameter(description = EXP_UUID) @PathParam("expUUID") UUID expUUID,
@Parameter(description = OUTPUT_ID) @PathParam("outputId") String outputId) {

if (outputId == null) {
return Response.status(Status.BAD_REQUEST).entity(MISSING_OUTPUTID).build();
}

TmfExperiment experiment = ExperimentManagerService.getExperimentByUUID(expUUID);
if (experiment == null) {
return Response.status(Status.NOT_FOUND).entity(NO_SUCH_TRACE).build();
}

IDataProviderDescriptor descriptor = getDescriptor(experiment, outputId);
Copy link
Copy Markdown
Contributor

@bhufmann bhufmann Apr 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always return null, because the DataProviderManager doesn't know about the data provider with that ID (unless it was registered). This shows some issue with the DataProviderManager because it manages ITmfTreeDataProviders. So, either the reports data provider implements ITmfTreeDataProvider which is created by the reports data provider factory (not the cleanest implementation), or in Trace Compass mainline the DataProviderManager is updated to handle other data providers than trees or a new data provider manager is created for new types of data providers. Trace Compass designers will have to be involved with this design in Trace Compass core.

We need something like this below:

provider = manager.getOrCreateDataProvider(experiment, outputId, IReportDataProvider.class)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have already tested this endpoint, and I could see that this code block is not returning null in cases where the given output is a valid report DP. It also works fine for cases where the ID of the output is not valid (e.g., not present in the DP manager).

I'm not sure if I have understood your point correctly or not, but please correct me if I'm wrong.

if (descriptor == null) {
return Response.status(Status.NOT_FOUND).entity(NO_SUCH_PROVIDER).build();
}

IDataProviderFactory factory = manager.getFactory(REPORTS_FACTORY_ID);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint has to be independent of the reports customization. For example, if the incubator analysis.core plug-in not installed, how would one fetch the data of a data provider of type MIME?.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I tried to decouple this endpoint from REPORTS_FACTORY_ID by using the given output's id in the endpoint. However, the issue is that, except for ReportsDataProviderFactory, no other report data provider classes are inheriting the IDataProviderFactory. Hence, when loading the available factories in the startup, the report-based data providers whose ID is not REPORTS_FACTORY_ID would not be recognized as a factory.

As the ReportsDataProviderFactory class is responsible for handling the entire pipeline of reports, I guess we might need to keep the structure as is. Also, the endpoint is specifically for getting report data, which makes it suitable for using the REPORTS_FACTORY_ID in it, although in some cases, the user might not have installed this plugin.

What do you think of this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REPORTS_FACTORY_ID is only available if the incubator.analysis.core plug-in is installed. Otherwise there can't be any reports create by anything else.

Copy link
Copy Markdown
Contributor Author

@kavehshahedi kavehshahedi Apr 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that's fine. Since the endpoint is only for this specific DP, in case of missing (e.g., not being installed), we can tell the user that it's not installed as a plug-in. wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that's fine. Since the endpoint is only for this specific DP, in case of missing (e.g., not being installed), we can tell the user that it's not installed as a plug-in. wdyt?

That's not really an option. Users cannot install plug-ins. Trace server applications are pre-build.

if (factory == null) {
return Response.status(Status.NOT_FOUND).entity(NO_SUCH_PROVIDER).build();
Comment thread
kavehshahedi marked this conversation as resolved.
}

ITmfDataProviderDataFetcher dataFetcher = factory.getAdapter(ITmfDataProviderDataFetcher.class);
if (dataFetcher == null) {
return Response.status(Status.NOT_FOUND).entity("Report data fetcher not found").build(); //$NON-NLS-1$ "
}

try {
TmfModelResponse<TmfDataProviderDataModel<?>> reportResponse = dataFetcher.getData(experiment, descriptor);
if (reportResponse == null) {
return Response.status(Status.NOT_FOUND).entity("Report data not found").build(); //$NON-NLS-1$
}

if (reportResponse.getStatus() != ITmfResponse.Status.COMPLETED) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(reportResponse.getStatusMessage()).build();
}

TmfDataProviderDataModel<?> responseModel = reportResponse.getModel();
if (responseModel == null) {
return Response.status(Status.NOT_FOUND).entity("Report data model not found").build(); //$NON-NLS-1$
}

return Response.ok(responseModel.getContent(), responseModel.getContentType()).build();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For images, responseModel.getContent() returns a object of type File. The content type will be determined by Files.probeContentType() which could return image/jpg or image/svg or if it can't be determined it will be application/octet-stream. I'm clear what will happen in each case and how a FE client can handle that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the endpoint is producing MediaType.WILD_CARD to ensure any MIME data can be handled. The responseModel.getContentType would be responsible for indicating the output type (e.g., image/jpg).
The FE client should handle the output based on the response's headers that indicate the output type.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How, will it work? Clients could ask for e.g. applicationt/pdf and the endpoint will allow it because of the wildcard but then the implementation won't be able to create a PDF but an image instead. I'm not clear about the procedure and header negotiation yet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, basically, the client can ask only based on the report type. For instance, the client only knows that it's an image, but doesn't know if it's a JPG or PNG or SVG. The endpoint then fetches the DP data, and returns the output with a specific content type, which can be image/jpg or image/png or etc. This information is provided in the response's headers as the content type (I have checked it already).
So, it would be the responsibility of the FE to properly show the image in the output based on the response's content type.

} catch (TmfConfigurationException e) {
return Response.status(Status.NOT_FOUND).entity(e.getMessage()).build();
} catch (Exception e) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
}
}

/**
* Query the provider for styles
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public final class EndpointConstants {
static final String EXP = "Experiments"; //$NON-NLS-1$
static final String IDF = "Identifier"; //$NON-NLS-1$
static final String OCG = "Output Configurations"; //$NON-NLS-1$
static final String RPT = "Report"; //$NON-NLS-1$
static final String STY = "Styles"; //$NON-NLS-1$
static final String TGR = "TimeGraph"; //$NON-NLS-1$
static final String TRA = "Traces"; //$NON-NLS-1$
Expand Down
Loading