/* * 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.services.appfeat; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import javax.annotation.PostConstruct; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.isis.applib.DomainObjectContainer; import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.applib.annotation.SemanticsOf; import org.apache.isis.applib.annotation.When; import org.apache.isis.applib.annotation.Where; import org.apache.isis.applib.fixturescripts.FixtureScript; import org.apache.isis.applib.services.appfeat.ApplicationFeatureRepository; import org.apache.isis.applib.services.appfeat.ApplicationMemberType; import org.apache.isis.applib.services.config.ConfigurationService; import org.apache.isis.applib.services.registry.ServiceRegistry2; import org.apache.isis.core.metamodel.facetapi.FacetHolder; import org.apache.isis.core.metamodel.facets.SingleIntValueFacet; import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet; import org.apache.isis.core.metamodel.facets.collections.modify.CollectionAddToFacet; import org.apache.isis.core.metamodel.facets.collections.modify.CollectionRemoveFromFacet; import org.apache.isis.core.metamodel.facets.objectvalue.maxlen.MaxLengthFacet; import org.apache.isis.core.metamodel.facets.objectvalue.typicallen.TypicalLengthFacet; import org.apache.isis.core.metamodel.facets.properties.update.modify.PropertySetterFacet; 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.SpecificationLoader; import org.apache.isis.core.metamodel.specloader.specimpl.ContributeeMember; @DomainService( nature = NatureOfService.DOMAIN, repositoryFor = ApplicationFeature.class, menuOrder = "" + Integer.MAX_VALUE ) public class ApplicationFeatureRepositoryDefault implements ApplicationFeatureRepository { //region > caches SortedMap<ApplicationFeatureId, ApplicationFeature> packageFeatures = Maps.newTreeMap(); private final SortedMap<ApplicationFeatureId, ApplicationFeature> classFeatures = Maps.newTreeMap(); private final SortedMap<ApplicationFeatureId, ApplicationFeature> memberFeatures = Maps.newTreeMap(); private final SortedMap<ApplicationFeatureId, ApplicationFeature> propertyFeatures = Maps.newTreeMap(); private final SortedMap<ApplicationFeatureId, ApplicationFeature> collectionFeatures = Maps.newTreeMap(); private final SortedMap<ApplicationFeatureId, ApplicationFeature> actionFeatures = Maps.newTreeMap(); //endregion //region > init private static final String KEY = "isis.services.applicationFeatures.init"; @Programmatic @PostConstruct public void init() { if(isEagerInitialize()) { initializeIfRequired(); } } private boolean isEagerInitialize() { final String configuredValue = configurationService.getProperty(KEY); return "eager".equalsIgnoreCase(configuredValue) || "eagerly".equalsIgnoreCase(configuredValue); } //endregion //region > initializeIfRequired enum InitializationState { NOT_INITIALIZED, INITIALIZED } private InitializationState initializationState = InitializationState.NOT_INITIALIZED; private synchronized void initializeIfRequired() { if(initializationState == InitializationState.INITIALIZED) { return; } initializationState = InitializationState.INITIALIZED; final Collection<ObjectSpecification> specifications = primeMetaModel(); createApplicationFeaturesFor(specifications); } private Collection<ObjectSpecification> primeMetaModel() { final List<Object> services = serviceRegistry.getRegisteredServices(); for (final Object service : services) { specificationLoader.loadSpecification(service.getClass()); } return specificationLoader.allSpecifications(); } private void createApplicationFeaturesFor(final Collection<ObjectSpecification> specifications) { // take copy to avoid ConcurrentModificationException final List<ObjectSpecification> objectSpecifications = Lists.newArrayList(specifications); for (final ObjectSpecification spec : objectSpecifications) { createApplicationFeaturesFor(spec); } } void createApplicationFeaturesFor(final ObjectSpecification spec) { if (exclude(spec)) { return; } final List<ObjectAssociation> properties = spec.getAssociations(Contributed.INCLUDED, ObjectAssociation.Filters.PROPERTIES); final List<ObjectAssociation> collections = spec.getAssociations(Contributed.INCLUDED, ObjectAssociation.Filters.COLLECTIONS); final List<ObjectAction> actions = spec.getObjectActions(Contributed.INCLUDED); if (properties.isEmpty() && collections.isEmpty() && actions.isEmpty()) { return; } final String fullIdentifier = spec.getFullIdentifier(); final ApplicationFeatureId classFeatureId = ApplicationFeatureId.newClass(fullIdentifier); // add class to our map // (later on it may get removed if the class turns out to have no features, // but we require it in the map for the next bit). final ApplicationFeature classFeature = newFeature(classFeatureId); classFeatures.put(classFeatureId, classFeature); // add members boolean addedMembers = false; for (final ObjectAssociation property : properties) { final Class<?> returnType = correspondingClassFor(property.getSpecification()); final Integer maxLength = returnType == String.class ? valueOf(property, MaxLengthFacet.class) : null; final Integer typicalLength = returnType == String.class ? valueOf(property, TypicalLengthFacet.class) : null; final boolean derived = !property.containsDoOpFacet(PropertySetterFacet.class); final boolean contributed = property instanceof ContributeeMember; addedMembers = newProperty(classFeatureId, property, returnType, contributed, maxLength, typicalLength, derived) || addedMembers; } for (final ObjectAssociation collection : collections) { final boolean derived = !(collection.containsDoOpFacet(CollectionAddToFacet.class) || collection.containsDoOpFacet(CollectionRemoveFromFacet.class)); final Class<?> elementType = correspondingClassFor(collection.getSpecification()); final boolean contributed = collection instanceof ContributeeMember; addedMembers = newCollection(classFeatureId, collection, elementType, contributed, derived) || addedMembers; } for (final ObjectAction action : actions) { final Class<?> returnType = correspondingClassFor(action.getReturnType()); final SemanticsOf actionSemantics = SemanticsOf.from(action.getSemantics()); final boolean contributed = action instanceof ContributeeMember; addedMembers = newAction(classFeatureId, action, returnType, contributed, actionSemantics) || addedMembers; } if (!addedMembers) { // remove this class feature, since it turned out to have no members classFeatures.remove(classFeatureId); return; } // leave the class as is and (as there were indeed members for this class) // and add all of its parent packages final ApplicationFeatureId classParentPackageId = addClassParent(classFeatureId); addParents(classParentPackageId); } private static Class<?> correspondingClassFor(final ObjectSpecification objectSpec) { return objectSpec != null ? objectSpec.getCorrespondingClass() : null; } private static Integer valueOf( final FacetHolder facetHolder, final Class<? extends SingleIntValueFacet> cls) { final SingleIntValueFacet facet = facetHolder.getFacet(cls); return facet != null ? facet.value() : null; } ApplicationFeatureId addClassParent(final ApplicationFeatureId classFeatureId) { final ApplicationFeatureId parentPackageId = classFeatureId.getParentPackageId(); final ApplicationFeature parentPackage = findPackageElseCreate(parentPackageId); parentPackage.addToContents(classFeatureId); return parentPackageId; } void addParents(final ApplicationFeatureId classOrPackageId) { final ApplicationFeatureId parentPackageId = classOrPackageId.getParentPackageId(); if (parentPackageId == null) { return; } final ApplicationFeature parentPackage = findPackageElseCreate(parentPackageId); // add this feature as part of the contents of its parent parentPackage.addToContents(classOrPackageId); // and recurse up addParents(parentPackageId); } private ApplicationFeature findPackageElseCreate(final ApplicationFeatureId parentPackageId) { ApplicationFeature parentPackage = findPackage(parentPackageId); if (parentPackage == null) { parentPackage = newPackage(parentPackageId); } return parentPackage; } private ApplicationFeature newPackage(final ApplicationFeatureId packageId) { final ApplicationFeature parentPackage = newFeature(packageId); packageFeatures.put(packageId, parentPackage); return parentPackage; } private boolean newProperty( final ApplicationFeatureId classFeatureId, final ObjectMember objectMember, final Class<?> returnType, final boolean contributed, final Integer maxLength, final Integer typicalLength, final boolean derived) { return newMember(classFeatureId, objectMember, ApplicationMemberType.PROPERTY, returnType, contributed, derived, maxLength, typicalLength, null); } private boolean newCollection( final ApplicationFeatureId classFeatureId, final ObjectMember objectMember, final Class<?> returnType, final boolean contributed, final boolean derived) { return newMember(classFeatureId, objectMember, ApplicationMemberType.COLLECTION, returnType, contributed, derived, null, null, null); } private boolean newAction( final ApplicationFeatureId classFeatureId, final ObjectMember objectMember, final Class<?> returnType, final boolean contributed, final SemanticsOf actionSemantics) { return newMember(classFeatureId, objectMember, ApplicationMemberType.ACTION, returnType, contributed, null, null, null, actionSemantics); } private boolean newMember( final ApplicationFeatureId classFeatureId, final ObjectMember objectMember, final ApplicationMemberType memberType, final Class<?> returnType, final boolean contributed, final Boolean derived, final Integer maxLength, final Integer typicalLength, final SemanticsOf actionSemantics) { if (objectMember.isAlwaysHidden()) { return false; } newMember(classFeatureId, objectMember.getId(), memberType, returnType, contributed, derived, maxLength, typicalLength, actionSemantics); return true; } private void newMember( final ApplicationFeatureId classFeatureId, final String memberId, final ApplicationMemberType memberType, final Class<?> returnType, final boolean contributed, final Boolean derived, final Integer maxLength, final Integer typicalLength, final SemanticsOf actionSemantics) { final ApplicationFeatureId featureId = ApplicationFeatureId.newMember(classFeatureId.getFullyQualifiedName(), memberId); final ApplicationFeature memberFeature = newFeature(featureId); memberFeature.setMemberType(memberType); memberFeature.setReturnTypeName(returnType != null ? returnType.getSimpleName() : null); memberFeature.setContributed(contributed); memberFeature.setDerived(derived); memberFeature.setPropertyMaxLength(maxLength); memberFeature.setPropertyTypicalLength(typicalLength); memberFeature.setActionSemantics(actionSemantics); memberFeatures.put(featureId, memberFeature); // also cache per memberType featuresMapFor(memberType).put(featureId, memberFeature); final ApplicationFeature classFeature = findClass(classFeatureId); classFeature.addToMembers(featureId, memberType); } private SortedMap<ApplicationFeatureId, ApplicationFeature> featuresMapFor(final ApplicationMemberType memberType) { switch (memberType) { case PROPERTY: return propertyFeatures; case COLLECTION: return collectionFeatures; default: // case ACTION: return actionFeatures; } } private ApplicationFeature newFeature(final ApplicationFeatureId featureId) { final ApplicationFeature feature = applicationFeatureFactory.newApplicationFeature(); feature.setFeatureId(featureId); return feature; } protected boolean exclude(final ObjectSpecification spec) { return spec.isAbstract() || isBuiltIn(spec) || isHidden(spec) || isFixtureScript(spec) || isSuperClassOfService(spec); } private boolean isFixtureScript(final ObjectSpecification spec) { return FixtureScript.class.isAssignableFrom(spec.getCorrespondingClass()); } /** * Ignore the (strict) superclasses of any services. * <p/> * <p> * For example, we want to ignore <code>ExceptionRecognizerComposite</code> because there is no service * of that type (only of subtypes of that). * </p> */ private boolean isSuperClassOfService(final ObjectSpecification spec) { final List<Object> registeredServices = serviceRegistry.getRegisteredServices(); final Class<?> specClass = spec.getCorrespondingClass(); // is this class a supertype or the actual type of one of the services? boolean serviceCls = false; for (final Object registeredService : registeredServices) { final Class<?> serviceClass = registeredService.getClass(); if (specClass.isAssignableFrom(serviceClass)) { serviceCls = true; } } if (!serviceCls) { return false; } // yes it is. In which case, is it the actual concrete class of one of those services? for (final Object registeredService : registeredServices) { final Class<?> serviceClass = registeredService.getClass(); if (serviceClass.isAssignableFrom(specClass)) { return false; } } // couldn't find a service of exactly this type, so ignore the spec. return true; } protected boolean isHidden(final ObjectSpecification spec) { final HiddenFacet facet = spec.getFacet(HiddenFacet.class); return facet != null && !facet.isNoop() && (facet.where() == Where.EVERYWHERE || facet.where() == Where.ANYWHERE) && facet.when() == When.ALWAYS; } protected boolean isBuiltIn(final ObjectSpecification spec) { final String className = spec.getFullIdentifier(); return className.startsWith("java") || className.startsWith("org.joda"); } //endregion //region > packageFeatures, classFeatures, memberFeatures @Programmatic public ApplicationFeature findFeature(final ApplicationFeatureId featureId) { initializeIfRequired(); switch (featureId.getType()) { case PACKAGE: return findPackage(featureId); case CLASS: return findClass(featureId); case MEMBER: return findMember(featureId); } throw new IllegalArgumentException("Feature has unknown feature type " + featureId.getType()); } @Programmatic public ApplicationFeature findPackage(final ApplicationFeatureId featureId) { initializeIfRequired(); return packageFeatures.get(featureId); } @Programmatic public ApplicationFeature findClass(final ApplicationFeatureId featureId) { initializeIfRequired(); return classFeatures.get(featureId); } @Programmatic public ApplicationFeature findMember(final ApplicationFeatureId featureId) { initializeIfRequired(); return memberFeatures.get(featureId); } //endregion //region > allFeatures, allPackages, allClasses, allMembers @Programmatic public Collection<ApplicationFeature> allFeatures(final ApplicationFeatureType featureType) { initializeIfRequired(); if (featureType == null) { return Collections.emptyList(); } switch (featureType) { case PACKAGE: return allPackages(); case CLASS: return allClasses(); case MEMBER: return allMembers(); } throw new IllegalArgumentException("Unknown feature type " + featureType); } @Programmatic public Collection<ApplicationFeature> allPackages() { initializeIfRequired(); return packageFeatures.values(); } @Programmatic public Collection<ApplicationFeature> allClasses() { initializeIfRequired(); return classFeatures.values(); } @Programmatic public Collection<ApplicationFeature> allMembers() { initializeIfRequired(); return memberFeatures.values(); } @Programmatic public Collection<ApplicationFeature> allProperties() { initializeIfRequired(); return propertyFeatures.values(); } @Programmatic public Collection<ApplicationFeature> allCollections() { initializeIfRequired(); return collectionFeatures.values(); } @Programmatic public Collection<ApplicationFeature> allActions() { initializeIfRequired(); return actionFeatures.values(); } //endregion //region > packageNames, packageNamesContainingClasses, classNamesContainedIn, memberNamesOf @Override @Programmatic public List<String> packageNames() { initializeIfRequired(); return Lists.newArrayList( Iterables.transform( allFeatures(ApplicationFeatureType.PACKAGE), ApplicationFeature.Functions.GET_FQN)); } @Override @Programmatic public List<String> packageNamesContainingClasses(final ApplicationMemberType memberType) { initializeIfRequired(); final Collection<ApplicationFeature> packages = allFeatures(ApplicationFeatureType.PACKAGE); return Lists.newArrayList( Iterables.transform( Iterables.filter( packages, ApplicationFeature.Predicates.packageContainingClasses(memberType, this) ), ApplicationFeature.Functions.GET_FQN)); } @Override @Programmatic public List<String> classNamesContainedIn(final String packageFqn, final ApplicationMemberType memberType) { initializeIfRequired(); final ApplicationFeatureId packageId = ApplicationFeatureId.newPackage(packageFqn); final ApplicationFeature pkg = findPackage(packageId); if (pkg == null) { return Collections.emptyList(); } final SortedSet<ApplicationFeatureId> contents = pkg.getContents(); return Lists.newArrayList( Iterables.transform( Iterables.filter( contents, ApplicationFeatureId.Predicates.isClassContaining(memberType, this)), ApplicationFeatureId.Functions.GET_CLASS_NAME)); } @Override @Programmatic public List<String> classNamesRecursivelyContainedIn(final String packageFqn) { initializeIfRequired(); final ApplicationFeatureId packageId = ApplicationFeatureId.newPackage(packageFqn); final ApplicationFeature pkg = findPackage(packageId); if (pkg == null) { return Collections.emptyList(); } final Set<ApplicationFeatureId> classIds = this.classFeatures.keySet(); return Lists.newArrayList( Iterables.transform( Iterables.filter( classIds, ApplicationFeatureId.Predicates.isClassRecursivelyWithin(packageId)), ApplicationFeatureId.Functions.GET_CLASS_NAME)); } @Override @Programmatic public List<String> memberNamesOf( final String packageFqn, final String className, final ApplicationMemberType memberType) { initializeIfRequired(); final ApplicationFeatureId classId = ApplicationFeatureId.newClass(packageFqn + "." + className); final ApplicationFeature cls = findClass(classId); if (cls == null) { return Collections.emptyList(); } final SortedSet<ApplicationFeatureId> featureIds = cls.membersOf(memberType); return Lists.newArrayList( Iterables.transform(featureIds, ApplicationFeatureId.Functions.GET_MEMBER_NAME) ); } //endregion //region > services (injected) @javax.inject.Inject ServiceRegistry2 serviceRegistry; @javax.inject.Inject DomainObjectContainer container; @javax.inject.Inject ConfigurationService configurationService; @javax.inject.Inject SpecificationLoader specificationLoader; @javax.inject.Inject ApplicationFeatureFactory applicationFeatureFactory; //endregion }