/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.isis.runtimes.dflt.remoting.protocol.internal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.log4j.Logger;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.commons.ensure.Assert;
import org.apache.isis.core.commons.exceptions.UnknownTypeException;
import org.apache.isis.core.commons.factory.InstanceUtil;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.adapter.ResolveState;
import org.apache.isis.core.metamodel.adapter.oid.Oid;
import org.apache.isis.core.metamodel.adapter.version.Version;
import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacetUtils;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
import org.apache.isis.runtimes.dflt.remoting.common.IsisRemoteException;
import org.apache.isis.runtimes.dflt.remoting.common.client.facets.ActionInvocationFacetWrapProxy;
import org.apache.isis.runtimes.dflt.remoting.common.client.facets.PropertySetterFacetWrapProxy;
import org.apache.isis.runtimes.dflt.remoting.common.client.persistence.ClientSideTransactionManager;
import org.apache.isis.runtimes.dflt.remoting.common.client.persistence.PersistenceSessionProxy;
import org.apache.isis.runtimes.dflt.remoting.common.data.Data;
import org.apache.isis.runtimes.dflt.remoting.common.data.DataFactory;
import org.apache.isis.runtimes.dflt.remoting.common.data.DataFactoryDefault;
import org.apache.isis.runtimes.dflt.remoting.common.data.common.CollectionData;
import org.apache.isis.runtimes.dflt.remoting.common.data.common.EncodableObjectData;
import org.apache.isis.runtimes.dflt.remoting.common.data.common.IdentityData;
import org.apache.isis.runtimes.dflt.remoting.common.data.common.NullData;
import org.apache.isis.runtimes.dflt.remoting.common.data.common.ObjectData;
import org.apache.isis.runtimes.dflt.remoting.common.data.common.ReferenceData;
import org.apache.isis.runtimes.dflt.remoting.common.data.query.PersistenceQueryData;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.AuthorizationResponse;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.ClearAssociationRequest;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.ExecuteClientActionRequest;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.ExecuteClientActionResponse;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.ExecuteServerActionRequest;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.ExecuteServerActionResponse;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.FindInstancesRequest;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.GetObjectRequest;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.KnownObjectsRequest;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.ResolveFieldRequest;
import org.apache.isis.runtimes.dflt.remoting.common.exchange.ResolveObjectRequest;
import org.apache.isis.runtimes.dflt.remoting.common.facade.impl.ServerFacadeImpl;
import org.apache.isis.runtimes.dflt.remoting.common.protocol.ObjectEncoderDecoder;
import org.apache.isis.runtimes.dflt.remoting.common.protocol.PersistenceQueryEncoder;
import org.apache.isis.runtimes.dflt.remoting.common.protocol.ProtocolConstants;
import org.apache.isis.runtimes.dflt.runtime.persistence.PersistorUtil;
import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceQuery;
public class ObjectEncoderDecoderDefault implements ObjectEncoderDecoder {
private final static Logger LOG = Logger.getLogger(ObjectEncoderDecoderDefault.class);
public static final int DEFAULT_CLIENT_SIDE_ADD_OBJECT_GRAPH_DEPTH = 1;
public static final int DEFAULT_CLIENT_SIDE_UPDATE_OBJECT_GRAPH_DEPTH = 1;
public static final int DEFAULT_CLIENT_SIDE_ACTION_TARGET_GRAPH_DEPTH = 0;
public static final int DEFAULT_CLIENT_SIDE_ACTION_PARAMETER_GRAPH_DEPTH = 0;
public static final int DEFAULT_SERVER_SIDE_RETRIEVED_OBJECT_GRAPH_DEPTH = 100;
public static final int DEFAULT_SERVER_SIDE_TOUCHED_OBJECT_GRAPH_DEPTH = 1;
private final ObjectSerializer serializer;
private final ObjectDeserializer deserializer;
private final FieldOrderCache fieldOrderCache;
private final DataFactory dataFactory;
private final Map<Class<?>, PersistenceQueryEncoder> persistenceEncoderByClass =
new HashMap<Class<?>, PersistenceQueryEncoder>();
private final int clientSideAddGraphDepth = DEFAULT_CLIENT_SIDE_ADD_OBJECT_GRAPH_DEPTH;
private final int clientSideUpdateGraphDepth = DEFAULT_CLIENT_SIDE_UPDATE_OBJECT_GRAPH_DEPTH;
private final int clientSideActionTargetRemotelyGraphDepth = DEFAULT_CLIENT_SIDE_ACTION_TARGET_GRAPH_DEPTH;
private final int clientSideActionParameterGraphDepth = DEFAULT_CLIENT_SIDE_ACTION_PARAMETER_GRAPH_DEPTH;
private final int serverSideTouchedObjectGraphDepth = DEFAULT_SERVER_SIDE_TOUCHED_OBJECT_GRAPH_DEPTH;
private final int serverSideRetrievedObjectGraphDepth = DEFAULT_SERVER_SIDE_RETRIEVED_OBJECT_GRAPH_DEPTH;
/**
* Factory method.
*/
public static ObjectEncoderDecoderDefault create(final IsisConfiguration configuration) {
final ObjectEncoderDecoderDefault encoderDecoder = new ObjectEncoderDecoderDefault();
addPersistenceEncoders(configuration, encoderDecoder, ProtocolConstants.ENCODER_CLASS_NAME_LIST);
addPersistenceEncoders(configuration, encoderDecoder, ProtocolConstants.ENCODER_CLASS_NAME_LIST_DEPRECATED);
return encoderDecoder;
}
private static void addPersistenceEncoders(final IsisConfiguration configuration,
final ObjectEncoderDecoderDefault encoder, final String encoderClassNameList) {
final String[] encoders = configuration.getList(encoderClassNameList);
for (final String encoder2 : encoders) {
final PersistenceQueryEncoder encoding =
InstanceUtil.createInstance(encoder2, PersistenceQueryEncoder.class);
encoder.addPersistenceQueryEncoder(encoding);
}
}
/**
* Package-level visibility (for tests to use only)
*/
public ObjectEncoderDecoderDefault() {
this.fieldOrderCache = new FieldOrderCache();
this.dataFactory = new DataFactoryDefault();
this.serializer = new ObjectSerializer(dataFactory, fieldOrderCache);
this.deserializer = new ObjectDeserializer(fieldOrderCache);
addPersistenceQueryEncoder(new PersistenceQueryFindAllInstancesEncoder());
addPersistenceQueryEncoder(new PersistenceQueryFindByTitleEncoder());
addPersistenceQueryEncoder(new PersistenceQueryFindByPatternEncoder());
addPersistenceQueryEncoder(new PersistenceQueryFindUsingApplibQueryDefaultEncoder());
addPersistenceQueryEncoder(new PersistenceQueryFindUsingApplibQuerySerializableEncoder());
// TODO: look up overrides of depths from Configuration.
}
public void addPersistenceQueryEncoder(final PersistenceQueryEncoder encoder) {
encoder.setObjectEncoder(this);
persistenceEncoderByClass.put(encoder.getPersistenceQueryClass(), encoder);
}
// /////////////////////////////////////////////////////////
// called both client- and server-side only
// /////////////////////////////////////////////////////////
/**
* Creates a ReferenceData that contains the type, version and OID for the specified object. This can only be used
* for persistent objects.
*
* <p>
* Called both client and server-side, in multiple locations.
*/
@Override
public final IdentityData encodeIdentityData(final ObjectAdapter object) {
Assert.assertNotNull("OID needed for reference", object, object.getOid());
return dataFactory.createIdentityData(object.getSpecification().getFullIdentifier(), object.getOid(),
object.getVersion());
}
// /////////////////////////////////////////////////////////
// client-side encoding
// /////////////////////////////////////////////////////////
/**
* Called client-side only:
* <ul>
* <li>by {@link ClientSideTransactionManager#endTransaction()}
* </ul>
*/
@Override
public ObjectData encodeMakePersistentGraph(final ObjectAdapter adapter, final KnownObjectsRequest knownObjects) {
Assert.assertTrue("transient", adapter.isTransient());
return (ObjectData) encode(adapter, clientSideAddGraphDepth, knownObjects);
}
/**
* Called client-side only:
* <ul>
* <li>by {@link ClientSideTransactionManager#endTransaction()}
* </ul>
*/
@Override
public ObjectData encodeGraphForChangedObject(final ObjectAdapter object, final KnownObjectsRequest knownObjects) {
return (ObjectData) encode(object, clientSideUpdateGraphDepth, knownObjects);
}
/**
* Called client-side only:
* <ul>
* <li>by {@link PropertySetterFacetWrapProxy#setProperty(ObjectAdapter, ObjectAdapter)}
* </ul>
*/
@Override
public EncodableObjectData encodeAsValue(final ObjectAdapter value) {
return serializer.serializeEncodeable(value);
}
/**
* Called client-side only:
* <ul>
* <li>by {@link ActionInvocationFacetWrapProxy#invoke(ObjectAdapter, ObjectAdapter[])} (calling remotely)
* </ul>
*/
@Override
public ReferenceData encodeActionTarget(final ObjectAdapter target, final KnownObjectsRequest knownObjects) {
return serializer.serializeAdapter(target, clientSideActionTargetRemotelyGraphDepth, knownObjects);
}
/**
* Called client-side only:
* <ul>
* <li>by {@link ActionInvocationFacetWrapProxy#invoke(ObjectAdapter, ObjectAdapter[])}
* <li>by {@link PersistenceQueryFindByPatternEncoder#encode(PersistenceQuery)}
* <li>by hibernate's equivalent encoder
* </ul>
*/
@Override
public final Data[] encodeActionParameters(final ObjectSpecification[] parameterTypes,
final ObjectAdapter[] parameters, final KnownObjectsRequest knownObjects) {
final Data parameterData[] = new Data[parameters.length];
for (int i = 0; i < parameters.length; i++) {
final ObjectAdapter parameter = parameters[i];
final String type = parameterTypes[i].getFullIdentifier();
parameterData[i] = createParameter(type, parameter, knownObjects);
}
return parameterData;
}
/**
* Called client-side only:
* <ul>
* <li>by {@link PersistenceSessionProxy#findInstances(PersistenceQuery)}
* </ul>
*/
@Override
public PersistenceQueryData encodePersistenceQuery(final PersistenceQuery criteria) {
final PersistenceQueryEncoder strategy = findPersistenceQueryEncoder(criteria.getClass());
return strategy.encode(criteria);
}
// /////////////////////////////////////////////////////////
// client-side decoding
// /////////////////////////////////////////////////////////
/**
* Called client-side only:
* <ul>
* <li>by {@link ActionInvocationFacetWrapProxy#invoke(ObjectAdapter, ObjectAdapter[])}
* </ul>
*/
@Override
public void madePersistent(final ObjectAdapter target, final ObjectData persistedTarget) {
deserializer.madePersistent(target, persistedTarget);
}
/**
* Called client-side only, in multiple locations.
*/
@Override
public ObjectAdapter decode(final Data data) {
return deserializer.deserialize(data);
}
@Override
public void decode(final ObjectData[] dataArray) {
for (final ObjectData element : dataArray) {
if (LOG.isDebugEnabled()) {
LOG.debug("update " + element.getOid());
}
this.decode(element);
}
}
// /////////////////////////////////////////////////////////
// server-side decoding
// /////////////////////////////////////////////////////////
/**
* Called server-side only:
* <ul>
* <li>by {@link ServerFacadeImpl#executeClientAction(ExecuteClientActionRequest)}
* <li>by {@link ServerFacadeImpl#executeServerAction(ExecuteServerActionRequest) </ul>
*/
@Override
public ObjectAdapter decode(final Data data, final KnownObjectsRequest knownObjects) {
return deserializer.deserialize(data, knownObjects);
}
/**
* Called server-side only:
* <ul>
* <li>by {@link ServerFacadeImpl#findInstances(FindInstancesRequest) </ul>
*/
@Override
public PersistenceQuery decodePersistenceQuery(final PersistenceQueryData persistenceQueryData) {
final Class<?> criteriaClass = persistenceQueryData.getPersistenceQueryClass();
final PersistenceQueryEncoder encoderDecoder = findPersistenceQueryEncoder(criteriaClass);
return encoderDecoder.decode(persistenceQueryData);
}
private PersistenceQueryEncoder findPersistenceQueryEncoder(final Class<?> persistenceQueryClass) {
final PersistenceQueryEncoder encoder = persistenceEncoderByClass.get(persistenceQueryClass);
if (encoder == null) {
throw new IsisRemoteException("No encoder for " + persistenceQueryClass.getName());
}
return encoder;
}
// /////////////////////////////////////////////////////////
// server-side encoding
// /////////////////////////////////////////////////////////
@Override
public AuthorizationResponse encodeAuthorizeResponse(final boolean authorized) {
return new AuthorizationResponse(authorized);
}
/**
* Called server-side only:
* <ul>
* <li>by {@link ServerFacadeImpl#executeClientAction(ExecuteClientActionRequest)}
* </ul>
*/
@Override
public ExecuteClientActionResponse encodeClientActionResult(final ReferenceData[] madePersistent,
final Version[] changedVersion, final ObjectData[] updates) {
return new ExecuteClientActionResponse(madePersistent, changedVersion, updates);
}
/**
* Encodes a complete set of data for the specified object.
*
* <p>
* Called server-side only, in several locations:
* <ul>
* <li>by {@link ServerFacadeImpl#findInstances(FindInstancesRequest)}
* <li>by {@link ServerFacadeImpl#executeServerAction(ExecuteServerActionRequest)}
* <li>by {@link ServerFacadeImpl#resolveImmediately(ResolveObjectRequest)}
* </ul>
*/
@Override
public final ObjectData encodeCompletePersistentGraph(final ObjectAdapter object) {
return encode(object, serverSideRetrievedObjectGraphDepth);
}
/**
* Encodes a minimal set of data for the specified object.
*
* <p>
* Called server-side only:
* <ul>
* <li>by {@link ServerFacadeImpl#getObject(GetObjectRequest)}
* <li>by {@link ServerFacadeImpl#clearAssociation(ClearAssociationRequest) <li><li> by<li> {
* @link ServerFacadeImpl#clearValue(ClearValueRequest) <li><li> by<li> {
* @link ServerFacadeImpl#executeClientAction(ExecuteClientActionRequest) <li><li> by<li> {
* @link ServerFacadeImpl#executeServerAction(ExecuteServerActionRequest) <li><li> by<li> {
* @link ServerFacadeImpl#setAssociation(SetAssociationRequest) <li><li> by<li> {
* @link ServerFacadeImpl#setValue(SetValueRequest) </ul>
*/
@Override
public ObjectData encodeForUpdate(final ObjectAdapter object) {
final ResolveState resolveState = object.getResolveState();
if (resolveState.isSerializing() || resolveState.isGhost()) {
throw new IsisRemoteException("Illegal resolve state: " + object);
}
return encode(object, serverSideTouchedObjectGraphDepth);
}
/**
* Called server-side only:
* <ul>
* <li>by {@link ServerFacadeImpl#resolveField(ResolveFieldRequest)}
* </ul>
*/
@Override
public Data encodeForResolveField(final ObjectAdapter adapter, final String fieldName) {
final Oid oid = adapter.getOid();
final ObjectSpecification specification = adapter.getSpecification();
final String type = specification.getFullIdentifier();
final ResolveState resolveState = adapter.getResolveState();
Data[] fieldContent;
final ObjectAssociation[] fields = getFieldOrder(specification);
fieldContent = new Data[fields.length];
PersistorUtil.start(adapter, adapter.getResolveState().serializeFrom());
final KnownObjectsRequest knownObjects = new KnownObjectsRequest();
for (int i = 0; i < fields.length; i++) {
if (fields[i].getId().equals(fieldName)) {
final ObjectAdapter field = fields[i].get(adapter);
if (field == null) {
fieldContent[i] = dataFactory.createNullData(fields[i].getSpecification().getFullIdentifier());
} else if (fields[i].getSpecification().isEncodeable()) {
fieldContent[i] = serializer.serializeEncodeable(field);
} else if (fields[i].isOneToManyAssociation()) {
fieldContent[i] =
serializer.serializeCollection(field, serverSideRetrievedObjectGraphDepth, knownObjects);
} else {
IsisContext.getPersistenceSession().resolveImmediately(field);
fieldContent[i] =
serializer.serializeAdapter(field, serverSideRetrievedObjectGraphDepth, knownObjects);
}
break;
}
}
PersistorUtil.end(adapter);
// TODO remove the fudge - needed as collections are part of parents, hence parent object gets set as
// resolving (is not a ghost) yet it has no version number
// return createObjectData(oid, type, fieldContent, resolveState.isResolved(),
// !resolveState.isGhost(), object.getVersion());
final ObjectData data =
dataFactory.createObjectData(type, oid, resolveState.isResolved(), adapter.getVersion());
data.setFieldContent(fieldContent);
return data;
// return createObjectData(oid, type, fieldContent, resolveState.isResolved(), object.getVersion());
}
/**
* Called server-side only:
* <ul>
* <li>by {@link ServerFacadeImpl#executeServerAction(ExecuteServerActionRequest)}
* </ul>
*/
@Override
public ObjectData encodeMadePersistentGraph(final ObjectData data, final ObjectAdapter object) {
final Oid objectsOid = object.getOid();
Assert.assertNotNull(objectsOid);
if (objectsOid.hasPrevious()) {
final Version version = object.getVersion();
final String type = data.getType();
final ObjectData persistedData = dataFactory.createObjectData(type, objectsOid, true, version);
final Data[] allContents = data.getFieldContent();
if (allContents != null) {
final int contentLength = allContents.length;
final Data persistentContents[] = new Data[contentLength];
final ObjectAssociation[] fields = getFieldOrder(object.getSpecification());
for (int i = 0; i < contentLength; i++) {
final Data fieldData = allContents[i];
if (fieldData instanceof NullData) {
persistentContents[i] = null;
} else if (fields[i].isOneToOneAssociation()) {
if (fieldData instanceof ObjectData) {
final ObjectAdapter fieldReference = fields[i].get(object);
persistentContents[i] = encodeMadePersistentGraph((ObjectData) fieldData, fieldReference);
} else {
persistentContents[i] = null;
}
} else if (fields[i].isOneToManyAssociation()) {
final ObjectAdapter fieldReference = fields[i].get(object);
persistentContents[i] =
createMadePersistentCollection((CollectionData) fieldData, fieldReference);
}
}
persistedData.setFieldContent(persistentContents);
}
return persistedData;
} else {
return null;
}
}
private Data createMadePersistentCollection(final CollectionData collectionData, final ObjectAdapter collection) {
final ReferenceData[] elementData = collectionData.getElements();
final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(collection);
final Iterator elements = facet.iterator(collection);
for (int i = 0; i < elementData.length; i++) {
final ObjectAdapter element = (ObjectAdapter) elements.next();
final Oid oid = element.getOid();
Assert.assertNotNull(oid);
elementData[i] = encodeMadePersistentGraph((ObjectData) elementData[i], element);
}
return collectionData;
}
/**
* Called server-side only:
* <ul>
* <li>by {@link ServerFacadeImpl#executeServerAction(ExecuteServerActionRequest)}
* </ul>
*/
@Override
public ExecuteServerActionResponse encodeServerActionResult(final ObjectAdapter result,
final ObjectData[] updatesData, final ReferenceData[] disposedData, final ObjectData persistedTargetData,
final ObjectData[] persistedParametersData, final String[] messages, final String[] warnings) {
Data resultData;
if (result == null) {
resultData = dataFactory.createNullData("");
} else if (result.getSpecification().isCollection()) {
resultData =
serializer.serializeCollection(result, serverSideRetrievedObjectGraphDepth, new KnownObjectsRequest());
} else if (result.getSpecification().isNotCollection()) {
resultData = encodeCompletePersistentGraph(result);
} else {
throw new UnknownTypeException(result);
}
return new ExecuteServerActionResponse(resultData, updatesData, disposedData, persistedTargetData,
persistedParametersData, messages, warnings);
}
/**
* Called server-side only:
* <ul>
* <li>by {@link ServerFacadeImpl#resolveField(ResolveFieldRequest)}
* <li>by {@link ServerFacadeImpl#executeServerAction(ExecuteServerActionRequest) </ul>
*/
@Override
public ObjectAssociation[] getFieldOrder(final ObjectSpecification specification) {
return fieldOrderCache.getFields(specification);
}
// ///////////////////////////////////////////////////////////////
// Helpers
// ///////////////////////////////////////////////////////////////
private final Data createParameter(final String type, final ObjectAdapter adapter,
final KnownObjectsRequest knownObjects) {
if (adapter == null) {
return dataFactory.createNullData(type);
}
if (!adapter.getSpecification().isNotCollection()) {
throw new UnknownTypeException(adapter.getSpecification());
}
if (adapter.getSpecification().isEncodeable()) {
return serializer.serializeEncodeable(adapter);
} else {
return encode(adapter, clientSideActionParameterGraphDepth, knownObjects);
}
}
private ObjectData encode(final ObjectAdapter adapter, final int depth) {
return (ObjectData) encode(adapter, depth, new KnownObjectsRequest());
}
private ReferenceData encode(final ObjectAdapter adapter, final int depth, final KnownObjectsRequest knownObjects) {
return serializer.serializeAdapter(adapter, depth, knownObjects);
}
}