All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmdx.application.rest.adapter.InboundConnection_2 Maven / Gradle / Ivy

There is a newer version: 2.18.10
Show newest version
/*
 * ====================================================================
 * Project:     openMDX/Core, http://www.openmdx.org/
 * Description: InboundConnection_2 
 * Owner:       OMEX AG, Switzerland, http://www.omex.ch
 * ====================================================================
 *
 * This software is published under the BSD license as listed below.
 * 
 * Copyright (c) 2009-2014, OMEX AG, Switzerland
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * * Neither the name of the openMDX team nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * ------------------
 * 
 * This product includes software developed by other organizations as
 * listed in the NOTICE file.
 */
package org.openmdx.application.rest.adapter;

import static org.openmdx.base.accessor.rest.spi.ControlObjects_2.isTransactionCommitIdentifier;
import static org.openmdx.base.accessor.rest.spi.ControlObjects_2.isTransactionObjectIdentifier;

import java.io.InputStream;
import java.io.Reader;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import javax.jdo.Constants;
import javax.jdo.FetchGroup;
import javax.jdo.FetchPlan;
import javax.jdo.JDOException;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jmi.reflect.InvalidObjectException;
import javax.jmi.reflect.JmiException;
import javax.jmi.reflect.RefException;
import javax.jmi.reflect.RefObject;
import javax.jmi.reflect.RefPackage;
import javax.jmi.reflect.RefStruct;
import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.cci.IndexedRecord;
import javax.resource.cci.Interaction;
import javax.resource.cci.InteractionSpec;
import javax.resource.cci.LocalTransaction;
import javax.resource.cci.MappedRecord;
import javax.resource.cci.Record;
import javax.resource.spi.EISSystemException;
import javax.resource.spi.LocalTransactionException;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;

import org.oasisopen.cci2.QualifierType;
import org.oasisopen.jmi1.RefContainer;
import org.openmdx.base.accessor.cci.SystemAttributes;
import org.openmdx.base.accessor.jmi.cci.RefObject_1_0;
import org.openmdx.base.accessor.jmi.cci.RefPackage_1_0;
import org.openmdx.base.accessor.jmi.cci.RefStruct_1_0;
import org.openmdx.base.accessor.jmi.spi.ReferenceDef;
import org.openmdx.base.exception.ServiceException;
import org.openmdx.base.mof.cci.ModelElement_1_0;
import org.openmdx.base.mof.cci.ModelHelper;
import org.openmdx.base.mof.cci.Model_1_0;
import org.openmdx.base.mof.cci.Multiplicity;
import org.openmdx.base.mof.cci.PrimitiveTypes;
import org.openmdx.base.mof.spi.Model_1Factory;
import org.openmdx.base.naming.Path;
import org.openmdx.base.naming.TransactionalSegment;
import org.openmdx.base.persistence.cci.PersistenceHelper;
import org.openmdx.base.persistence.cci.Queries;
import org.openmdx.base.persistence.spi.SharedObjects;
import org.openmdx.base.persistence.spi.UnitOfWork;
import org.openmdx.base.resource.Records;
import org.openmdx.base.resource.cci.ConnectionFactory;
import org.openmdx.base.resource.spi.LocalTransactions;
import org.openmdx.base.resource.spi.ResourceExceptions;
import org.openmdx.base.resource.spi.RestInteractionSpec;
import org.openmdx.base.rest.cci.FeatureOrderRecord;
import org.openmdx.base.rest.cci.MessageRecord;
import org.openmdx.base.rest.cci.ObjectRecord;
import org.openmdx.base.rest.cci.QueryFilterRecord;
import org.openmdx.base.rest.cci.QueryRecord;
import org.openmdx.base.rest.cci.RestConnection;
import org.openmdx.base.rest.cci.RestConnectionSpec;
import org.openmdx.base.rest.cci.ResultRecord;
import org.openmdx.base.rest.spi.AbstractConnection;
import org.openmdx.base.rest.spi.AbstractRestInteraction;
import org.openmdx.base.rest.spi.Numbers;
import org.openmdx.base.rest.spi.Object_2Facade;
import org.openmdx.base.transaction.Status;
import org.openmdx.kernel.exception.BasicException;
import org.w3c.cci2.BinaryLargeObjects;
import org.w3c.cci2.CharacterLargeObjects;
import org.w3c.cci2.SparseArray;
import org.w3c.spi2.Datatypes;

/**
 * Inbound Connection
 * 
 * TODO Let's configure
 * 
    *
  • the optimal fetch size
  • *
  • a maximal batch size limit (leading to quota exceeded exception when exceeded)
  • *
*/ public class InboundConnection_2 extends AbstractConnection { /** * Constructor * * @param connectionSpec * the JCA connection specification * @param persistenceManager * the JDO persistence manager * * @throws ResourceException */ public InboundConnection_2( ConnectionFactory connectionFactory, RestConnectionSpec connectionSpec, PersistenceManager persistenceManager ) throws ResourceException { super(connectionFactory, connectionSpec); this.persistenceManager = persistenceManager; this.localTransaction = createLocalTransaction(persistenceManager); } /** * The JDO persistence manager */ private PersistenceManager persistenceManager; /** * The inbound connection's transaction adapter */ final LocalTransaction localTransaction; /** * The org::openmdx::base authority id */ protected static final Path BASE_AUTHORITY = new Path("xri://@openmdx*org.openmdx.base"); /** * Used in case of FetchPlan.FETCH_SIZE_OPTIMAL */ protected static final int OPTIMAL_FETCH_SIZE = 64; /** * No limit (yet) in case of FetchPlan.FETCH_SIZE_GREEDY */ protected static final int BATCH_SIZE_LIMIT = Integer.MAX_VALUE; private LocalTransaction createLocalTransaction( PersistenceManager persistenceManager ) throws ResourceException { return isResourceLocalTransaction(persistenceManager) ? LocalTransactions.getLocalTransaction(persistenceManager) : new TransitionalTransactionAdapter(); } protected UnitOfWork currentUnitOfWork() { return (UnitOfWork) PersistenceHelper.currentUnitOfWork(getPersistenceManager()); } /** * Determines whether a JTA-Transaction or a resource local transaction is used. * * @return {@code true} if a resource local transaction is used */ private static boolean isResourceLocalTransaction( PersistenceManager persistenceManager ) { return Constants.RESOURCE_LOCAL.equals(persistenceManager.getPersistenceManagerFactory().getTransactionType()); } /** * Retrieve an object by its resource identifier * * @param resourceIdentifier * which may be null * * @return the requested object or null if the resource identifier is null */ protected RefObject getObjectByResourceIdentifier( Object resourceIdentifier ) { if (resourceIdentifier == null) { // // Null Object Id // return null; } Object objectId = resourceIdentifier; if (objectId instanceof String) { objectId = new Path((String) objectId); } if (objectId instanceof Path) { Path xri = (Path) objectId; if (xri.getLastSegment() instanceof TransactionalSegment) { objectId = ((TransactionalSegment) xri.getLastSegment()).getTransactionalObjectId(); } } return (RefObject) getPersistenceManager().getObjectById(objectId); } /** * Retrieve an object's XRO *
    *
  • a $t*uuid XRI in case of a transient object *
  • an @openmdx XRI in case of a persistent object *
* * @param object * @return the object's resource identifier */ protected static Path getResourceIdentifier( Object object ) { return JDOHelper.isPersistent(object) ? (Path) JDOHelper.getObjectId(object) : new Path( (UUID) JDOHelper.getTransactionalObjectId(object) ); } /* * (non-Javadoc) * * @see javax.resource.cci.Connection#close() */ @Override public void close() throws ResourceException { super.close(); try { this.persistenceManager.close(); } catch (JDOException exception) { throw ResourceExceptions.initHolder( new EISSystemException( "Connection disposal failure", BasicException.newEmbeddedExceptionStack( exception, BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.DEACTIVATION_FAILURE ) ) ); } finally { this.persistenceManager = null; } } /* * (non-Javadoc) * * @see javax.resource.cci.Connection#getLocalTransaction() */ public LocalTransaction getLocalTransaction() throws ResourceException { assertResourceLocalTransaction(); return this.localTransaction; } /** * Asserts that local transaction demarcation is supported *

* TODO
* It will be unnecessary to distinguish the two transaction * types once the JTAAdapter delegates the requests to the JTA * transaction. *
* * @throws NotSupportedException */ private void assertResourceLocalTransaction() throws NotSupportedException { if (!isResourceLocalTransaction(this.persistenceManager)) { throw new NotSupportedException( "Local transaction demarcation is supported if and only if " + "the transaction type is " + Constants.RESOURCE_LOCAL ); } } /* * (non-Javadoc) * * @see javax.resource.cci.Connection#createInteraction() */ public Interaction createInteraction() throws ResourceException { return new InboundInteraction(this); } /** * Provide the for the inbound interaction * * @return the */ protected PersistenceManager getPersistenceManager() { return this.persistenceManager; } // ------------------------------------------------------------------------ // Class TransitionalTransactionAdapter // ------------------------------------------------------------------------ /** * Transitional Transaction Adapter for transaction type {@code JCA} *

* TODO
* This implementation keeps the actual behaviour for the moment.
* But in the light of the (CDI induced) recent changes it seems more * appropriate to forward the the transaction control requests to JTA and * rely on the call-back for container managed transactions.
*/ class TransitionalTransactionAdapter implements LocalTransaction { /* * (non-Javadoc) * * @see javax.resource.cci.LocalTransaction#begin() */ @Override public void begin() throws ResourceException { try { final UnitOfWork unitOfWork = currentUnitOfWork(); if (!unitOfWork.isActive()) { unitOfWork.begin(); } } catch (JDOException exception) { throw ResourceExceptions.toResourceException(exception); } } /* * (non-Javadoc) * * @see javax.resource.cci.LocalTransaction#commit() */ @Override public void commit() throws ResourceException { try { currentUnitOfWork().beforeCompletion(); } catch (JDOException exception) { throw ResourceExceptions.toResourceException(exception); } } /* * (non-Javadoc) * * @see javax.resource.cci.LocalTransaction#rollback() */ @Override public void rollback() throws ResourceException { try { currentUnitOfWork().afterCompletion(Status.STATUS_ROLLEDBACK); } catch (JDOException exception) { throw ResourceExceptions.toResourceException(exception); } } } // ------------------------------------------------------------------------ // Class InboundInteraction // ------------------------------------------------------------------------ /** * Inbound Interaction */ class InboundInteraction extends AbstractRestInteraction { /** * Constructor * * @param connection * the REST connection */ protected InboundInteraction( RestConnection connection ) { super(connection); } /** * The MOF repository accessor */ protected final Model_1_0 model = Model_1Factory.getModel(); /** * Test the transaction state and id * * @param path * @param existence * * @throws ResourceException */ private void validateTransactionStateAndId( Path path, boolean existence ) throws ResourceException { boolean active = currentUnitOfWork().isActive(); if (active != existence) { throw ResourceExceptions.initHolder( new LocalTransactionException( "Invalid transaction state", BasicException.newEmbeddedExceptionStack( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.ILLEGAL_STATE, new BasicException.Parameter( "expected", existence ? "active" : "not active" ), new BasicException.Parameter( "actual", active ? "active" : "not active" ) ) ) ); } if (path.size() > 2 && existence) { String requestedId = path.getSegment(2).toClassicRepresentation(); String actualId = SharedObjects.getUnitOfWorkIdentifier(getPersistenceManager()); if (!requestedId.equals(actualId)) { throw ResourceExceptions.initHolder( new LocalTransactionException( "Invalid transaction id", BasicException.newEmbeddedExceptionStack( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.BAD_PARAMETER, new BasicException.Parameter( "requested", requestedId ), new BasicException.Parameter("actual", actualId) ) ) ); } } } private Path getTransactionId( Path path ) { String actualId = SharedObjects.getUnitOfWorkIdentifier(getPersistenceManager()); return actualId == null ? null : path.size() == 2 ? path.getChild(actualId) : path.getSegment( 2 ).toClassicRepresentation().equals(actualId) ? path.getPrefix(3) : null; } /** * Convert a RefStruct's type name to a MappedRecord record name * * @param refValue * the RefStruct * * @return to its MappedRecord record name */ private String jcaRecordName( RefStruct refValue ) { if (refValue instanceof RefStruct_1_0) { return ((RefStruct_1_0) refValue).refDelegate().getRecordName(); } else { StringBuilder recordName = new StringBuilder(); for (Object component : refValue.refTypeName()) { recordName.append(':').append(component); } return recordName.substring(1); } } /** * Guarded iteration * * @param type * the result record type * @param source * the JMI collection * * @return the next JCA value * * @throws ServiceException * @throws ResourceException */ @SuppressWarnings("unchecked") private IndexedRecord toJcaValue( Multiplicity type, Collection source ) throws ServiceException, ResourceException { IndexedRecord target = Records.getRecordFactory().createIndexedRecord(type.toString()); for (Iterator i = source.iterator(); i.hasNext();) { try { target.add(toJcaValue(i.next())); } catch (InvalidObjectException exception) { target.add(toJcaValue(exception)); } catch (RuntimeException exception) { throw new ServiceException(exception); } } return target; } /** * Guarded iteration * * @param type * the result record type * @param source * the JMI map * * @return the next JCA value * * @throws ServiceException * @throws ResourceException */ @SuppressWarnings("unchecked") private MappedRecord toJcaValue( Multiplicity type, Map source ) throws ServiceException, ResourceException { MappedRecord target = Records.getRecordFactory().createMappedRecord(type.code()); for (Iterator i = source.keySet().iterator(); i.hasNext();) { try { Object key = i.next(); try { target.put(key, toJcaValue(source.get(key))); } catch (InvalidObjectException exception) { target.put(key, toJcaValue(exception)); } } catch (RuntimeException exception) { throw new ServiceException(exception); } } return target; } /** * Guarded iteration * * @param type * the result record type * @param source * the JMI structure * * @return the next JCA value * * @throws ServiceException * @throws ResourceException */ @SuppressWarnings("unchecked") private MappedRecord toJcaValue( String type, RefStruct source ) throws ServiceException, ResourceException { MappedRecord target = Records.getRecordFactory().createMappedRecord(type); for (Iterator i = source.refFieldNames().iterator(); i.hasNext();) { try { String fieldName = (String) i.next(); try { target.put(fieldName, toJcaValue(source.refGetValue(fieldName))); } catch (InvalidObjectException exception) { target.put(fieldName, toJcaValue(exception)); } } catch (RuntimeException exception) { throw new ServiceException(exception); } } return target; } /** * Retrieve an invalid object's id * * @param source * the invalid object exception * * @return the invalid object's id */ private Path toJcaValue( InvalidObjectException source ) { return new Path(source.getElementInError().refMofId()); } /** * Guarded feature retrieval * * @param source * @param feature * * @return the requested feature * * @throws ServiceException */ private Object getJcaValue( RefObject source, ModelElement_1_0 featureDef ) throws ServiceException, ResourceException { try { Model_1_0 model = featureDef.getModel(); String featureName = featureDef.getName(); if (featureDef.isReferenceType() && model.referenceIsStoredAsAttribute(featureDef) && !ModelHelper.isDerived(featureDef)) { return this.toJcaValue(PersistenceHelper.getFeatureReplacingObjectById(source, featureName)); } else { return this.toJcaValue(source.refGetValue(featureName)); } } catch (InvalidObjectException exception) { return this.toJcaValue(exception); } catch (RuntimeException exception) { throw new ServiceException(exception); } } /** * Convert a RefObject value to a MappedRecord value * * @param refValue * the RefObject value * * @return its MappedRecord value representation * * @throws ResourceException * @throws ServiceException */ private Object toJcaValue( Object refValue ) throws ResourceException, ServiceException { if (refValue instanceof RefObject) { return getResourceIdentifier(refValue); } else if (refValue instanceof Set) { return this.toJcaValue(Multiplicity.SET, (Set) refValue); } else if (refValue instanceof List) { return this.toJcaValue(Multiplicity.LIST, (List) refValue); } else if (refValue instanceof SparseArray) { return this.toJcaValue(Multiplicity.SPARSEARRAY, (SparseArray) refValue); } else if (refValue instanceof RefStruct) { RefStruct refStruct = (RefStruct) refValue; return this.toJcaValue(jcaRecordName(refStruct), refStruct); } else { return refValue; } } /** * Convert a MappedRecord value to a RefObject value * * @param jcaValue * the JCA value * @param featureDef * * @return the JMI value * * @throws ResourceException */ private Object toRefValue( Object jcaValue, ModelElement_1_0 featureDef ) throws ResourceException { try { ModelElement_1_0 featureType = this.model.getDereferencedType(featureDef.getType()); if (ModelHelper.getMultiplicity(featureDef) == Multiplicity.STREAM) { return jcaValue instanceof char[] ? CharacterLargeObjects.valueOf((char[]) jcaValue) : jcaValue instanceof byte[] ? BinaryLargeObjects.valueOf((byte[]) jcaValue) : jcaValue instanceof Reader ? CharacterLargeObjects.valueOf( (Reader) jcaValue ) : jcaValue instanceof InputStream ? BinaryLargeObjects.valueOf( (InputStream) jcaValue ) : jcaValue; } else if (jcaValue instanceof String && PrimitiveTypes.DATETIME.equals(featureType.getQualifiedName())) { return Datatypes.create(java.util.Date.class, (String) jcaValue); } else if (jcaValue instanceof String && PrimitiveTypes.DATE.equals(featureType.getQualifiedName())) { return Datatypes.create(XMLGregorianCalendar.class, (String) jcaValue); } else if (jcaValue instanceof String && PrimitiveTypes.DURATION.equals(featureType.getQualifiedName())) { return Datatypes.create(Duration.class, (String) jcaValue); } else { return featureDef.getModel().isReferenceType(featureDef) ? getObjectByResourceIdentifier(jcaValue) : jcaValue; } } catch (ServiceException exception) { throw ResourceExceptions.toResourceException(exception); } } /** * Convert a RefObject to a MappedRecord * * @param object * the RefObject * @param requestedFeatures, * the requested features, maybe null * @param fetchGroups, * the requested getch groups maybe null * * @return its MappedRecord representation * * @throws ResourceException */ @SuppressWarnings("unchecked") private MappedRecord toJcaRecord( RefObject object, Set requestedFeatures, Set fetchGroups ) throws ResourceException { try { RefObject_1_0 refObject = (RefObject_1_0) object; ObjectRecord reply = newObject(getResourceIdentifier(object)); reply.setVersion((byte[]) JDOHelper.getVersion(refObject)); MappedRecord jcaValue = Records.getRecordFactory().createMappedRecord(refObject.refClass().refMofId()); reply.setValue(jcaValue); Map features = this.model.getAttributeDefs( this.model.getElement(refObject.refClass().refMofId()), false, true ); if (requestedFeatures != null && !requestedFeatures.isEmpty() && !features.keySet().containsAll(requestedFeatures)) { throw new ServiceException( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.ASSERTION_FAILURE, "requested features not acceptable", new BasicException.Parameter("object", refObject), new BasicException.Parameter( "requested", requestedFeatures ), new BasicException.Parameter( "acceptable", features.keySet() ) ); } boolean fetchAll = JDOHelper.isPersistent(refObject) && !JDOHelper.isNew(refObject) && (fetchGroups == null || fetchGroups.contains(FetchGroup.ALL)); for (ModelElement_1_0 feature : features.values()) { final String featureName = feature.getName(); if (fetchAll || isRquestedFeature(requestedFeatures, featureName) || isLoadedFeature(refObject, featureName)) { try { jcaValue.put(featureName, this.getJcaValue(refObject, feature)); } catch (RuntimeException exception) { new ServiceException( exception, BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.TRANSFORMATION_FAILURE, "Unable to retrieve feature value", new BasicException.Parameter(BasicException.Parameter.XRI, refObject.refMofId()), new BasicException.Parameter("feature", featureName) ).log(); } } } return reply; } catch (ServiceException exception) { throw ResourceExceptions.toResourceException(exception); } } private boolean isLoadedFeature( RefObject_1_0 refObject, final String featureName ) { return ((RefPackage_1_0) refObject.refOutermostPackage()).refPersistenceManager().isLoaded( (UUID) JDOHelper.getTransactionalObjectId(refObject), featureName ); } private boolean isRquestedFeature( Set requestedFeatures, final String featureName ) { return (requestedFeatures != null && requestedFeatures.contains(featureName)) || SystemAttributes.OBJECT_IDENTITY.equals(featureName); } /** * Create a query object. *

    *
  • TODO take explicitly requested features into consideration *
  • TODO take extensions into consideration *
* * @param input * * @return a new query object * * @throws ResourceException */ private Query toRefQuery( QueryRecord input ) throws ResourceException { Query query = getPersistenceManager().newQuery(Queries.QUERY_LANGUAGE, input); // // Fetch Plan // query.getFetchPlan().setGroups(toFetchGroups(input)); // // Fetch Size // Long fetchSize = input.getSize(); if (fetchSize != null) { query.getFetchPlan().setFetchSize(fetchSize.intValue()); } // // TODO Extension // // for(Map.Entry extension : input.getExtensions().entrySet()) { // query.addExtension(extension.getKey(), extension.getValue()); // } // return query; } private Set toFetchGroups(QueryRecord input) { final String fetchGroupName = input.getFetchGroupName(); return fetchGroupName == null ? Collections.emptySet() : Collections.singleton(fetchGroupName); } @SuppressWarnings("unchecked") private void toRefObject( UUID transactionalObjectId, Path objectId, RefObject refTarget, MappedRecord jcaSource ) throws ResourceException { try { ModelElement_1_0 classDef = this.model.getElement(refTarget.refClass().refMofId()); for (Object rawObjectEntry : jcaSource.entrySet()) { Map.Entry objectEntry = (Entry) rawObjectEntry; String featureName = objectEntry.getKey().toString(); Object rawValue = objectEntry.getValue(); ModelElement_1_0 featureDef = this.model.getFeatureDef(classDef, featureName, false); if (featureDef == null) { throw new ServiceException( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.BAD_MEMBER_NAME, "Unknown feature", new BasicException.Parameter( BasicException.Parameter.XRI, objectId ), new BasicException.Parameter("uuid", transactionalObjectId), new BasicException.Parameter( "class", refTarget.refClass().refMofId() ), new BasicException.Parameter("feature", featureName) ); } featureName = featureDef.getName(); final Boolean isChangeable = featureDef.isChangeable(); final Boolean isDerived = featureDef.isDerived(); if (Boolean.TRUE.equals(isChangeable) && !Boolean.TRUE.equals(isDerived)) { switch (ModelHelper.getMultiplicity(featureDef)) { case LIST: case SET: { @SuppressWarnings("rawtypes") Collection target = (Collection) refTarget.refGetValue(featureName); target.clear(); Collection source = rawValue == null ? Collections.EMPTY_LIST : rawValue instanceof List ? (List) rawValue : Collections.singletonList(rawValue); for (Object v : source) { target.add(this.toRefValue(v, featureDef)); } } break; case SPARSEARRAY: { @SuppressWarnings("rawtypes") SparseArray target = (SparseArray) refTarget.refGetValue(featureName); target.clear(); if (rawValue != null) { if (rawValue instanceof MappedRecord) { Map source = (MappedRecord) rawValue; for (Map.Entry e : source.entrySet()) { target.put(e.getKey(), this.toRefValue(e.getValue(), featureDef)); } } else if (rawValue instanceof SparseArray) { SparseArray source = (SparseArray) rawValue; for (ListIterator i = source.populationIterator(); i.hasNext();) { target.put(Integer.valueOf(i.nextIndex()), this.toRefValue(i.next(), featureDef)); } } else { target.put(Integer.valueOf(0), this.toRefValue(rawValue, featureDef)); } } } break; default: { refTarget.refSetValue(featureName, this.toRefValue(rawValue, featureDef)); } } } } } catch (ServiceException exception) { throw ResourceExceptions.toResourceException(exception); } } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#execute(javax.resource.cci.InteractionSpec, javax.resource.cci.Record, * javax.resource.cci.Record) */ @Override public boolean execute( InteractionSpec ispec, Record input, Record output ) throws ResourceException { try { return super.execute(ispec, input, output); } catch (JDOException exception) { throw ResourceExceptions.toResourceException(exception); } catch (JmiException exception) { throw ResourceExceptions.toResourceException(exception); } } /** * Propagate the RefObject to indexed IndexedRecord * * @param refObject * @param output * @param requestedFeatures the requested features, may be null * * @param fetchGroups * the requested fetch groups, may be null * * @return true * * @throws ResourceException */ @SuppressWarnings("unchecked") private boolean propagate( RefObject refObject, IndexedRecord output, Set requestedFeatures, Set fetchGroups ) throws ResourceException { if (output != null) output.add(this.toJcaRecord(refObject, requestedFeatures, fetchGroups)); return true; } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#get(org.openmdx.base.resource.spi.RestInteractionSpec, * org.openmdx.base.rest.spi.Query_2Facade, javax.resource.cci.IndexedRecord) */ @SuppressWarnings("unchecked") @Override public boolean get( RestInteractionSpec ispec, QueryRecord input, ResultRecord output ) throws ResourceException { Path xri = input.getResourceIdentifier(); if (isTransactionObjectIdentifier(xri)) { Path transactionId = getTransactionId(xri); if (transactionId != null) { if (output == null) { return false; } else { output.add(Object_2Facade.newInstance(transactionId, "org:openmdx:kernel:UnitOfWork").getDelegate()); return true; } } else { return false; } } else { RefObject refObject = getObjectByResourceIdentifier(xri); if (input.isRefresh()) { getPersistenceManager().refresh(refObject); } if (output == null) { return true; } else { Set features = input.getFeatureName(); final QueryFilterRecord queryFilter = input.getQueryFilter(); if (queryFilter != null) { features = features == null ? new HashSet() : new HashSet(features); for (FeatureOrderRecord orderSpecifier : queryFilter.getOrderSpecifier()) { features.add(orderSpecifier.featureName()); } } return propagate(refObject, output, features, toFetchGroups(input)); } } } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#create(org.openmdx.base.resource.spi.RestInteractionSpec, * org.openmdx.base.rest.spi.ObjectHolder_2Facade, javax.resource.cci.IndexedRecord) */ @SuppressWarnings("unchecked") @Override public boolean create( RestInteractionSpec ispec, ObjectRecord input, ResultRecord output ) throws ResourceException { Path xri = input.getResourceIdentifier(); if (isTransactionObjectIdentifier(xri)) { validateTransactionStateAndId(xri, false); localTransaction.begin(); if (output != null) { output.add(Object_2Facade.newInstance(getTransactionId(xri), "org:openmdx:kernel:UnitOfWork").getDelegate()); } return true; } else if (xri.isTransactionalObjectId()) { RefPackage refPackage = getObjectByResourceIdentifier(BASE_AUTHORITY).refOutermostPackage(); RefObject_1_0 newObject = (RefObject_1_0) refPackage.refClass( input.getValue().getRecordName() ).refCreateInstance(Collections.singletonList(xri)); this.toRefObject(input.getTransientObjectId(), xri, newObject, input.getValue()); return propagate(newObject, output, null, null); } else { boolean newId = xri.size() % 2 == 0; int featurePosition = xri.size() - (newId ? 1 : 2); RefObject refParent = getObjectByResourceIdentifier(xri.getPrefix(featurePosition)); RefObject_1_0 refObject = (RefObject_1_0) refParent.refOutermostPackage().refClass( input.getValue().getRecordName() ).refCreateInstance(null); this.toRefObject(input.getTransientObjectId(), xri, refObject, input.getValue()); Object container = refParent.refGetValue(xri.getSegment(featurePosition).toClassicRepresentation()); if (newId) { @SuppressWarnings("rawtypes") Collection refContainer = (Collection) container; refContainer.add(refObject); } else { RefContainer refContainer = (RefContainer) container; refContainer.refAdd(toAddArguments(refContainer.getClass(), xri.getLastSegment().toClassicRepresentation(), refObject)); } return propagate(refObject, output, null, null); } } /** * Provide the add() argument list * * @param containerClass * @param qualifier * @param object * * @return the add() argument list * @throws ServiceException */ @SuppressWarnings("rawtypes") private Object[] toAddArguments( Class containerClass, String qualifier, RefObject object ) throws ResourceException { final Class[] argumentClasses = ReferenceDef.getAddArguments(containerClass); if (argumentClasses.length == 3) { boolean persistent = qualifier.startsWith("!"); return new Object[] { QualifierType.valueOf( persistent ), Datatypes.create(argumentClasses[1], persistent ? qualifier.substring(1) : qualifier), object }; } else { throw ResourceExceptions.initHolder( new NotSupportedException( "More than one qualifier is not yet supported", BasicException.newEmbeddedExceptionStack( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.NOT_IMPLEMENTED, new BasicException.Parameter( "argumentClasses", (Object[]) argumentClasses ) ) ) ); } } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#move(org.openmdx.base.resource.spi.RestInteractionSpec, * org.openmdx.base.naming.Path, org.openmdx.base.rest.spi.Object_2Facade, javax.resource.cci.IndexedRecord) */ @Override public boolean move( RestInteractionSpec ispec, ObjectRecord input, ResultRecord output ) throws ResourceException { RefObject_1_0 newObject = (RefObject_1_0) getObjectByResourceIdentifier(input.getTransientObjectId()); this.toRefObject(input.getTransientObjectId(), input.getResourceIdentifier(), newObject, input.getValue()); Path newResourceIdentifier = input.getResourceIdentifier(); int featurePosition = newResourceIdentifier.size() - 2; RefObject refObject = getObjectByResourceIdentifier(newResourceIdentifier.getPrefix(featurePosition)); RefContainer refContainer = (RefContainer) refObject.refGetValue( newResourceIdentifier.getSegment(featurePosition).toClassicRepresentation() ); String qualifier = newResourceIdentifier.getLastSegment().toClassicRepresentation(); boolean persistent = qualifier.startsWith("!"); refContainer.refAdd(QualifierType.valueOf(persistent), persistent ? qualifier.substring(1) : qualifier, newObject); return propagate(newObject, output, null, null); } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#delete(org.openmdx.base.resource.spi.RestInteractionSpec, * org.openmdx.base.rest.spi.ObjectHolder_2Facade, javax.resource.cci.IndexedRecord) */ @Override public boolean delete( RestInteractionSpec ispec, ObjectRecord input ) throws ResourceException { Path xri = input.getResourceIdentifier(); if (isTransactionObjectIdentifier(xri)) { validateTransactionStateAndId(xri, true); localTransaction.rollback(); } else { getObjectByResourceIdentifier(xri).refDelete(); } return true; } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#put(org.openmdx.base.resource.spi.RestInteractionSpec, * org.openmdx.base.rest.spi.ObjectHolder_2Facade, javax.resource.cci.IndexedRecord) */ @Override public boolean update( RestInteractionSpec ispec, ObjectRecord input, ResultRecord output ) throws ResourceException { final Path xri = input.getResourceIdentifier(); final UUID transientObjectId = input.getTransientObjectId(); RefObject refObject = getObjectByResourceIdentifier(transientObjectId == null ? xri : transientObjectId); this.toRefObject(transientObjectId, xri, refObject, input.getValue()); return propagate(refObject, output, null, null); } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#find(org.openmdx.base.resource.spi.RestInteractionSpec, * org.openmdx.base.rest.spi.Query_2Facade, javax.resource.cci.IndexedRecord) */ @SuppressWarnings("unchecked") @Override public boolean find( RestInteractionSpec ispec, QueryRecord input, ResultRecord output ) throws ResourceException { Query query = this.toRefQuery(input); List objects = (List) query.execute(); if (output != null) { final Long size = input.getSize(); final int batchSize = getBatchSize(size); final int position = Numbers.getValue(input.getPosition(), 0); if (position >= 0) { ListIterator i = objects.listIterator(position); int count = 0; while (i.hasNext() && count < batchSize) { output.add(this.toJcaRecord(i.next(), input.getFeatureName(), Collections.singleton(input.getFetchGroupName()))); count++; } boolean hasMore = i.hasNext(); output.setHasMore(hasMore); if (!hasMore) { output.setTotal(position + count); } } else { ListIterator i = objects.listIterator(-position); for (int count = 0; i.hasPrevious() && count < batchSize; count++) { output.add( 0, this.toJcaRecord(i.previous(), input.getFeatureName(), Collections.singleton(input.getFetchGroupName())) ); } } } return true; } private int getBatchSize( final Long size ) { final int fetchSize = Numbers.getValue(size, FetchPlan.FETCH_SIZE_OPTIMAL); return fetchSize == FetchPlan.FETCH_SIZE_GREEDY ? BATCH_SIZE_LIMIT : fetchSize == FetchPlan.FETCH_SIZE_OPTIMAL ? OPTIMAL_FETCH_SIZE : fetchSize; } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#delete(org.openmdx.base.resource.spi.RestInteractionSpec, * org.openmdx.base.rest.spi.Query_2Facade, javax.resource.cci.IndexedRecord) */ @Override public boolean delete( RestInteractionSpec ispec, QueryRecord input ) throws ResourceException { Path xri = input.getResourceIdentifier(); if (xri.size() % 2 == 0 || xri.isPattern()) { try { Query query = this.toRefQuery(input); return query.deletePersistentAll() > 0; } catch (JDOException exception) { throw ResourceExceptions.toResourceException(exception); } } else if (isTransactionObjectIdentifier(xri)) { validateTransactionStateAndId(xri, true); localTransaction.rollback(); return true; } else { try { getObjectByResourceIdentifier(xri).refDelete(); return true; } catch (JDOException exception) { // // Retrieval Failure // return false; } catch (JmiException exception) { // // Removal Failure // throw ResourceExceptions.toResourceException(exception); } } } /* * (non-Javadoc) * * @see org.openmdx.base.rest.spi.AbstractFacadeInteraction#invoke(org.openmdx.base.resource.spi.RestInteractionSpec, * javax.resource.cci.MessageRecord, javax.resource.cci.MessageRecord) */ @Override public boolean invoke( RestInteractionSpec ispec, MessageRecord input, MessageRecord output ) throws ResourceException { try { Path xri = input.getResourceIdentifier(); if (isTransactionCommitIdentifier(xri)) { validateTransactionStateAndId(xri, true); localTransaction.commit(); if (output != null) { output.setResourceIdentifier(newResponseId(xri)); output.setBody(null); } } else { final int featurePosition = xri.size() - (xri.isObjectPath() ? 2 : 1); RefObject refObject = getObjectByResourceIdentifier(xri.getPrefix(featurePosition)); RefPackage_1_0 refPackage = (RefPackage_1_0) refObject.refOutermostPackage(); MappedRecord arguments = input.getBody(); Object reply = refObject.refInvokeOperation( xri.getSegment( featurePosition ).toClassicRepresentation(), Collections.singletonList(refPackage.refCreateStruct(arguments)) ); if (output != null) { output.setResourceIdentifier(xri); output.setBody( reply instanceof RefStruct_1_0 ? (MappedRecord) ((RefStruct_1_0) reply).refDelegate() : (MappedRecord) reply ); } } return true; } catch (RefException exception) { throw ResourceExceptions.toResourceException(exception); } } } }