/* * 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.viewer.wicket.model.mementos; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.Lists; import org.apache.isis.applib.services.bookmark.Bookmark; import org.apache.isis.applib.services.hint.HintStore; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking; import org.apache.isis.core.metamodel.adapter.oid.Oid; import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller; import org.apache.isis.core.metamodel.adapter.oid.RootOid; import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet; import org.apache.isis.core.metamodel.spec.ObjectSpecId; import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.spec.feature.ObjectAction; import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter; import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation; import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation; import org.apache.isis.core.metamodel.specloader.SpecificationLoader; import org.apache.isis.core.runtime.memento.Memento; import org.apache.isis.core.runtime.persistence.ObjectNotFoundException; import org.apache.isis.core.runtime.system.persistence.PersistenceSession; import org.apache.isis.core.runtime.system.session.IsisSessionFactory; public class ObjectAdapterMemento implements Serializable { private static final long serialVersionUID = 1L; public static final OidMarshaller OID_MARSHALLER = OidMarshaller.INSTANCE; /** * Factory method */ public static ObjectAdapterMemento createOrNull(final ObjectAdapter adapter) { if (adapter == null) { return null; } final Object object = adapter.getObject(); if(object == null) { return null; } return new ObjectAdapterMemento(adapter); } /** * Factory method */ public static ObjectAdapterMemento createPersistent(final RootOid rootOid) { return new ObjectAdapterMemento(rootOid); } public static ObjectAdapterMemento createForList( final ArrayList<ObjectAdapterMemento> list, final ObjectSpecId objectSpecId) { return new ObjectAdapterMemento(list, objectSpecId); } public static ObjectAdapterMemento createForList( final Collection<ObjectAdapterMemento> list, final ObjectSpecId objectSpecId) { return list != null ? createForList(Lists.newArrayList(list), objectSpecId) : null; } enum Sort { /** * represents a single object */ SCALAR { @Override public ObjectAdapter asAdapter( final ObjectAdapterMemento oam, final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { return oam.type.getAdapter(oam, concurrencyChecking, persistenceSession, specificationLoader); } @Override public int hashCode(final ObjectAdapterMemento oam) { return oam.type.hashCode(oam); } @Override public boolean equals(final ObjectAdapterMemento oam, final Object other) { if (!(other instanceof ObjectAdapterMemento)) { return false; } final ObjectAdapterMemento otherOam = (ObjectAdapterMemento) other; if(otherOam.sort != SCALAR) { return false; } return oam.type.equals(oam, (ObjectAdapterMemento) other); } @Override public String asString(final ObjectAdapterMemento oam) { return oam.type.toString(oam); } }, /** * represents a list of objects */ VECTOR { @Override public ObjectAdapter asAdapter( final ObjectAdapterMemento oam, final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { final List<Object> listOfPojos = Lists.newArrayList( FluentIterable.from(oam.list) .transform(Functions.toPojo(persistenceSession, specificationLoader)) .toList() ); return ObjectAdapter.Functions.adapterForUsing(persistenceSession).apply(listOfPojos); } @Override public int hashCode(final ObjectAdapterMemento oam) { return oam.list.hashCode(); } @Override public boolean equals(final ObjectAdapterMemento oam, final Object other) { if (!(other instanceof ObjectAdapterMemento)) { return false; } final ObjectAdapterMemento otherOam = (ObjectAdapterMemento) other; if(otherOam.sort != VECTOR) { return false; } return oam.list.equals(otherOam.list); } @Override public String asString(final ObjectAdapterMemento oam) { return oam.list.toString(); } }; void ensure(final Sort sort) { if(this == sort) { return; } throw new IllegalStateException("Memento is not for " + sort); } public abstract ObjectAdapter asAdapter( final ObjectAdapterMemento oam, final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader); public abstract int hashCode(final ObjectAdapterMemento oam); public abstract boolean equals(final ObjectAdapterMemento oam, final Object other); public abstract String asString(final ObjectAdapterMemento oam); } enum Type { /** * The {@link ObjectAdapter} that this is the memento for directly has * an {@link EncodableFacet} (it is almost certainly a value), and so is * stored directly. */ ENCODEABLE { @Override ObjectAdapter recreateAdapter( final ObjectAdapterMemento oam, final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { ObjectSpecId objectSpecId = oam.objectSpecId; ObjectSpecification objectSpec = SpecUtils.getSpecificationFor(objectSpecId, specificationLoader); final EncodableFacet encodableFacet = objectSpec.getFacet(EncodableFacet.class); return encodableFacet.fromEncodedString(oam.encodableValue); } @Override public boolean equals(ObjectAdapterMemento oam, ObjectAdapterMemento other) { return other.type == ENCODEABLE && oam.encodableValue.equals(other.encodableValue); } @Override public int hashCode(ObjectAdapterMemento oam) { return oam.encodableValue.hashCode(); } @Override public String toString(final ObjectAdapterMemento oam) { return oam.encodableValue; } @Override public void resetVersion( ObjectAdapterMemento objectAdapterMemento, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { } }, /** * The {@link ObjectAdapter} that this is for is already known by its * (persistent) {@link Oid}. */ PERSISTENT { @Override ObjectAdapter recreateAdapter( final ObjectAdapterMemento oam, ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { RootOid oid = OID_MARSHALLER.unmarshal(oam.persistentOidStr, RootOid.class); try { final ObjectAdapter adapter = persistenceSession.adapterFor(oid, concurrencyChecking); return adapter; } finally { // a side-effect of AdapterManager#adapterFor(...) is that it will update the oid // with the correct version, even when there is a concurrency exception // we copy this updated oid string into our memento so that, if we retry, // we will succeed second time around oam.persistentOidStr = oid.enString(); } } @Override public void resetVersion( final ObjectAdapterMemento oam, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { // REVIEW: this may be redundant because recreateAdapter also guarantees the version will be reset. final ObjectAdapter adapter = recreateAdapter( oam, ConcurrencyChecking.NO_CHECK, persistenceSession, specificationLoader); Oid oid = adapter.getOid(); oam.persistentOidStr = oid.enString(); } @Override public boolean equals(ObjectAdapterMemento oam, ObjectAdapterMemento other) { return other.type == PERSISTENT && oam.persistentOidStr.equals(other.persistentOidStr); } @Override public int hashCode(ObjectAdapterMemento oam) { return oam.persistentOidStr.hashCode(); } @Override public String toString(final ObjectAdapterMemento oam) { return oam.persistentOidStr; } }, /** * Uses Isis' own {@link Memento}, to capture the state of a transient * object. */ TRANSIENT { /** * {@link AdapterManager.ConcurrencyChecking} is ignored for transients. */ @Override ObjectAdapter recreateAdapter( final ObjectAdapterMemento oam, final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { return oam.transientMemento.recreateObject(); } @Override public boolean equals(ObjectAdapterMemento oam, ObjectAdapterMemento other) { return other.type == TRANSIENT && oam.transientMemento.equals(other.transientMemento); } @Override public int hashCode(ObjectAdapterMemento oam) { return oam.transientMemento.hashCode(); } @Override public String toString(final ObjectAdapterMemento oam) { return oam.transientMemento.toString(); } @Override public void resetVersion( final ObjectAdapterMemento objectAdapterMemento, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { } }; public ObjectAdapter getAdapter( final ObjectAdapterMemento nom, final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { return recreateAdapter(nom, concurrencyChecking, persistenceSession, specificationLoader); } abstract ObjectAdapter recreateAdapter( final ObjectAdapterMemento nom, final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader); public abstract boolean equals(ObjectAdapterMemento oam, ObjectAdapterMemento other); public abstract int hashCode(ObjectAdapterMemento objectAdapterMemento); public abstract String toString(ObjectAdapterMemento adapterMemento); public abstract void resetVersion( ObjectAdapterMemento objectAdapterMemento, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader); } private final Sort sort; private final ObjectSpecId objectSpecId; /** * Populated only if {@link #getSort() sort} is {@link Sort#SCALAR scalar} */ private Type type; /** * Populated only if {@link #getSort() sort} is {@link Sort#SCALAR scalar} */ private String titleHint; /** * The current value, if {@link Type#ENCODEABLE}; will be <tt>null</tt> otherwise. * * <p> * Also, populated only if {@link #getSort() sort} is {@link Sort#SCALAR scalar} */ private String encodableValue; /** * The current value, if {@link Type#PERSISTENT}, will be <tt>null</tt> otherwise. * * <p> * Also, populated only if {@link #getSort() sort} is {@link Sort#SCALAR scalar} */ private String persistentOidStr; /** * The current value, if {@link Type#PERSISTENT}, will be <tt>null</tt> otherwise. * * <p> * Also, populated only if {@link #getSort() sort} is {@link Sort#SCALAR scalar} */ private Bookmark bookmark; /** * Only populated for {@link ObjectAdapter#getObject() domain object}s that implement {@link HintStore.HintIdProvider}. */ private String hintId; /** * The current value, if {@link Type#TRANSIENT}, will be <tt>null</tt> otherwise. * * <p> * Also, populated only if {@link #getSort() sort} is {@link Sort#SCALAR scalar} */ private Memento transientMemento; /** * opulated only if {@link #getSort() sort} is {@link Sort#VECTOR vector} */ private ArrayList<ObjectAdapterMemento> list; public ObjectAdapterMemento(final ArrayList<ObjectAdapterMemento> list, final ObjectSpecId objectSpecId) { this.sort = Sort.VECTOR; this.list = list; this.objectSpecId = objectSpecId; } private ObjectAdapterMemento(final RootOid rootOid) { assert !rootOid.isTransient(); this.sort = Sort.SCALAR; this.persistentOidStr = rootOid.enString(); this.bookmark = rootOid.asBookmark(); this.objectSpecId = rootOid.getObjectSpecId(); this.type = Type.PERSISTENT; } private ObjectAdapterMemento(final ObjectAdapter adapter) { if (adapter == null) { throw new IllegalArgumentException("adapter cannot be null"); } this.sort = Sort.SCALAR; final ObjectSpecification specification = adapter.getSpecification(); objectSpecId = specification.getSpecId(); init(adapter); } private void init(final ObjectAdapter adapter) { final ObjectSpecification specification = adapter.getSpecification(); final EncodableFacet encodableFacet = specification.getFacet(EncodableFacet.class); final boolean isEncodable = encodableFacet != null; if (isEncodable) { encodableValue = encodableFacet.toEncodedString(adapter); type = Type.ENCODEABLE; return; } final RootOid oid = (RootOid) adapter.getOid(); if (oid.isTransient()) { transientMemento = new Memento(adapter); type = Type.TRANSIENT; return; } persistentOidStr = oid.enString(); bookmark = oid.asBookmark(); if(adapter.getObject() instanceof HintStore.HintIdProvider) { HintStore.HintIdProvider provider = (HintStore.HintIdProvider) adapter.getObject(); this.hintId = provider.hintId(); } type = Type.PERSISTENT; } public Sort getSort() { return sort; } public ArrayList<ObjectAdapterMemento> getList() { ensureVector(); return list; } public void resetVersion( final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { ensureScalar(); type.resetVersion(this, persistenceSession, specificationLoader); } public Bookmark asBookmark() { ensureScalar(); return bookmark; } public Bookmark asHintingBookmark() { Bookmark bookmark = asBookmark(); return hintId != null && bookmark != null ? new HintStore.BookmarkWithHintId(bookmark, hintId) : bookmark; } /** * Lazily looks up {@link ObjectAdapter} if required. * * <p> * For transient objects, be aware that calling this method more than once * will cause the underlying {@link ObjectAdapter} to be recreated, * overwriting any changes that may have been made. In general then it's * best to call once and then hold onto the value thereafter. Alternatively, * can call {@link #setAdapter(ObjectAdapter)} to keep this memento in sync. */ public ObjectAdapter getObjectAdapter( final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { return sort.asAdapter(this, concurrencyChecking, persistenceSession, specificationLoader); } /** * Updates the memento if the adapter's state has changed. * * @param adapter */ public void setAdapter(final ObjectAdapter adapter) { ensureScalar(); init(adapter); } public ObjectSpecId getObjectSpecId() { return objectSpecId; } /** * Analogous to {@link List#contains(Object)}, but does not perform * {@link ConcurrencyChecking concurrency checking} of the OID. */ public boolean containedIn( List<ObjectAdapterMemento> list, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { ensureScalar(); // REVIEW: heavy handed, ought to be possible to just compare the OIDs // ignoring the concurrency checking final ObjectAdapter currAdapter = getObjectAdapter(ConcurrencyChecking.NO_CHECK, persistenceSession, specificationLoader); for (ObjectAdapterMemento each : list) { if(each == null) { continue; } final ObjectAdapter otherAdapter = each.getObjectAdapter( ConcurrencyChecking.NO_CHECK, persistenceSession, specificationLoader); if(currAdapter == otherAdapter) { return true; } } return false; } @Override public int hashCode() { return sort.hashCode(this); } @Override public boolean equals(Object obj) { return sort.equals(this, obj); } @Override public String toString() { return asString(); } public String asString() { return sort.asString(this); } ////////////////////////////////////////////////// // Functions ////////////////////////////////////////////////// public final static class Functions { private Functions() { } public static Function<ObjectSpecification, ObjectSpecId> fromSpec() { return new Function<ObjectSpecification, ObjectSpecId>() { @Override public ObjectSpecId apply(final ObjectSpecification from) { return from.getSpecId(); } }; } public static Function<OneToOneAssociation, PropertyMemento> fromProperty( final IsisSessionFactory isisSessionFactory) { return new Function<OneToOneAssociation, PropertyMemento>() { @Override public PropertyMemento apply(final OneToOneAssociation from) { return new PropertyMemento(from, isisSessionFactory); } }; } public static Function<OneToManyAssociation, CollectionMemento> fromCollection( final IsisSessionFactory isisSessionFactory) { return new Function<OneToManyAssociation, CollectionMemento>() { @Override public CollectionMemento apply(final OneToManyAssociation from) { return new CollectionMemento(from, isisSessionFactory); } }; } public static Function<ObjectAction, ActionMemento> fromAction() { return new Function<ObjectAction, ActionMemento>() { @Override public ActionMemento apply(final ObjectAction from) { return new ActionMemento(from); } }; } public static Function<ObjectActionParameter, ActionParameterMemento> fromActionParameter() { return new Function<ObjectActionParameter, ActionParameterMemento>() { @Override public ActionParameterMemento apply(final ObjectActionParameter from) { return new ActionParameterMemento(from); } }; } public static Function<Object, ObjectAdapterMemento> fromPojo(final AdapterManager adapterManager) { return new Function<Object, ObjectAdapterMemento>() { @Override public ObjectAdapterMemento apply(final Object pojo) { final ObjectAdapter adapter = adapterManager.adapterFor(pojo); return ObjectAdapterMemento.createOrNull(adapter); } }; } public static Function<ObjectAdapter, ObjectAdapterMemento> fromAdapter() { return new Function<ObjectAdapter, ObjectAdapterMemento>() { @Override public ObjectAdapterMemento apply(final ObjectAdapter adapter) { return ObjectAdapterMemento.createOrNull(adapter); } }; } public static Function<ObjectAdapterMemento, ObjectAdapter> fromMemento( final ConcurrencyChecking concurrencyChecking, final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { return new Function<ObjectAdapterMemento, ObjectAdapter>() { @Override public ObjectAdapter apply(final ObjectAdapterMemento from) { try { return from.getObjectAdapter(concurrencyChecking, persistenceSession, specificationLoader); } catch (ObjectNotFoundException e) { // this can happen if for example the object is not visible (due to the security tenanted facet) return null; } } }; } public static Function<ObjectAdapter, ObjectAdapterMemento> toMemento() { return new Function<ObjectAdapter, ObjectAdapterMemento>() { @Override public ObjectAdapterMemento apply(ObjectAdapter from) { return ObjectAdapterMemento.createOrNull(from); } }; } public static Function<? super ObjectAdapterMemento, Object> toPojo( final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) { return new Function<ObjectAdapterMemento, Object>() { @Nullable @Override public Object apply(@Nullable final ObjectAdapterMemento input) { if(input == null) { return null; } final ObjectAdapter objectAdapter = input .getObjectAdapter(ConcurrencyChecking.NO_CHECK, persistenceSession, specificationLoader); if(objectAdapter == null) { return null; } return objectAdapter.getObject(); } }; } } private void ensureScalar() { getSort().ensure(Sort.SCALAR); } private void ensureVector() { getSort().ensure(Sort.VECTOR); } }