| /******************************************************************************** |
| * Copyright (c) 2015-2021 Contributors to the Eclipse Foundation |
| * |
| * See the NOTICE file(s) distributed with this work for additional |
| * information regarding copyright ownership. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| ********************************************************************************/ |
| |
| package org.eclipse.mdm.query.boundary; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.stream.Collectors; |
| |
| import javax.ejb.Stateless; |
| import javax.inject.Inject; |
| |
| import org.eclipse.mdm.api.base.ServiceNotProvidedException; |
| import org.eclipse.mdm.api.base.adapter.Attribute; |
| import org.eclipse.mdm.api.base.adapter.EntityType; |
| import org.eclipse.mdm.api.base.adapter.ModelManager; |
| import org.eclipse.mdm.api.base.model.Entity; |
| import org.eclipse.mdm.api.base.query.DataAccessException; |
| import org.eclipse.mdm.api.base.query.Filter; |
| import org.eclipse.mdm.api.base.query.Result; |
| import org.eclipse.mdm.api.base.search.SearchService; |
| import org.eclipse.mdm.api.dflt.ApplicationContext; |
| import org.eclipse.mdm.businessobjects.control.FilterParser; |
| import org.eclipse.mdm.businessobjects.utils.ServiceUtils; |
| import org.eclipse.mdm.connector.boundary.ConnectorService; |
| import org.eclipse.mdm.connector.boundary.ConnectorServiceException; |
| import org.eclipse.mdm.property.GlobalProperty; |
| import org.eclipse.mdm.query.entity.QueryRequest; |
| import org.eclipse.mdm.query.entity.QueryResult; |
| import org.eclipse.mdm.query.entity.Row; |
| import org.eclipse.mdm.query.entity.SourceFilter; |
| import org.eclipse.mdm.query.entity.SuggestionRequest; |
| import org.eclipse.mdm.query.util.Util; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * |
| * @author Matthias Koller, Peak Solution GmbH |
| * |
| */ |
| @Stateless |
| public class QueryService { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(QueryService.class); |
| |
| @Inject |
| @GlobalProperty(value = "businessobjects.query.maxresultspersource") |
| private String maxResultsPerSource; |
| |
| @Inject |
| ConnectorService connectorService; |
| |
| public QueryResult queryRows(QueryRequest request) { |
| QueryResult result =new QueryResult(new ArrayList<>(), 0); |
| |
| for (SourceFilter filter : request.getFilters()) { |
| try { |
| ApplicationContext context = this.connectorService.getContextByName(filter.getSourceName()); |
| |
| QueryResult inner = queryRowsForSource(context, request.getResultType(), request.getColumns(), |
| request.getResultOffset(), request.getResultLimit(), filter.getFilter(), filter.getSearchString()); |
| result.getRows().addAll(inner.getRows()); |
| result.setTotalRecords(result.getTotalRecords() + inner.getTotalRecords()); |
| } catch (ConnectorServiceException e) { |
| LOG.warn("Could not retrieve EntityManager for environment '" + filter.getSourceName() + "'!", e); |
| } catch (Exception e) { |
| LOG.warn("Could not retrieve query results for environment '" + filter.getSourceName() + "': " |
| + e.getMessage(), e); |
| } |
| } |
| return result; |
| } |
| |
| public List<String> getSuggestions(SuggestionRequest suggestionRequest) { |
| |
| List<String> suggestions = new ArrayList<>(); |
| |
| for (String envName : suggestionRequest.getSourceNames()) { |
| |
| ApplicationContext context = this.connectorService.getContextByName(envName); |
| Optional<ModelManager> mm = context.getModelManager(); |
| Optional<org.eclipse.mdm.api.base.query.QueryService> qs = context.getQueryService(); |
| |
| if (mm.isPresent() && qs.isPresent()) { |
| |
| try { |
| EntityType entityType = mm.get().getEntityType(suggestionRequest.getType()); |
| |
| Attribute attr = entityType.getAttribute(suggestionRequest.getAttrName()); |
| |
| suggestions.addAll(qs.get().createQuery().select(attr).group(attr).fetch().stream() |
| .map(r -> Objects.toString(r.getValue(attr).extract())).collect(Collectors.toList())); |
| |
| } catch (DataAccessException | IllegalArgumentException e) { |
| LOG.warn("Cannot retreive suggestions " + suggestionRequest + " for Environment + " + envName + "!", |
| e); |
| } |
| } |
| } |
| return suggestions; |
| } |
| |
| private QueryResult queryRowsForSource(ApplicationContext context, String resultEntity, List<String> columns, |
| int resultOffset, Integer resultLimit, String filterString, String searchString) throws DataAccessException { |
| |
| ModelManager modelManager = context.getModelManager() |
| .orElseThrow(() -> new ServiceNotProvidedException(ModelManager.class)); |
| |
| SearchService searchService = context.getSearchService() |
| .orElseThrow(() -> new IllegalStateException("neccessary SearchService is not available")); |
| |
| Class<? extends Entity> resultType = getEntityClassByNameType(searchService, resultEntity); |
| |
| List<EntityType> searchableTypes = searchService.listEntityTypes(resultType); |
| List<Attribute> attributes = columns.stream().map(c -> getAttribute(searchableTypes, c)) |
| .filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); |
| |
| Filter filter = FilterParser.parseFilterString(searchableTypes, filterString); |
| |
| List<Result> result = searchService.fetchResults(resultType, attributes, filter, searchString); |
| List<Row> rows = Util.convertResultList(extractRequestedSubList(resultOffset, resultLimit, result), resultType, |
| modelManager.getEntityType(resultType)); |
| return new QueryResult(rows, result.size()); |
| } |
| |
| /** |
| * Extracts requested results from all results. |
| * |
| * @param resultOffset if positive or zero: offset from start, if negative: offset from end |
| * @param resultLimit Specifies maximum results, if set to zero no limit is set. If null the value from maxResultsPerSource property is used instead. |
| * @param results the original list |
| * @return |
| */ |
| private List<Result> extractRequestedSubList(int resultOffset, Integer resultLimit, List<Result> results) { |
| int firstIndex = resultOffset < 0 ? results.size() + resultOffset : resultOffset; |
| int endIndex; |
| if (resultLimit == null) { |
| endIndex = Math.min(results.size(), getMaxResultsPerSource() + firstIndex); |
| } else if (resultLimit == 0) { |
| endIndex = results.size(); |
| } else { |
| endIndex = Math.min(results.size(), resultLimit + firstIndex); |
| } |
| return results.subList(firstIndex, endIndex); |
| } |
| |
| private Optional<Attribute> getAttribute(List<EntityType> searchableTypes, String c) { |
| String[] parts = c.split("\\."); |
| |
| if (parts.length != 2) { |
| throw new IllegalArgumentException("Cannot parse attribute " + c + "!"); |
| } |
| |
| String type = parts[0]; |
| String attributeName = parts[1]; |
| |
| Optional<EntityType> entityType = searchableTypes.stream() |
| .filter(e -> ServiceUtils.workaroundForTypeMapping(e).equalsIgnoreCase(type)).findFirst(); |
| |
| if (entityType.isPresent()) { |
| return entityType.get().getAttributes().stream().filter(a -> a.getName().equalsIgnoreCase(attributeName)) |
| .findFirst(); |
| } else { |
| return Optional.empty(); |
| } |
| } |
| |
| private Class<? extends Entity> getEntityClassByNameType(SearchService s, String name) { |
| |
| for (Class<? extends Entity> entityClass : s.listSearchableTypes()) { |
| if (entityClass.getSimpleName().equalsIgnoreCase(name)) { |
| return entityClass; |
| } |
| } |
| throw new IllegalArgumentException("Invalid Entity '" + name + "'. Allowed values are: " |
| + s.listSearchableTypes().stream().map(Class::getSimpleName).collect(Collectors.joining(", "))); |
| } |
| |
| |
| private int getMaxResultsPerSource() { |
| try { |
| return Integer.parseInt(maxResultsPerSource); |
| } catch (NumberFormatException e) { |
| return 1001; |
| } |
| } |
| |
| } |