/*
* 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;
import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.commons.debug.DebugBuilder;
import org.apache.isis.core.commons.debug.DebuggableWithTitle;
import org.apache.isis.core.commons.ensure.Assert;
import org.apache.isis.core.commons.exceptions.IsisException;
import org.apache.isis.core.commons.lang.JavaClassUtils;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.adapter.ServicesProvider;
import org.apache.isis.core.metamodel.adapter.map.AdapterMap;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facetdecorator.FacetDecorator;
import org.apache.isis.core.metamodel.facetdecorator.FacetDecoratorSet;
import org.apache.isis.core.metamodel.facets.object.bounded.BoundedFacetUtils;
import org.apache.isis.core.metamodel.layout.MemberLayoutArranger;
import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
import org.apache.isis.core.metamodel.runtimecontext.DependencyInjector;
import org.apache.isis.core.metamodel.runtimecontext.RuntimeContext;
import org.apache.isis.core.metamodel.runtimecontext.RuntimeContextAware;
import org.apache.isis.core.metamodel.runtimecontext.noruntime.RuntimeContextNoRuntime;
import org.apache.isis.core.metamodel.spec.ObjectInstantiator;
import org.apache.isis.core.metamodel.spec.ObjectList;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.SpecificationContext;
import org.apache.isis.core.metamodel.spec.SpecificationLoader;
import org.apache.isis.core.metamodel.spec.SpecificationLoaderAware;
import org.apache.isis.core.metamodel.spec.SpecificationLookup;
import org.apache.isis.core.metamodel.spec.SpecificationLookupAware;
import org.apache.isis.core.metamodel.spec.feature.ObjectMemberContext;
import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
import org.apache.isis.core.metamodel.specloader.collectiontyperegistry.CollectionTypeRegistry;
import org.apache.isis.core.metamodel.specloader.collectiontyperegistry.CollectionTypeRegistryDefault;
import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor;
import org.apache.isis.core.metamodel.specloader.speccache.SpecificationCache;
import org.apache.isis.core.metamodel.specloader.speccache.SpecificationCacheDefault;
import org.apache.isis.core.metamodel.specloader.specimpl.CreateObjectContext;
import org.apache.isis.core.metamodel.specloader.specimpl.FacetedMethodsBuilderContext;
import org.apache.isis.core.metamodel.specloader.specimpl.IntrospectionContext;
import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
import org.apache.isis.core.metamodel.specloader.specimpl.objectlist.ObjectSpecificationForObjectList;
import org.apache.isis.core.metamodel.specloader.traverser.SpecificationTraverser;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidator;
import org.apache.log4j.Logger;
import com.google.common.collect.Lists;
/**
* Builds the meta-model.
*
* <p>
* The implementation provides for a degree of pluggability:
* <ul>
* <li>The most important plug-in point is {@link ProgrammingModel} that specifies the set of {@link Facet} that make up
* programming model. If not specified then defaults to {@link ProgrammingModelFacetsJava5} (which should be used as a
* starting point for your own customizations).
* <li>The only mandatory plug-in point is {@link ClassSubstitutor}, which allows the class to be loaded to be
* substituted if required. This is used in conjunction with some <tt>PersistenceMechanism</tt>s that do class
* enhancement.
* <li>The {@link CollectionTypeRegistry} specifies the types that should be considered as collections. If not specified
* then will {@link CollectionTypeRegistryDefault default}. (Note: this extension point has not been tested, so should
* be considered more of a "statement of intent" than actual API. Also, we may use annotations (similar to the
* way in which Values are specified) as an alternative mechanism).
* </ul>
*
* <p>
* In addition, the {@link RuntimeContext} can optionally be injected, but will default to
* {@link RuntimeContextNoRuntime} if not provided prior to {@link #init() initialization}. The purpose of
* {@link RuntimeContext} is to allow the metamodel to be used standalone, for example in a Maven plugin. The
* {@link RuntimeContextNoRuntime} implementation will through an exception for any methods (such as finding an
* {@link ObjectAdapter adapter}) because there is no runtime session. In the case of the metamodel being used by the
* framework (that is, when there <i>is</i> a runtime), then the framework injects an implementation of
* {@link RuntimeContext} that acts like a bridge to its <tt>IsisContext</tt>.
*/
public class ObjectReflectorDefault implements ObjectReflector, DebuggableWithTitle {
private final static Logger LOG = Logger.getLogger(ObjectReflectorDefault.class);
/**
* Injected in the constructor.
*/
private final IsisConfiguration configuration;
/**
* Injected in the constructor.
*/
private final ClassSubstitutor classSubstitutor;
/**
* Injected in the constructor.
*/
private final CollectionTypeRegistry collectionTypeRegistry;
/**
* Injected in the constructor.
*/
private final ProgrammingModel programmingModel;
/**
* Defaulted in the constructor.
*/
private final FacetProcessor facetProcessor;
/**
* Defaulted in the constructor, so can be added to via {@link #setFacetDecorators(FacetDecoratorSet)} or
* {@link #addFacetDecorator(FacetDecorator)}.
*
* <p>
* {@link FacetDecorator}s must be added prior to {@link #init() initialization.}
*/
private final FacetDecoratorSet facetDecoratorSet;
/**
* Can optionally be injected, but will default (to {@link RuntimeContextNoRuntime}) otherwise.
*
* <p>
* Should be injected when used by framework, but will default to a no-op implementation if the metamodel is being
* used standalone (eg for a code-generator).
*/
private RuntimeContext runtimeContext;
private final SpecificationTraverser specificationTraverser;
private final MemberLayoutArranger memberLayoutArranger;
/**
* Priming cache, optionally {@link #setServiceClasses(List) injected}.
*/
private List<Class<?>> serviceClasses = Lists.newArrayList();
private final MetaModelValidator metaModelValidator;
/**
* Defaulted in the constructor.
*/
private final SpecificationCache cache;
// /////////////////////////////////////////////////////////////
// Constructor
// /////////////////////////////////////////////////////////////
public ObjectReflectorDefault(final IsisConfiguration configuration, final ClassSubstitutor classSubstitutor,
final CollectionTypeRegistry collectionTypeRegistry, final SpecificationTraverser specificationTraverser,
final MemberLayoutArranger memberLayoutArranger, final ProgrammingModel programmingModel,
final Set<FacetDecorator> facetDecorators, final MetaModelValidator metaModelValidator) {
ensureThatArg(configuration, is(notNullValue()));
ensureThatArg(classSubstitutor, is(notNullValue()));
ensureThatArg(collectionTypeRegistry, is(notNullValue()));
ensureThatArg(specificationTraverser, is(notNullValue()));
ensureThatArg(memberLayoutArranger, is(notNullValue()));
ensureThatArg(programmingModel, is(notNullValue()));
ensureThatArg(facetDecorators, is(notNullValue()));
ensureThatArg(metaModelValidator, is(notNullValue()));
this.configuration = configuration;
this.classSubstitutor = classSubstitutor;
this.collectionTypeRegistry = collectionTypeRegistry;
this.programmingModel = programmingModel;
this.specificationTraverser = specificationTraverser;
this.memberLayoutArranger = memberLayoutArranger;
this.facetDecoratorSet = new FacetDecoratorSet();
for (final FacetDecorator facetDecorator : facetDecorators) {
this.facetDecoratorSet.add(facetDecorator);
}
this.metaModelValidator = metaModelValidator;
this.facetProcessor = new FacetProcessor(configuration, collectionTypeRegistry, programmingModel);
this.cache = new SpecificationCacheDefault();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
LOG.info("finalizing reflector factory " + this);
}
// /////////////////////////////////////////////////////////////
// init, shutdown
// /////////////////////////////////////////////////////////////
/**
* Initializes and wires up, and primes the cache based on any service classes that may have been
* {@link #setServiceClasses(List) injected}.
*/
@Override
public void init() {
if (LOG.isDebugEnabled()) {
LOG.debug("initialising " + this);
}
// default subcomponents
if (runtimeContext == null) {
runtimeContext = new RuntimeContextNoRuntime();
}
injectInto(runtimeContext);
injectInto(specificationTraverser);
injectInto(memberLayoutArranger);
injectInto(metaModelValidator);
// wire subcomponents into each other
runtimeContext.injectInto(facetProcessor);
// initialize subcomponents
facetDecoratorSet.init();
classSubstitutor.init();
collectionTypeRegistry.init();
specificationTraverser.init();
facetProcessor.init();
programmingModel.init();
metaModelValidator.init();
// prime cache and validate
primeCache();
metaModelValidator.validate();
}
/**
* load the service specifications and then, using the {@link #getSpecificationTraverser() traverser}, keep loading
* all referenced specifications until we can find no more.
*/
private void primeCache() {
for (final Class<?> serviceClass : serviceClasses) {
internalLoadSpecification(serviceClass);
}
loadAllSpecifications();
}
private void loadAllSpecifications() {
List<Class<?>> newlyDiscoveredClasses = newlyDiscoveredClasses();
while (newlyDiscoveredClasses.size() > 0) {
for (final Class<?> newClass : newlyDiscoveredClasses) {
internalLoadSpecification(newClass);
}
newlyDiscoveredClasses = newlyDiscoveredClasses();
}
}
private List<Class<?>> newlyDiscoveredClasses() {
final List<Class<?>> newlyDiscoveredClasses = new ArrayList<Class<?>>();
final Collection<ObjectSpecification> noSpecs = allSpecifications();
try {
for (final ObjectSpecification noSpec : noSpecs) {
getSpecificationTraverser().traverseReferencedClasses(noSpec, newlyDiscoveredClasses);
}
} catch (final ClassNotFoundException ex) {
throw new IsisException(ex);
}
return newlyDiscoveredClasses;
}
@Override
public void shutdown() {
LOG.info("shutting down " + this);
getCache().clear();
facetDecoratorSet.shutdown();
}
// /////////////////////////////////////////////////////////////
// install, load, allSpecifications
// /////////////////////////////////////////////////////////////
/**
* API: Return the specification for the specified class of object.
*/
@Override
public final ObjectSpecification loadSpecification(final String className) {
ensureThatArg(className, is(notNullValue()), "specification class name must be specified");
try {
final Class<?> cls = loadBuiltIn(className);
return internalLoadSpecification(cls);
} catch (final ClassNotFoundException e) {
final ObjectSpecification spec = getCache().get(className);
if (spec == null) {
throw new IsisException("No such class available: " + className);
}
return spec;
}
}
/**
* API: Return specification.
*/
@Override
public ObjectSpecification loadSpecification(final Class<?> type) {
return internalLoadSpecification(type);
}
private ObjectSpecification internalLoadSpecification(final Class<?> type) {
final Class<?> substitutedType = getClassSubstitutor().getClass(type);
return substitutedType != null ? loadSpecificationForSubstitutedClass(substitutedType) : null;
}
private ObjectSpecification loadSpecificationForSubstitutedClass(final Class<?> type) {
Assert.assertNotNull(type);
final String typeName = type.getName();
final SpecificationCache specificationCache = getCache();
synchronized (specificationCache) {
final ObjectSpecification spec = specificationCache.get(typeName);
if (spec != null) {
return spec;
}
final ObjectSpecification specification = createSpecification(type);
if (specification == null) {
throw new IsisException("Failed to create specification for class " + typeName);
}
// put into the cache prior to introspecting, to prevent
// infinite loops
specificationCache.cache(typeName, specification);
introspectSpecificationIfRequired(specification);
return specification;
}
}
/**
* Loads the specifications of the specified types except the one specified (to prevent an infinite loop).
*/
@Override
public boolean loadSpecifications(final List<Class<?>> typesToLoad, final Class<?> typeToIgnore) {
boolean anyLoadedAsNull = false;
for (final Class<?> typeToLoad : typesToLoad) {
if (typeToLoad != typeToIgnore) {
final ObjectSpecification noSpec = internalLoadSpecification(typeToLoad);
final boolean loadedAsNull = (noSpec == null);
anyLoadedAsNull = loadedAsNull || anyLoadedAsNull;
}
}
return anyLoadedAsNull;
}
/**
* Loads the specifications of the specified types.
*/
@Override
public boolean loadSpecifications(final List<Class<?>> typesToLoad) {
return loadSpecifications(typesToLoad, null);
}
/**
* Creates the appropriate type of {@link ObjectSpecification}.
*/
private ObjectSpecification createSpecification(final Class<?> cls) {
final AuthenticationSessionProvider authenticationSessionProvider =
getRuntimeContext().getAuthenticationSessionProvider();
final SpecificationLookup specificationLookup = getRuntimeContext().getSpecificationLookup();
final ServicesProvider servicesProvider = getRuntimeContext().getServicesProvider();
final ObjectInstantiator objectInstantiator = getRuntimeContext().getObjectInstantiator();
final SpecificationContext specContext =
new SpecificationContext(authenticationSessionProvider, servicesProvider, objectInstantiator,
specificationLookup);
if (ObjectList.class.isAssignableFrom(cls)) {
return new ObjectSpecificationForObjectList(specContext);
} else {
final SpecificationLoader specificationLoader = this;
final AdapterMap adapterMap = getRuntimeContext().getAdapterMap();
final ObjectMemberContext objectMemberContext =
new ObjectMemberContext(authenticationSessionProvider, specificationLookup, adapterMap,
getRuntimeContext().getQuerySubmitter());
final IntrospectionContext introspectionContext =
new IntrospectionContext(getClassSubstitutor(), getMemberLayoutArranger());
final DependencyInjector dependencyInjector = getRuntimeContext().getDependencyInjector();
final CreateObjectContext createObjectContext = new CreateObjectContext(adapterMap, dependencyInjector);
final FacetedMethodsBuilderContext facetedMethodsBuilderContext =
new FacetedMethodsBuilderContext(specificationLoader, classSubstitutor, specificationTraverser,
facetProcessor);
return new ObjectSpecificationDefault(cls, facetedMethodsBuilderContext, introspectionContext, specContext,
objectMemberContext, createObjectContext);
}
}
private Class<?> loadBuiltIn(final String className) throws ClassNotFoundException {
final Class<?> builtIn = JavaClassUtils.getBuiltIn(className);
if (builtIn != null) {
return builtIn;
}
return Class.forName(className);
}
/**
* Return all the loaded specifications.
*/
@Override
public Collection<ObjectSpecification> allSpecifications() {
return getCache().allSpecifications();
}
@Override
public boolean loaded(final Class<?> cls) {
return loaded(cls.getName());
}
@Override
public boolean loaded(final String fullyQualifiedClassName) {
return getCache().get(fullyQualifiedClassName) != null;
}
private ObjectSpecification introspectSpecificationIfRequired(final ObjectSpecification spec) {
if (!spec.isIntrospected()) {
spec.introspectTypeHierarchyAndMembers();
facetDecoratorSet.decorate(spec);
spec.updateFromFacetValues();
}
return spec;
}
// ////////////////////////////////////////////////////////////////////
// injectInto
// ////////////////////////////////////////////////////////////////////
/**
* Injects self into candidate if required, and instructs its subcomponents to do so also.
*/
@Override
public void injectInto(final Object candidate) {
final Class<?> candidateClass = candidate.getClass();
if (SpecificationLoaderAware.class.isAssignableFrom(candidateClass)) {
final SpecificationLoaderAware cast = SpecificationLoaderAware.class.cast(candidate);
cast.setSpecificationLoader(this);
}
if (SpecificationLookupAware.class.isAssignableFrom(candidateClass)) {
final SpecificationLookupAware cast = SpecificationLookupAware.class.cast(candidate);
cast.setSpecificationLookup(this);
}
getClassSubstitutor().injectInto(candidate);
getCollectionTypeRegistry().injectInto(candidate);
}
// /////////////////////////////////////////////////////////////
// Debugging
// /////////////////////////////////////////////////////////////
@Override
public void debugData(final DebugBuilder str) {
facetDecoratorSet.debugData(str);
str.appendln();
str.appendTitle("Specifications");
final List<ObjectSpecification> specs = Lists.newArrayList(allSpecifications());
Collections.sort(specs, ObjectSpecification.COMPARATOR_SHORT_IDENTIFIER_IGNORE_CASE);
for (final ObjectSpecification spec : specs) {
str.append(spec.isAbstract() ? "A" : ".");
str.append(spec.isService() ? "S" : ".");
str.append(BoundedFacetUtils.isBoundedSet(spec) ? "B" : ".");
str.append(spec.isCollection() ? "C" : ".");
str.append(spec.isNotCollection() ? "O" : ".");
str.append("."); // placeholder for future support of maps
str.append(spec.isParseable() ? "P" : ".");
str.append(spec.isEncodeable() ? "E" : ".");
str.append(spec.isValueOrIsAggregated() ? "A" : ".");
str.append(!spec.isCollectionOrIsAggregated() ? "I" : ".");
str.append(" ");
str.append(spec.getShortIdentifier());
str.append(" [fqc=");
str.append(spec.getFullIdentifier());
str.append(",type=");
str.append(spec.getClass().getName());
str.appendln("]");
}
}
@Override
public String debugTitle() {
return "Reflector";
}
// /////////////////////////////////////////////////////////////
// Helpers (were previously injected, but no longer required)
// /////////////////////////////////////////////////////////////
/**
* Provides access to the registered {@link Facet}s.
*/
public FacetProcessor getFacetProcessor() {
return facetProcessor;
}
private SpecificationCache getCache() {
return cache;
}
// ////////////////////////////////////////////////////////////////////
// Dependencies (injected by setter due to *Aware)
// ////////////////////////////////////////////////////////////////////
/**
* As per {@link #setRuntimeContext(RuntimeContext)}.
*/
public RuntimeContext getRuntimeContext() {
return runtimeContext;
}
/**
* Due to {@link RuntimeContextAware}.
*/
@Override
public void setRuntimeContext(final RuntimeContext runtimeContext) {
this.runtimeContext = runtimeContext;
}
// ////////////////////////////////////////////////////////////////////
// Dependencies (setters, optional)
// ////////////////////////////////////////////////////////////////////
public List<Class<?>> getServiceClasses() {
return Collections.unmodifiableList(serviceClasses);
}
@Override
public void setServiceClasses(final List<Class<?>> serviceClasses) {
this.serviceClasses = serviceClasses;
}
// ////////////////////////////////////////////////////////////////////
// Dependencies (injected from constructor)
// ////////////////////////////////////////////////////////////////////
protected IsisConfiguration getIsisConfiguration() {
return configuration;
}
protected CollectionTypeRegistry getCollectionTypeRegistry() {
return collectionTypeRegistry;
}
protected ClassSubstitutor getClassSubstitutor() {
return classSubstitutor;
}
protected SpecificationTraverser getSpecificationTraverser() {
return specificationTraverser;
}
protected ProgrammingModel getProgrammingModelFacets() {
return programmingModel;
}
protected MemberLayoutArranger getMemberLayoutArranger() {
return memberLayoutArranger;
}
protected MetaModelValidator getMetaModelValidator() {
return metaModelValidator;
}
protected Set<FacetDecorator> getFacetDecoratorSet() {
return facetDecoratorSet.getFacetDecorators();
}
}