blob: b64e2ae147a29a4a5f56f4d96cae7b8ccc8a5b36 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2013, 2017 CEA LIST and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* CEA LIST - Initial API and implementation
* Christian W. Damus (CEA) - bug 431953 (pre-requisite refactoring of ModelSet service start-up)
* Eike Stepper (CEA) - bug 466520
*
*****************************************************************************/
package org.eclipse.papyrus.cdo.uml.search.internal.ui.query;
import static com.google.common.base.Predicates.instanceOf;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.filter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.cdo.explorer.CDOExplorerUtil;
import org.eclipse.emf.cdo.explorer.checkouts.CDOCheckout;
import org.eclipse.emf.cdo.util.CDOURIUtil;
import org.eclipse.emf.cdo.view.CDOQuery;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.net4j.util.collection.Pair;
import org.eclipse.net4j.util.collection.Triplet;
import org.eclipse.papyrus.cdo.core.resource.CDOAwareModelSet;
import org.eclipse.papyrus.cdo.internal.core.CDOUtils;
import org.eclipse.papyrus.cdo.uml.search.internal.ui.Activator;
import org.eclipse.papyrus.cdo.uml.search.internal.ui.open.CDOOpenElementService;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.services.ServiceException;
import org.eclipse.papyrus.infra.core.services.ServicesRegistry;
import org.eclipse.papyrus.infra.core.utils.ServiceUtils;
import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForResourceSet;
import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService;
import org.eclipse.papyrus.infra.services.labelprovider.service.impl.LabelProviderServiceImpl;
import org.eclipse.papyrus.infra.services.openelement.service.OpenElementService;
import org.eclipse.papyrus.uml.search.ui.providers.ParticipantTypeAttribute;
import org.eclipse.papyrus.uml.search.ui.providers.ParticipantTypeElement;
import org.eclipse.papyrus.uml.search.ui.query.AbstractPapyrusQuery;
import org.eclipse.papyrus.uml.search.ui.query.CompositePapyrusQuery;
import org.eclipse.papyrus.uml.search.ui.query.IPapyrusQueryProvider;
import org.eclipse.papyrus.uml.search.ui.query.QueryInfo;
import org.eclipse.papyrus.views.search.utils.DefaultServiceRegistryTracker;
import org.eclipse.papyrus.views.search.utils.IServiceRegistryTracker;
import org.eclipse.uml2.uml.UMLPackage;
import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
/**
* A search-query provider for CDO model repositories.
*/
public class CDOSearchQueryProvider implements IPapyrusQueryProvider {
public CDOSearchQueryProvider() {
super();
}
@Override
public boolean canProvideFor(URI scope) {
return CDOUtils.isCDOURI(scope);
}
@Override
public AbstractPapyrusQuery createSimpleSearchQuery(QueryInfo queryInfo) {
Pair<String, Boolean> _searchPattern = getSearchPattern(queryInfo);
final String searchPattern = _searchPattern.getElement1();
final boolean isRegexMatch = _searchPattern.getElement2();
return createOCLSearchQuery(queryInfo, AttributeMatchStrategy.create(queryInfo), new Function<Triplet<QueryInfo, CDOView, Collection<URI>>, CDOQuery>() {
@Override
public CDOQuery apply(Triplet<QueryInfo, CDOView, Collection<URI>> input) {
Map<String, Object> parameters = Maps.newHashMap();
String ocl = createOCLExpression(searchPattern, isRegexMatch, input.getElement1().isSearchAllStringAttributes(), input.getElement3(), parameters);
CDOQuery result = input.getElement2().createQuery("ocl", ocl, UMLPackage.Literals.NAMED_ELEMENT);
// variables referenced by the OCL query expression
for (Map.Entry<String, ?> next : parameters.entrySet()) {
result.setParameter(next.getKey(), next.getValue());
}
return result;
}
});
}
@Override
public AbstractPapyrusQuery createAdvancedSearchQuery(QueryInfo queryInfo) {
Pair<String, Boolean> _searchPattern = getSearchPattern(queryInfo);
final String searchPattern = _searchPattern.getElement1();
final boolean isRegexMatch = _searchPattern.getElement2();
// build a multi-map of EClasses to EAttributes. For any EClass that doesn't have
// attributes specifically selected, add all of its attributes
final Multimap<EClass, EAttribute> attributes = ArrayListMultimap.create();
for (ParticipantTypeAttribute next : filter(queryInfo.getParticipantTypes(), ParticipantTypeAttribute.class)) {
if (next.getParent().getElement() instanceof EClass) {
EAttribute attr = (EAttribute) next.getElement();
attributes.put((EClass) next.getParent().getElement(), attr);
}
}
for (ParticipantTypeElement next : filter(queryInfo.getParticipantTypes(), not(instanceOf(ParticipantTypeAttribute.class)))) {
if (next.getElement() instanceof EClass) {
EClass eclass = (EClass) next.getElement();
if (!attributes.containsKey(eclass)) {
// don't bother looking for instances of classes that have no attributes to search anyways
if (!eclass.getEAllAttributes().isEmpty()) {
attributes.putAll(eclass, eclass.getEAllAttributes());
}
}
}
}
return createOCLSearchQuery(queryInfo, AttributeMatchStrategy.create(queryInfo, attributes), new Function<Triplet<QueryInfo, CDOView, Collection<URI>>, CDOQuery>() {
@Override
public CDOQuery apply(Triplet<QueryInfo, CDOView, Collection<URI>> input) {
Map<String, Object> parameters = Maps.newHashMap();
String ocl = createOCLExpression(searchPattern, isRegexMatch, attributes, input.getElement3(), parameters);
CDOQuery result = input.getElement2().createQuery("ocl", ocl, UMLPackage.Literals.NAMED_ELEMENT);
// variables referenced by the OCL query expression
for (Map.Entry<String, ?> next : parameters.entrySet()) {
result.setParameter(next.getKey(), next.getValue());
}
return result;
}
});
}
protected Pair<String, Boolean> getSearchPattern(QueryInfo queryInfo) {
String searchPattern = PatternUtil.wrap(queryInfo.getQueryText(), queryInfo.isCaseSensitive(), queryInfo.isRegularExpression(), queryInfo.isSearchAllStringAttributes());
boolean isRegexMatch = searchPattern != null;
if (!isRegexMatch) {
searchPattern = queryInfo.getQueryText();
}
return Pair.create(searchPattern, isRegexMatch);
}
protected AbstractPapyrusQuery createOCLSearchQuery(QueryInfo queryInfo, AttributeMatchStrategy attributeMatcheStrategy, Function<Triplet<QueryInfo, CDOView, Collection<URI>>, CDOQuery> queryFunction) {
IServiceRegistryTracker tracker = new DefaultServiceRegistryTracker();
Multimap<CDOView, URI> views = getViews(queryInfo.getScope());
List<AbstractPapyrusQuery> result = Lists.newArrayListWithCapacity(views.keySet().size());
for (CDOView view : views.keySet()) {
CDOQuery query = queryFunction.apply(new Triplet<QueryInfo, CDOView, Collection<URI>>(queryInfo, view, views.get(view)));
// parameters for the server-side OCL query handler
query.setParameter("cdoImplicitRootClass", EcorePackage.Literals.EOBJECT);
AbstractPapyrusQuery searchQuery = new CDOPapyrusQuery(queryInfo.getQueryText(), view, query, attributeMatcheStrategy);
result.add(searchQuery);
try {
// automatically clean up the view and services registry when no longer needed
tracker.track(searchQuery, ServiceUtilsForResourceSet.getInstance().getServiceRegistry(view.getResourceSet()));
} catch (ServiceException e) {
Activator.log.error("Cannot track services registry for automatic clean-up.", e); //$NON-NLS-1$
}
}
return CompositePapyrusQuery.compose(result);
}
protected Multimap<CDOView, URI> getViews(Collection<URI> scope) {
Multimap<CDOView, URI> result = HashMultimap.create();
Map<CDOCheckout, CDOView> views = Maps.newHashMap();
try {
for (URI uri : scope) {
CDOCheckout checkout = CDOExplorerUtil.getCheckout(uri);
if ((checkout != null) && checkout.isOpen()) {
CDOView view = views.get(checkout);
if (view == null) {
// no view, yet, for this repo
ServicesRegistry services = new ServicesRegistry();
services.add(LabelProviderService.class, 10, new LabelProviderServiceImpl());
services.add(OpenElementService.class, 10, new CDOOpenElementService());
services.add(ModelSet.class, 10, new CDOAwareModelSet());
services.startRegistry();
// create our own transaction for the model-set
view = checkout.openTransaction(ServiceUtils.getInstance().getModelSet(services));
views.put(checkout, view);
}
result.put(view, uri);
}
}
} catch (ServiceException e) {
Activator.log.error("Failed to initialize service registry for CDO search query.", e); //$NON-NLS-1$
}
return result;
}
/**
* Create the OCL query expression for a "basic" (from the user's perspective) search.
*/
protected String createOCLExpression(String searchPattern, boolean isRegexMatch, boolean isAllStringAttributes, Collection<URI> scope, Map<String, Object> parameters) {
StringBuilder result = new StringBuilder();
// parameters to pass through to OCL
parameters.put("searchPattern", searchPattern); //$NON-NLS-1$
// first, build the CDOResource.allInstances() select clause for the scope
StringBuilder scopeClause = getScopeClause(scope);
// based on the CDOResource scope clause, find the candidate NamedElements
if (scopeClause.length() == 0) {
// easy case. Do an allInstances() query
result.append("NamedElement.allInstances()"); //$NON-NLS-1$
} else {
// iterate the contents of resources matching the scope criteria
result.append("eresource::CDOResource.allInstances()->select(r | ");
result.append(scopeClause);
// close the CDOResource.allInstances()->select(...) scope clause
result.append(")"); //$NON-NLS-1$
// and collect all of the NamedElements within those resources
result.append("->collect(r | r.cdoAllProperContents(NamedElement))"); //$NON-NLS-1$
}
// from our candidate NamedElements, select those that match
if (isAllStringAttributes) {
result.append("->select(e | e.cdoMatches(searchPattern))"); //$NON-NLS-1$
} else {
result.append("->select(e | not e.name.oclIsUndefined() and e.name."); //$NON-NLS-1$
if (isRegexMatch) {
result.append("matches(searchPattern)"); //$NON-NLS-1$
} else {
result.append("indexOf(searchPattern) > 0"); //$NON-NLS-1$
}
// close the ->select(...)
result.append(")"); //$NON-NLS-1$
}
return result.toString();
}
protected StringBuilder getScopeClause(Iterable<URI> scope) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (URI uri : scope) {
String path = CDOURIUtil.extractResourcePath(uri);
if (uri.hasTrailingPathSeparator() && !path.endsWith("/")) { //$NON-NLS-1$
path = path + "/"; //$NON-NLS-1$
}
if ((path.length() > 1) || (!path.startsWith("/") && (path.length() > 0))) { //$NON-NLS-1$
if (first) {
first = false;
} else {
result.append(" or "); //$NON-NLS-1$
}
result.append("r.path.startsWith('"); //$NON-NLS-1$
result.append(oclQuoteString(path));
result.append("')"); //$NON-NLS-1$
}
}
return result;
}
/**
* Create the OCL query expression for an "advanced" (from the user's perspective) search.
*/
protected String createOCLExpression(String searchPattern, boolean isRegexMatch, Multimap<EClass, EAttribute> attributes, Collection<URI> scope, Map<String, Object> parameters) {
StringBuilder result = new StringBuilder();
// parameters to pass through to OCL
parameters.put("searchPattern", searchPattern); //$NON-NLS-1$
// first, build the CDOResource.allInstances() select clause for the scope
StringBuilder scopeClause = getScopeClause(scope);
// based on the CDOResource scope clause, find the candidate NamedElements
if (scopeClause.length() == 0) {
// easy case. Do an allInstances() query
result.append("NamedElement.allInstances()"); //$NON-NLS-1$
} else {
// iterate the contents of resources matching the scope criteria
result.append("eresource::CDOResource.allInstances()->select(r | ");
result.append(scopeClause);
// close the CDOResource.allInstances()->select(...) scope clause
result.append(")"); //$NON-NLS-1$
// and collect all of the NamedElements within those resources
result.append("->collect(r | r.cdoAllProperContents(NamedElement))"); //$NON-NLS-1$
}
// from our candidate elements, select those that match the attribute criteria
result.append("->select(e | "); //$NON-NLS-1$
boolean firstEClass = true;
for (EClass next : attributes.keySet()) {
if (firstEClass) {
firstEClass = false;
} else {
result.append(" or ");
}
result.append("e.oclIsKindOf(").append(next.getName()).append(") and ("); //$NON-NLS-1$ //$NON-NLS-2$
result.append("let s : ").append(next.getName()).append(" = e.oclAsType(").append(next.getName()).append(") in "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
boolean firstAttr = true;
for (EAttribute attr : attributes.get(next)) {
if (firstAttr) {
firstAttr = false;
} else {
result.append(" or ");
}
if (isString(attr)) {
if (attr.isMany()) {
result.append("s.").append(attr.getName()).append("->excluding(null)->exists(v | v"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
result.append("(not s.").append(attr.getName()).append(".oclIsUndefined() and s.").append(attr.getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
if (isRegexMatch) {
result.append(".matches(searchPattern)"); //$NON-NLS-1$
} else {
result.append(".indexOf(searchPattern) > 0"); //$NON-NLS-1$
}
// close the exists iterator (many case) or 'and' group (scalar case)
result.append(")"); //$NON-NLS-1$
} else {
// need toString() conversions. For simplicity, because we're doing extra conversions anyways, coerce scalars to sets.
// N.B.: toString() can produce nulls that need to be filtered out!
result.append("s.").append(attr.getName()).append("->excluding(null).toString()->excluding(null)->exists(v | v"); //$NON-NLS-1$ //$NON-NLS-2$
if (isRegexMatch) {
result.append(".matches(searchPattern)"); //$NON-NLS-1$
} else {
result.append(".indexOf(searchPattern) > 0"); //$NON-NLS-1$
}
// close the exists iterator
result.append(")"); //$NON-NLS-1$
}
}
// close the let expression
result.append(")"); //$NON-NLS-1$
}
// close select clause
result.append(")"); //$NON-NLS-1$
return result.toString();
}
static String oclQuoteString(String s) {
return s.replace("'", "\\'") //$NON-NLS-1$ //$NON-NLS-2$
.replace("\\", "\\\\"); //$NON-NLS-1$ //$NON-NLS-2$
}
static boolean isString(ETypedElement element) {
EClassifier type = element.getEType();
return (type != null) && (type.getInstanceClass() == String.class);
}
}