blob: 757f2d5a9d3af443f815550e38fd6f7b37ee3f81 [file] [log] [blame]
/*
* 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");
}
}
}