/* * 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.specloader.specimpl.dflt; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.filter.Filter; import org.apache.isis.applib.filter.Filters; import org.apache.isis.core.commons.lang.StringExtensions; import org.apache.isis.core.commons.util.ToString; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.facetapi.Facet; import org.apache.isis.core.metamodel.facetapi.FacetHolder; import org.apache.isis.core.metamodel.facets.FacetedMethod; import org.apache.isis.core.metamodel.facets.ImperativeFacet; import org.apache.isis.core.metamodel.facets.all.i18n.NamedFacetTranslated; import org.apache.isis.core.metamodel.facets.all.i18n.PluralFacetTranslated; import org.apache.isis.core.metamodel.facets.all.named.NamedFacet; import org.apache.isis.core.metamodel.facets.all.named.NamedFacetInferred; import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet; import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet; import org.apache.isis.core.metamodel.facets.object.plural.inferred.PluralFacetInferred; import org.apache.isis.core.metamodel.facets.object.value.ValueFacet; import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet; import org.apache.isis.core.metamodel.facets.object.wizard.WizardFacet; import org.apache.isis.core.metamodel.services.ServicesInjector; import org.apache.isis.core.metamodel.spec.ActionType; import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.spec.feature.Contributed; import org.apache.isis.core.metamodel.spec.feature.ObjectAction; import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation; import org.apache.isis.core.metamodel.spec.feature.ObjectMember; import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor; import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor; import org.apache.isis.core.metamodel.specloader.specimpl.FacetedMethodsBuilder; import org.apache.isis.core.metamodel.specloader.specimpl.FacetedMethodsBuilderContext; import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionDefault; import org.apache.isis.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract; import org.apache.isis.core.metamodel.specloader.specimpl.OneToManyAssociationDefault; import org.apache.isis.core.metamodel.specloader.specimpl.OneToOneAssociationDefault; public class ObjectSpecificationDefault extends ObjectSpecificationAbstract implements FacetHolder { private final static Logger LOG = LoggerFactory.getLogger(ObjectSpecificationDefault.class); private static final ClassSubstitutor classSubstitutor = new ClassSubstitutor(); private static String determineShortName(final Class<?> introspectedClass) { final String name = introspectedClass.getName(); return name.substring(name.lastIndexOf('.') + 1); } //region > constructor, fields /** * Lazily built by {@link #getMember(Method)}. */ private Map<Method, ObjectMember> membersByMethod = null; private final FacetedMethodsBuilder facetedMethodsBuilder; private final boolean isService; public ObjectSpecificationDefault( final Class<?> correspondingClass, final FacetedMethodsBuilderContext facetedMethodsBuilderContext, final ServicesInjector servicesInjector, final FacetProcessor facetProcessor, final NatureOfService natureOfServiceIfAny) { super(correspondingClass, determineShortName(correspondingClass), servicesInjector, facetProcessor); this.isService = natureOfServiceIfAny != null; this.facetedMethodsBuilder = new FacetedMethodsBuilder(this, facetedMethodsBuilderContext); } //endregion //region > introspectTypeHierarchyAndMembers @Override public void introspectTypeHierarchyAndMembers() { metadataProperties = null; if(isNotIntrospected()) { metadataProperties = facetedMethodsBuilder.introspectClass(); } // name if(isNotIntrospected()) { addNamedFacetAndPluralFacetIfRequired(); } // go no further if a value if(this.containsFacet(ValueFacet.class)) { if (LOG.isDebugEnabled()) { LOG.debug("skipping full introspection for value type " + getFullIdentifier()); } return; } // superclass if(isNotIntrospected()) { final Class<?> superclass = getCorrespondingClass().getSuperclass(); updateSuperclass(superclass); } // walk superinterfaces // // REVIEW: the processing here isn't quite the same as with // superclasses, in that with superclasses the superclass adds this type as its // subclass, whereas here this type defines itself as the subtype. // // it'd be nice to push the responsibility for adding subclasses to // the interface type... needs some tests around it, though, before // making that refactoring. // final Class<?>[] interfaceTypes = getCorrespondingClass().getInterfaces(); final List<ObjectSpecification> interfaceSpecList = Lists.newArrayList(); for (final Class<?> interfaceType : interfaceTypes) { final Class<?> substitutedInterfaceType = classSubstitutor.getClass(interfaceType); if (substitutedInterfaceType != null) { final ObjectSpecification interfaceSpec = getSpecificationLoader().loadSpecification(substitutedInterfaceType); interfaceSpecList.add(interfaceSpec); } } if(isNotIntrospected()) { updateAsSubclassTo(interfaceSpecList); } if(isNotIntrospected()) { updateInterfaces(interfaceSpecList); } // associations and actions if(isNotIntrospected()) { final List<ObjectAssociation> associations = createAssociations(metadataProperties); sortAndUpdateAssociations(associations); } if(isNotIntrospected()) { final List<ObjectAction> actions = createActions(metadataProperties); sortCacheAndUpdateActions(actions); } if(isNotIntrospected()) { facetedMethodsBuilder.introspectClassPostProcessing(metadataProperties); } if(isNotIntrospected()) { updateFromFacetValues(); } } private void addNamedFacetAndPluralFacetIfRequired() { NamedFacet namedFacet = getFacet(NamedFacet.class); if (namedFacet == null) { namedFacet = new NamedFacetInferred(StringExtensions.asNaturalName2(getShortIdentifier()), this); addFacet(namedFacet); } PluralFacet pluralFacet = getFacet(PluralFacet.class); if (pluralFacet == null) { if(namedFacet instanceof NamedFacetTranslated) { final NamedFacetTranslated facet = (NamedFacetTranslated) namedFacet; pluralFacet = new PluralFacetTranslated(facet, this); } else { pluralFacet = new PluralFacetInferred(StringExtensions.asPluralName(namedFacet.value()), this); } addFacet(pluralFacet); } } //endregion //region > create associations and actions private List<ObjectAssociation> createAssociations(Properties properties) { final List<FacetedMethod> associationFacetedMethods = facetedMethodsBuilder.getAssociationFacetedMethods(properties); final List<ObjectAssociation> associations = Lists.newArrayList(); for (FacetedMethod facetedMethod : associationFacetedMethods) { final ObjectAssociation association = createAssociation(facetedMethod); if(association != null) { associations.add(association); } } return associations; } private ObjectAssociation createAssociation(final FacetedMethod facetMethod) { if (facetMethod.getFeatureType().isCollection()) { return new OneToManyAssociationDefault(facetMethod, servicesInjector); } else if (facetMethod.getFeatureType().isProperty()) { return new OneToOneAssociationDefault(facetMethod, servicesInjector); } else { return null; } } private List<ObjectAction> createActions(Properties metadataProperties) { final List<FacetedMethod> actionFacetedMethods = facetedMethodsBuilder.getActionFacetedMethods(metadataProperties); final List<ObjectAction> actions = Lists.newArrayList(); for (FacetedMethod facetedMethod : actionFacetedMethods) { final ObjectAction action = createAction(facetedMethod); if(action != null) { actions.add(action); } } return actions; } private ObjectAction createAction(final FacetedMethod facetedMethod) { if (facetedMethod.getFeatureType().isAction()) { return new ObjectActionDefault(facetedMethod, servicesInjector); } else { return null; } } //endregion //region > isXxx @Override public boolean isViewModel() { return containsFacet(ViewModelFacet.class); } @Override public boolean isViewModelCloneable(ObjectAdapter targetAdapter) { final ViewModelFacet facet = getFacet(ViewModelFacet.class); if(facet == null) { return false; } final Object pojo = targetAdapter.getObject(); return facet.isCloneable(pojo); } @Override public boolean isMixin() { return containsFacet(MixinFacet.class); } @Override public boolean isWizard() { return containsFacet(WizardFacet.class); } @Override public boolean isService() { return isService; } //endregion //region > getObjectAction @Override public ObjectAction getObjectAction(final ActionType type, final String id, final List<ObjectSpecification> parameters) { final List<ObjectAction> actions = getObjectActions(type, Contributed.INCLUDED, Filters.<ObjectAction>any()); return firstAction(actions, id, parameters); } @Override public ObjectAction getObjectAction(final ActionType type, final String id) { final List<ObjectAction> actions = getObjectActions(type, Contributed.INCLUDED, Filters.<ObjectAction>any()); return firstAction(actions, id); } @Override public ObjectAction getObjectAction(final String id) { final List<ObjectAction> actions = getObjectActions(ActionType.ALL, Contributed.INCLUDED, Filters.<ObjectAction>any()); return firstAction(actions, id); } private static ObjectAction firstAction( final List<ObjectAction> candidateActions, final String actionName, final List<ObjectSpecification> parameters) { outer: for (int i = 0; i < candidateActions.size(); i++) { final ObjectAction action = candidateActions.get(i); if (actionName != null && !actionName.equals(action.getId())) { continue outer; } if (action.getParameters().size() != parameters.size()) { continue outer; } for (int j = 0; j < parameters.size(); j++) { if (!parameters.get(j).isOfType(action.getParameters().get(j).getSpecification())) { continue outer; } } return action; } return null; } private static ObjectAction firstAction( final List<ObjectAction> candidateActions, final String id) { if (id == null) { return null; } for (int i = 0; i < candidateActions.size(); i++) { final ObjectAction action = candidateActions.get(i); if (id.equals(action.getIdentifier().toNameParmsIdentityString())) { return action; } if (id.equals(action.getIdentifier().toNameIdentityString())) { return action; } continue; } return null; } //endregion //region > getMember, catalog... (not API) public ObjectMember getMember(final Method method) { if (membersByMethod == null) { this.membersByMethod = catalogueMembers(); } return membersByMethod.get(method); } private HashMap<Method, ObjectMember> catalogueMembers() { final HashMap<Method, ObjectMember> membersByMethod = Maps.newHashMap(); cataloguePropertiesAndCollections(membersByMethod); catalogueActions(membersByMethod); return membersByMethod; } private void cataloguePropertiesAndCollections(final Map<Method, ObjectMember> membersByMethod) { final Filter<ObjectAssociation> noop = Filters.anyOfType(ObjectAssociation.class); final List<ObjectAssociation> fields = getAssociations(Contributed.EXCLUDED, noop); for (int i = 0; i < fields.size(); i++) { final ObjectAssociation field = fields.get(i); final List<Facet> facets = field.getFacets(ImperativeFacet.FILTER); for (final Facet facet : facets) { final ImperativeFacet imperativeFacet = ImperativeFacet.Util.getImperativeFacet(facet); for (final Method imperativeFacetMethod : imperativeFacet.getMethods()) { membersByMethod.put(imperativeFacetMethod, field); } } } } private void catalogueActions(final Map<Method, ObjectMember> membersByMethod) { final List<ObjectAction> userActions = getObjectActions(Contributed.INCLUDED); for (int i = 0; i < userActions.size(); i++) { final ObjectAction userAction = userActions.get(i); final List<Facet> facets = userAction.getFacets(ImperativeFacet.FILTER); for (final Facet facet : facets) { final ImperativeFacet imperativeFacet = ImperativeFacet.Util.getImperativeFacet(facet); for (final Method imperativeFacetMethod : imperativeFacet.getMethods()) { membersByMethod.put(imperativeFacetMethod, userAction); } } } } //endregion //region > toString @Override public String toString() { final ToString str = new ToString(this); str.append("class", getFullIdentifier()); str.append("type", (isParentedOrFreeCollection() ? "Collection" : "Object")); str.append("persistable", persistability()); str.append("superclass", superclass() == null ? "Object" : superclass().getFullIdentifier()); return str.toString(); } //endregion }