/* * 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.core.metamodel.adapter; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import com.google.common.base.Function; import com.google.common.collect.Lists; import org.apache.isis.applib.annotation.Where; import org.apache.isis.core.commons.lang.ClassExtensions; import org.apache.isis.core.commons.lang.ListExtensions; import org.apache.isis.core.commons.lang.MethodExtensions; import org.apache.isis.core.commons.lang.MethodUtil; import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; import org.apache.isis.core.metamodel.adapter.oid.ParentedCollectionOid; import org.apache.isis.core.metamodel.adapter.oid.Oid; import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException; import org.apache.isis.core.metamodel.adapter.version.Version; import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; import org.apache.isis.core.metamodel.consent.InteractionResult; import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet; import org.apache.isis.core.metamodel.facets.object.title.TitleFacet; import org.apache.isis.core.metamodel.interactions.InteractionUtils; import org.apache.isis.core.metamodel.interactions.ObjectVisibilityContext; import org.apache.isis.core.metamodel.interactions.VisibilityContext; import org.apache.isis.core.metamodel.spec.ElementSpecificationProvider; import org.apache.isis.core.metamodel.spec.Instance; import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.spec.Specification; /** * Adapters to domain objects, where the application is written in terms of * domain objects and those objects are represented within the NOF through these * adapter, and not directly. */ public interface ObjectAdapter extends Instance, org.apache.isis.applib.annotation.When.Persistable { /** * Refines {@link Instance#getSpecification()}. */ @Override ObjectSpecification getSpecification(); /** * Returns the adapted domain object, the POJO, that this adapter represents * with the framework. */ Object getObject(); /** * Returns the title to display this object with, usually obtained from * the wrapped {@link #getObject() domain object}. * * @deprecated - use {@link #titleString(ObjectAdapter)} */ @Deprecated String titleString(); /** * Returns the title to display this object with, rendered within the context * of some other adapter. * * <p> * @see TitleFacet#title(ObjectAdapter, ObjectAdapter) */ String titleString(ObjectAdapter contextAdapter); /** * Return an {@link Instance} of the specified {@link Specification} with * respect to this {@link ObjectAdapter}. * * <p> * If called with {@link ObjectSpecification}, then just returns * <tt>this</tt>). If called for other subinterfaces, then should provide an * appropriate {@link Instance} implementation. * * <p> * Designed to be called in a double-dispatch design from * {@link Specification#getInstance(ObjectAdapter)}. * * <p> * Note: this method will throw an {@link UnsupportedOperationException} * unless the extended <tt>PojoAdapterXFactory</tt> is configured. (That is, * only <tt>PojoAdapterX</tt> provides support for this; the regular * <tt>PojoAdapter</tt> does not currently. * * @return */ Instance getInstance(Specification specification); /** * Sometimes it is necessary to manage the replacement of the underlying * domain object (by another component such as an object store). This method * allows the adapter to be kept while the domain object is replaced. */ void replacePojo(Object pojo); /** * For (stand-alone) collections, returns the element type. * * <p> * For owned (aggregated) collections, the element type can be determined * from the <tt>TypeOfFacet</tt> associated with the * <tt>ObjectAssociation</tt> representing the collection. * * @see #setElementSpecificationProvider(ElementSpecificationProvider) */ ObjectSpecification getElementSpecification(); /** * For (stand-alone) collections, returns the element type. * * @see #getElementSpecification() */ void setElementSpecificationProvider(ElementSpecificationProvider elementSpecificationProvider); /** * Returns the name of an icon to use if this object is to be displayed * graphically. * * <p> * May return <code>null</code> if no icon is specified. */ String getIconName(); /** * Checks the version of this adapter to make sure that it does not differ * from the specified version. * * @throws ConcurrencyException * if the specified version differs from the version held this * adapter. */ void checkLock(Version version); /** * The object's unique {@link Oid}. * * <p> * This id allows the object to added to, stored by, * and retrieved from the object store. Objects can be looked up by their * {@link Oid} from the {@link AdapterManager}. * * <p> * Note that standalone value objects ("foobar", or 5, or a date), * are not mapped and have a <tt>null</tt> oid. */ Oid getOid(); /** * Since {@link Oid}s are now immutable, it is the reference from the * {@link ObjectAdapter} to its {@link Oid} that must now be updated. */ void replaceOid(Oid persistedOid); /** * Returns either itself (if this is a root) or the parented collections, the * adapter corresponding to their {@link ParentedCollectionOid#getRootOid() root oid}. */ ObjectAdapter getAggregateRoot(); Version getVersion(); void setVersion(Version version); /** * Whether this instance belongs to another object (meaning its * {@link #getOid()} will be a {@link ParentedCollectionOid}). */ boolean isParentedCollection(); /** * Whether this is a value (standalone, has no oid). */ boolean isValue(); public final class Util { private Util() { } public static Object unwrap(final ObjectAdapter adapter) { return adapter != null ? adapter.getObject() : null; } public static Object[] unwrap(final ObjectAdapter[] adapters) { if (adapters == null) { return null; } final Object[] unwrappedObjects = new Object[adapters.length]; int i = 0; for (final ObjectAdapter adapter : adapters) { unwrappedObjects[i++] = unwrap(adapter); } return unwrappedObjects; } public static List<Object> unwrap(final List<ObjectAdapter> adapters) { List<Object> objects = Lists.newArrayList(); for (ObjectAdapter adapter : adapters) { objects.add(unwrap(adapter)); } return objects; } @SuppressWarnings("unchecked") public static <T> List<T> unwrapT(final List<ObjectAdapter> adapters) { return (List<T>) unwrap(adapters); } public static String unwrapAsString(final ObjectAdapter adapter) { final Object obj = unwrap(adapter); if (obj == null) { return null; } if (!(obj instanceof String)) { return null; } return (String) obj; } public static String titleString(final ObjectAdapter adapter) { return adapter != null ? adapter.titleString(null) : ""; } public static boolean exists(final ObjectAdapter adapter) { return adapter != null && adapter.getObject() != null; } public static boolean wrappedEqual(final ObjectAdapter adapter1, final ObjectAdapter adapter2) { final boolean defined1 = exists(adapter1); final boolean defined2 = exists(adapter2); if (defined1 && !defined2) { return false; } if (!defined1 && defined2) { return false; } if (!defined1 && !defined2) { return true; } // both null return adapter1.getObject().equals(adapter2.getObject()); } public static boolean nullSafeEquals(final Object obj1, final Object obj2) { if (obj1 == null && obj2 == null) { return true; } if (obj1 == null || obj2 == null) { return false; } if (obj1.equals(obj2)) { return true; } if (obj1 instanceof ObjectAdapter && obj2 instanceof ObjectAdapter) { final ObjectAdapter adapterObj1 = (ObjectAdapter) obj1; final ObjectAdapter adapterObj2 = (ObjectAdapter) obj2; return nullSafeEquals(adapterObj1.getObject(), adapterObj2.getObject()); } return false; } /** * Filters a collection (an adapter around either a Collection or an Object[]) and returns a list of * {@link ObjectAdapter}s of those that are visible (as per any facet(s) installed on the element class * of the collection). * @param collectionAdapter - an adapter around a collection (as returned by a getter of a collection, or of an autoCompleteNXxx() or choicesNXxx() method, etc * @param interactionInitiatedBy */ public static List<ObjectAdapter> visibleAdapters( final ObjectAdapter collectionAdapter, final InteractionInitiatedBy interactionInitiatedBy) { final CollectionFacet facet = CollectionFacet.Utils.getCollectionFacetFromSpec(collectionAdapter); Iterable<ObjectAdapter> objectAdapters = facet.iterable(collectionAdapter); return visibleAdapters(objectAdapters, interactionInitiatedBy); } /** * as per {@link #visibleAdapters(ObjectAdapter, InteractionInitiatedBy)}. * @param objectAdapters - iterable over the respective adapters of a collection (as returned by a getter of a collection, or of an autoCompleteNXxx() or choicesNXxx() method, etc * @param interactionInitiatedBy */ public static List<ObjectAdapter> visibleAdapters( final Iterable<ObjectAdapter> objectAdapters, final InteractionInitiatedBy interactionInitiatedBy) { final List<ObjectAdapter> adapters = Lists.newArrayList(); for (final ObjectAdapter adapter : objectAdapters) { final boolean visible = isVisible(adapter, interactionInitiatedBy); if(visible) { adapters.add(adapter); } } return adapters; } /** * @param adapter - an adapter around the domain object whose visibility is being checked * @param interactionInitiatedBy */ public static boolean isVisible( final ObjectAdapter adapter, final InteractionInitiatedBy interactionInitiatedBy) { if(adapter == null) { // a choices list could include a null (eg example in ToDoItems#choices1Categorized()); want to show as "visible" return true; } if(adapter.isDestroyed()) { return false; } if(interactionInitiatedBy == InteractionInitiatedBy.FRAMEWORK) { return true; } return isVisibleForUser(adapter); } private static boolean isVisibleForUser(final ObjectAdapter adapter) { final VisibilityContext<?> context = createVisibleInteractionContextForUser(adapter); final ObjectSpecification objectSpecification = adapter.getSpecification(); final InteractionResult visibleResult = InteractionUtils.isVisibleResult(objectSpecification, context); return visibleResult.isNotVetoing(); } private static VisibilityContext<?> createVisibleInteractionContextForUser( final ObjectAdapter objectAdapter) { return new ObjectVisibilityContext( objectAdapter, objectAdapter.getSpecification().getIdentifier(), InteractionInitiatedBy.USER, Where.OBJECT_FORMS); } } boolean representsPersistent(); boolean isDestroyed(); public final class InvokeUtils { private InvokeUtils() { } public static void invokeAll(final List<Method> methods, final ObjectAdapter adapter) { MethodUtil.invoke(methods, Util.unwrap(adapter)); } public static Object invoke(final Method method, final ObjectAdapter adapter) { return MethodExtensions.invoke(method, Util.unwrap(adapter)); } public static Object invoke(final Method method, final ObjectAdapter adapter, final Object arg0) { return MethodExtensions.invoke(method, Util.unwrap(adapter), new Object[] {arg0}); } public static Object invoke(final Method method, final ObjectAdapter adapter, final ObjectAdapter arg0Adapter) { return invoke(method, adapter, Util.unwrap(arg0Adapter)); } public static Object invoke(final Method method, final ObjectAdapter adapter, final ObjectAdapter[] argumentAdapters) { return MethodExtensions.invoke(method, Util.unwrap(adapter), Util.unwrap(argumentAdapters)); } public static Object invoke(final Method method, final ObjectAdapter adapter, final Map<Integer, ObjectAdapter> argumentAdapters) { return invoke(method, adapter, asArray(argumentAdapters, method.getParameterTypes().length)); } private static ObjectAdapter[] asArray(Map<Integer, ObjectAdapter> argumentAdapters, int length) { ObjectAdapter[] args = new ObjectAdapter[length]; for (final Map.Entry<Integer, ObjectAdapter> entry : argumentAdapters.entrySet()) { final Integer paramNum = entry.getKey(); if(paramNum < length) { args[paramNum] = entry.getValue(); } } return args; } /** * Invokes the method, adjusting arguments as required to make them fit the method's parameters. * * <p> * That is: * <ul> * <li>if the method declares parameters but arguments are missing, then will provide 'null' defaults for these. * <li>if the method does not declare all parameters for arguments, then truncates arguments. * </ul> */ public static Object invokeAutofit(final Method method, final ObjectAdapter target, List<ObjectAdapter> argumentsIfAvailable, final AdapterManager adapterManager) { final List<ObjectAdapter> args = Lists.newArrayList(); if(argumentsIfAvailable != null) { args.addAll(argumentsIfAvailable); } adjust(method, args, adapterManager); final ObjectAdapter[] argArray = args.toArray(new ObjectAdapter[]{}); return invoke(method, target, argArray); } private static void adjust(final Method method, final List<ObjectAdapter> args, final AdapterManager adapterManager) { final Class<?>[] parameterTypes = method.getParameterTypes(); ListExtensions.adjust(args, parameterTypes.length); for(int i=0; i<parameterTypes.length; i++) { final Class<?> cls = parameterTypes[i]; if(args.get(i) == null && cls.isPrimitive()) { final Object object = ClassExtensions.toDefault(cls); final ObjectAdapter adapter = adapterManager.adapterFor(object); args.set(i, adapter); } } } /** * Invokes the method, adjusting arguments as required. * * <p> * That is: * <ul> * <li>if the method declares parameters but no arguments are provided, then will provide 'null' defaults for these. * <li>if the method does not declare parameters but arguments were provided, then will ignore those argumens. * </ul> */ @SuppressWarnings("unused") private static Object invokeWithDefaults(final Method method, final ObjectAdapter adapter, final ObjectAdapter[] argumentAdapters) { final int numParams = method.getParameterTypes().length; ObjectAdapter[] adapters; if(argumentAdapters == null || argumentAdapters.length == 0) { adapters = new ObjectAdapter[numParams]; } else if(numParams == 0) { // ignore any arguments, even if they were supplied. // eg used by contributee actions, but // underlying service 'default' action declares no params adapters = new ObjectAdapter[0]; } else if(argumentAdapters.length == numParams){ adapters = argumentAdapters; } else { throw new IllegalArgumentException("Method has " + numParams + " params but " + argumentAdapters.length + " arguments provided"); } return invoke(method, adapter, adapters); } } public static class Functions { private Functions(){} public static Function<ObjectAdapter, Object> getObject() { return new Function<ObjectAdapter, Object>() { @Override public Object apply(ObjectAdapter input) { return Util.unwrap(input); } }; } public static Function<Object, ObjectAdapter> adapterForUsing(final AdapterManager adapterManager) { return new Function<Object, ObjectAdapter>() { @Override public ObjectAdapter apply(final Object pojo) { return adapterManager.adapterFor(pojo); } }; } } }