| /* |
| * Copyright (c) 2010-2015 Eike Stepper (Berlin, Germany) 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: |
| * Eike Stepper - initial API and implementation |
| * Christian W. Damus (CEA LIST) - Additional OCL operations for efficient queries |
| */ |
| package org.eclipse.emf.cdo.server.ocl; |
| |
| import org.eclipse.emf.cdo.common.commit.CDOChangeSetData; |
| import org.eclipse.emf.cdo.common.id.CDOID; |
| import org.eclipse.emf.cdo.common.model.CDOPackageInfo; |
| import org.eclipse.emf.cdo.common.model.CDOPackageRegistry; |
| import org.eclipse.emf.cdo.common.model.CDOPackageUnit; |
| import org.eclipse.emf.cdo.common.revision.CDORevision; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionCacheAdder; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionProvider; |
| import org.eclipse.emf.cdo.common.util.CDOQueryInfo; |
| import org.eclipse.emf.cdo.server.CDOServerUtil; |
| import org.eclipse.emf.cdo.server.IQueryContext; |
| import org.eclipse.emf.cdo.server.IQueryHandler; |
| import org.eclipse.emf.cdo.spi.common.commit.CDOChangeSetDataRevisionProvider; |
| import org.eclipse.emf.cdo.spi.server.QueryHandlerFactory; |
| import org.eclipse.emf.cdo.view.CDOView; |
| |
| import org.eclipse.net4j.util.WrappedException; |
| import org.eclipse.net4j.util.container.IManagedContainer; |
| import org.eclipse.net4j.util.factory.ProductCreationException; |
| |
| import org.eclipse.emf.common.util.Diagnostic; |
| import org.eclipse.emf.common.util.DiagnosticException; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.Enumerator; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EEnumLiteral; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.spi.cdo.FSMUtil; |
| import org.eclipse.emf.spi.cdo.InternalCDOObject; |
| |
| import org.eclipse.ocl.Environment; |
| import org.eclipse.ocl.EvaluationEnvironment; |
| import org.eclipse.ocl.OCL; |
| import org.eclipse.ocl.ParserException; |
| import org.eclipse.ocl.Query; |
| import org.eclipse.ocl.ecore.BooleanLiteralExp; |
| import org.eclipse.ocl.ecore.Constraint; |
| import org.eclipse.ocl.ecore.EcoreEnvironmentFactory; |
| import org.eclipse.ocl.ecore.EcoreFactory; |
| import org.eclipse.ocl.ecore.EnumLiteralExp; |
| import org.eclipse.ocl.ecore.IntegerLiteralExp; |
| import org.eclipse.ocl.ecore.RealLiteralExp; |
| import org.eclipse.ocl.ecore.StringLiteralExp; |
| import org.eclipse.ocl.expressions.OCLExpression; |
| import org.eclipse.ocl.expressions.Variable; |
| import org.eclipse.ocl.helper.OCLHelper; |
| import org.eclipse.ocl.options.ParsingOptions; |
| import org.eclipse.ocl.types.OCLStandardLibrary; |
| import org.eclipse.ocl.util.ProblemAware; |
| import org.eclipse.ocl.util.Tuple; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| /** |
| * A {@link IQueryHandler query handler} that evaluates OCL query expressions. |
| * |
| * @author Eike Stepper |
| */ |
| public class OCLQueryHandler implements IQueryHandler |
| { |
| public static final String LANGUAGE_NAME = "ocl"; //$NON-NLS-1$ |
| |
| /** |
| * @since 4.1 |
| */ |
| public static final String LAZY_EXTENTS_PARAMETER = "cdoLazyExtents"; |
| |
| /** |
| * Query parameter indicating the {@link EClass} to set as the implicit root class of the type |
| * hierarchy. The default is {@code null}. |
| * |
| * @since 4.2 |
| */ |
| public static final String IMPLICIT_ROOT_CLASS_PARAMETER = "cdoImplicitRootClass"; |
| |
| private static final Set<String> SERVER_QUERY_PARAMETERS = Collections.unmodifiableSet( |
| new java.util.HashSet<String>(Arrays.asList(LAZY_EXTENTS_PARAMETER, IMPLICIT_ROOT_CLASS_PARAMETER))); |
| |
| private static final EcoreFactory FACTORY = EcoreFactory.eINSTANCE; |
| |
| private boolean lazyExtents = true; |
| |
| private EClass implicitRootClass; |
| |
| public OCLQueryHandler() |
| { |
| } |
| |
| public void executeQuery(CDOQueryInfo info, IQueryContext context) |
| { |
| CDOExtentMap extentMap = null; |
| |
| try |
| { |
| readParameters(info.getParameters()); |
| |
| CDORevisionProvider revisionProvider = context.getView(); |
| CDOChangeSetData changeSetData = info.getChangeSetData(); |
| if (changeSetData != null) |
| { |
| revisionProvider = new CDOChangeSetDataRevisionProvider(revisionProvider, changeSetData); |
| } |
| |
| CDOView view = CDOServerUtil.openView(context.getView().getSession(), context, revisionProvider); |
| extentMap = createExtentMap(view, changeSetData, context); |
| OCL<?, EClassifier, ?, ?, ?, ?, ?, ?, ?, Constraint, EClass, EObject> ocl = createOCL(view, extentMap); |
| |
| ContextParameter contextParameter = getContextParameter(info, view); |
| Query<EClassifier, EClass, EObject> query = createQuery(view, info, contextParameter, ocl); |
| |
| Object result = evaluate(query, contextParameter.getObject()); |
| if (result == ocl.getEnvironment().getOCLStandardLibrary().getInvalid()) |
| { |
| throw new Exception( |
| "OCL query evaluated to 'invalid'. Run with '-Dorg.eclipse.ocl.debug=true' and visit the log for failure details."); |
| } |
| |
| if (result instanceof Collection<?>) |
| { |
| for (Object element : (Collection<?>)result) |
| { |
| if (!addResult(element, context, view)) |
| { |
| break; |
| } |
| } |
| } |
| else |
| { |
| addResult(result, context, view); |
| } |
| } |
| catch (Exception ex) |
| { |
| throw WrappedException.wrap(ex, "Problem executing OCL query: " + info.getQueryString()); |
| } |
| finally |
| { |
| if (extentMap != null) |
| { |
| extentMap.cancel(); |
| } |
| } |
| } |
| |
| protected boolean addResult(Object result, IQueryContext context, CDOView view) |
| { |
| result = convertResult(result, view); |
| return context.addResult(result); |
| } |
| |
| protected CDORevision getRevision(EObject object, CDOView view) |
| { |
| return FSMUtil.adapt(object, view).cdoRevision(); |
| } |
| |
| protected Object evaluate(Query<EClassifier, EClass, EObject> query, EObject object) |
| { |
| if (object == null) |
| { |
| return query.evaluate(); |
| } |
| |
| return query.evaluate(object); |
| } |
| |
| protected CDOExtentMap createExtentMap(CDOView view, CDOChangeSetData changeSetData, IQueryContext context) |
| { |
| CDOExtentCreator creator = createsLazyExtents() ? new CDOExtentCreator.Lazy(view) : new CDOExtentCreator(view); |
| creator.setChangeSetData(changeSetData); |
| creator.setRevisionCacheAdder((CDORevisionCacheAdder)context.getView().getRepository().getRevisionManager()); |
| return new CDOExtentMap(creator); |
| } |
| |
| protected boolean createsLazyExtents() |
| { |
| return lazyExtents; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected OCL<?, EClassifier, ?, ?, ?, ?, ?, ?, ?, Constraint, EClass, EObject> createOCL(CDOView view, |
| CDOExtentMap extentMap) |
| { |
| EcoreEnvironmentFactory envFactory = new CDOEnvironmentFactory(view.getSession().getPackageRegistry()); |
| |
| OCL<?, EClassifier, ?, ?, ?, ?, ?, ?, ?, Constraint, EClass, EObject> ocl = OCL.newInstance(envFactory); |
| CDOAdditionalOperation.registerOperations((CDOEnvironment)ocl.getEnvironment()); |
| ocl.setExtentMap(extentMap); |
| return ocl; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected Query<EClassifier, EClass, EObject> createQuery(CDOView view, CDOQueryInfo info, |
| ContextParameter contextParameter, OCL<?, EClassifier, ?, ?, ?, ?, ?, ?, ?, Constraint, EClass, EObject> ocl) |
| throws ParserException, DiagnosticException |
| { |
| Map<String, Object> parameters = new HashMap<String, Object>(info.getParameters()); |
| initEnvironment(ocl.getEnvironment(), view.getSession().getPackageRegistry(), parameters); |
| |
| OCLHelper<EClassifier, ?, ?, Constraint> helper = ocl.createOCLHelper(); |
| helper.setContext(contextParameter.getClassifier()); |
| |
| OCLExpression<EClassifier> expr = helper.createQuery(info.getQueryString()); |
| Query<EClassifier, EClass, EObject> query = ocl.createQuery(expr); |
| if (query instanceof ProblemAware) |
| { |
| ProblemAware problemAware = (ProblemAware)query; |
| Diagnostic problems = problemAware.getProblems(); |
| if (problems != null) |
| { |
| throw new DiagnosticException(problems); |
| } |
| } |
| |
| setOCLQueryParameters(parameters, query); |
| return query; |
| } |
| |
| /** |
| * @deprecated As of 4.2 no longer supported. |
| */ |
| @Deprecated |
| protected EClassifier getArbitraryContextClassifier(CDOPackageRegistry packageRegistry) |
| { |
| return ContextParameter.getArbitraryContextClassifier(packageRegistry); |
| } |
| |
| protected void initEnvironment( |
| Environment<?, EClassifier, ?, ?, ?, ?, ?, ?, ?, Constraint, EClass, EObject> environment, |
| CDOPackageRegistry packageRegistry, Map<String, Object> parameters) |
| { |
| |
| // initialize parsing options |
| EClass implicitRootClass = getImplicitRootClass(); |
| if (implicitRootClass != null) |
| { |
| ParsingOptions.setOption(environment, ParsingOptions.implicitRootClass(environment), implicitRootClass); |
| } |
| |
| // create variables for query parameters that should be passed through to the OCL query expression |
| OCLStandardLibrary<EClassifier> stdLib = environment.getOCLStandardLibrary(); |
| for (Entry<String, Object> parameter : parameters.entrySet()) |
| { |
| String name = parameter.getKey(); |
| if (isOCLQueryParameter(name)) |
| { |
| Object value = parameter.getValue(); |
| |
| OCLExpression<EClassifier> initExpression = createInitExpression(stdLib, packageRegistry, value); |
| |
| Variable<EClassifier, ?> variable = FACTORY.createVariable(); |
| variable.setName(name); |
| variable.setType(initExpression.getType()); |
| variable.setInitExpression(initExpression); |
| |
| addEnvironmentVariable(environment, variable); |
| } |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected EClass getImplicitRootClass() |
| { |
| return implicitRootClass; |
| } |
| |
| protected OCLExpression<EClassifier> createInitExpression(OCLStandardLibrary<EClassifier> stdLib, |
| CDOPackageRegistry packageRegistry, Object value) |
| { |
| if (value instanceof String) |
| { |
| String v = (String)value; |
| StringLiteralExp literal = FACTORY.createStringLiteralExp(); |
| literal.setType(stdLib.getString()); |
| literal.setStringSymbol(v); |
| return literal; |
| } |
| |
| if (value instanceof Boolean) |
| { |
| Boolean v = (Boolean)value; |
| BooleanLiteralExp literal = FACTORY.createBooleanLiteralExp(); |
| literal.setType(stdLib.getBoolean()); |
| literal.setBooleanSymbol(v); |
| return literal; |
| } |
| |
| Integer integerValue = getInteger(value); |
| if (integerValue != null) |
| { |
| IntegerLiteralExp literal = FACTORY.createIntegerLiteralExp(); |
| literal.setType(stdLib.getInteger()); |
| literal.setIntegerSymbol(integerValue); |
| return literal; |
| } |
| |
| Double doubleValue = getDouble(value); |
| if (doubleValue != null) |
| { |
| RealLiteralExp literal = FACTORY.createRealLiteralExp(); |
| literal.setType(stdLib.getReal()); |
| literal.setRealSymbol(doubleValue); |
| return literal; |
| } |
| |
| if (value instanceof Enumerator) |
| { |
| Enumerator v = (Enumerator)value; |
| String name = v.getName(); |
| |
| EEnumLiteral eEnumLiteral = packageRegistry.getEnumLiteralFor(v); |
| if (eEnumLiteral == null) |
| { |
| throw new IllegalArgumentException("Enum literal not found: " + name); |
| } |
| |
| EnumLiteralExp literal = FACTORY.createEnumLiteralExp(); |
| literal.setType(eEnumLiteral.getEEnum()); |
| literal.setReferredEnumLiteral(eEnumLiteral); |
| return literal; |
| } |
| |
| throw new IllegalArgumentException("Unrecognized parameter type: " + value.getClass().getName()); |
| } |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| protected void addEnvironmentVariable( |
| Environment<?, EClassifier, ?, ?, ?, ?, ?, ?, ?, Constraint, EClass, EObject> environment, |
| Variable<EClassifier, ?> variable) |
| { |
| environment.addElement(variable.getName(), (Variable)variable, true); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected ContextParameter getContextParameter(CDOQueryInfo info, CDOView view) |
| { |
| return new ContextParameter(view, info); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void readParameters(Map<String, ?> queryParameters) |
| { |
| lazyExtents = readParameter(queryParameters, LAZY_EXTENTS_PARAMETER, lazyExtents); |
| implicitRootClass = readParameter(queryParameters, IMPLICIT_ROOT_CLASS_PARAMETER, EClass.class, implicitRootClass); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected boolean readParameter(Map<String, ?> queryParameters, String name, boolean defaultValue) |
| { |
| return readParameter(queryParameters, name, Boolean.class, defaultValue); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected <T> T readParameter(Map<String, ?> queryParameters, String name, Class<T> type, T defaultValue) |
| { |
| T result = defaultValue; |
| |
| Object o = queryParameters.get(name); |
| if (o != null) |
| { |
| try |
| { |
| result = type.cast(o); |
| } |
| catch (ClassCastException ex) |
| { |
| throw new IllegalArgumentException("Parameter " + name + " must be a " + type.getSimpleName() + " but it is a " |
| + o + " class " + o.getClass().getName(), ex); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void setOCLQueryParameters(Map<String, Object> parameters, Query<EClassifier, EClass, EObject> query) |
| { |
| EvaluationEnvironment<EClassifier, ?, ?, EClass, EObject> evalEnv = query.getEvaluationEnvironment(); |
| for (Entry<String, Object> parameter : parameters.entrySet()) |
| { |
| String key = parameter.getKey(); |
| |
| if (isOCLQueryParameter(key)) |
| { |
| Object value = parameter.getValue(); |
| evalEnv.add(key, value); |
| } |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected boolean isOCLQueryParameter(String name) |
| { |
| return !SERVER_QUERY_PARAMETERS.contains(name); |
| } |
| |
| private Integer getInteger(Object value) |
| { |
| if (value instanceof Integer) |
| { |
| return (Integer)value; |
| } |
| |
| if (value instanceof Short) |
| { |
| return (int)(Short)value; |
| } |
| |
| if (value instanceof Byte) |
| { |
| return (int)(Byte)value; |
| } |
| |
| return null; |
| } |
| |
| private Double getDouble(Object value) |
| { |
| if (value instanceof Double) |
| { |
| return (Double)value; |
| } |
| |
| if (value instanceof Float) |
| { |
| return (double)(Float)value; |
| } |
| |
| return null; |
| } |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| private Object convertResult(Object result, CDOView view) |
| { |
| if (result instanceof EObject) |
| { |
| return getRevision((EObject)result, view); |
| } |
| |
| if (result instanceof Tuple) |
| { |
| Tuple tuple = (Tuple)result; |
| EList properties = tuple.getTupleType().oclProperties(); |
| Object[] array = new Object[properties.size()]; |
| for (int i = 0; i < array.length; ++i) |
| { |
| array[i] = convertResult(tuple.getValue(properties.get(i)), view); |
| } |
| |
| return array; |
| } |
| |
| return result; |
| } |
| |
| public static void prepareContainer(IManagedContainer container) |
| { |
| container.registerFactory(new Factory()); |
| } |
| |
| /** |
| * Creates {@link OCLQueryHandler} instances. |
| * |
| * @author Eike Stepper |
| */ |
| public static class Factory extends QueryHandlerFactory |
| { |
| public Factory() |
| { |
| super(LANGUAGE_NAME); |
| } |
| |
| @Override |
| public OCLQueryHandler create(String description) throws ProductCreationException |
| { |
| return new OCLQueryHandler(); |
| } |
| } |
| |
| /** |
| * An abstraction of the {@link EClassifier classifier} and/or {@link EObject obejct} of an OCL query context parameter. |
| * |
| * @author Eike Stepper |
| * @since 4.2 |
| */ |
| protected static final class ContextParameter |
| { |
| private final EClassifier classifier; |
| |
| private final EObject object; |
| |
| public ContextParameter(CDOView view, CDOQueryInfo info) |
| { |
| Object contextParameter = info.getContext(); |
| if (contextParameter instanceof CDOID) |
| { |
| CDOID id = (CDOID)contextParameter; |
| if (id.isNull()) |
| { |
| CDOPackageRegistry packageRegistry = view.getSession().getPackageRegistry(); |
| classifier = getArbitraryContextClassifier(packageRegistry); |
| object = null; |
| } |
| else |
| { |
| InternalCDOObject cdoObject = (InternalCDOObject)view.getObject(id); |
| classifier = cdoObject.eClass(); |
| object = cdoObject.cdoInternalInstance(); |
| } |
| } |
| else if (contextParameter instanceof EClassifier) |
| { |
| classifier = (EClassifier)contextParameter; |
| object = null; |
| } |
| else |
| { |
| CDOPackageRegistry packageRegistry = view.getSession().getPackageRegistry(); |
| classifier = getArbitraryContextClassifier(packageRegistry); |
| object = null; |
| } |
| } |
| |
| public ContextParameter(EClassifier classifier, EObject object) |
| { |
| this.classifier = classifier; |
| this.object = object; |
| } |
| |
| public EClassifier getClassifier() |
| { |
| return classifier; |
| } |
| |
| public EObject getObject() |
| { |
| return object; |
| } |
| |
| protected static EClassifier getArbitraryContextClassifier(CDOPackageRegistry packageRegistry) |
| { |
| for (CDOPackageUnit packageUnit : packageRegistry.getPackageUnits()) |
| { |
| for (CDOPackageInfo packageInfo : packageUnit.getPackageInfos()) |
| { |
| for (EClassifier classifier : packageInfo.getEPackage().getEClassifiers()) |
| { |
| return classifier; |
| } |
| } |
| } |
| |
| throw new IllegalStateException("Context missing"); |
| } |
| } |
| } |