/* * 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.runtime.services; import java.lang.reflect.Modifier; import java.util.List; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import javax.annotation.PreDestroy; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.reflections.Reflections; import org.reflections.vfs.Vfs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.isis.applib.AppManifest; import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.annotation.DomainServiceLayout; import org.apache.isis.applib.services.classdiscovery.ClassDiscoveryServiceUsingReflections; import org.apache.isis.core.commons.config.IsisConfigurationDefault; import org.apache.isis.core.metamodel.util.DeweyOrderComparator; import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.not; public class ServicesInstallerFromAnnotation extends ServicesInstallerAbstract { //region > constants private static final Logger LOG = LoggerFactory.getLogger(ServicesInstallerFromAnnotation.class); public static final String NAME = "annotation"; public final static String PACKAGE_PREFIX_KEY = "isis.services.ServicesInstallerFromAnnotation.packagePrefix"; /** * These package prefixes (core and modules) are always included. * * <p> * It's important that any services annotated {@link org.apache.isis.applib.annotation.DomainService} and residing * in any of these packages must have no side-effects. * * <p> * Services are ordered according to the {@link org.apache.isis.applib.annotation.DomainService#menuOrder() menuOrder}, * with the first service found used. * </p> */ public final static String PACKAGE_PREFIX_STANDARD = Joiner.on(",").join(AppManifest.Registry.FRAMEWORK_PROVIDED_SERVICES); //endregion //region > constructor, fields private final ServiceInstantiator serviceInstantiator; public ServicesInstallerFromAnnotation(final IsisConfigurationDefault isisConfiguration) { this(new ServiceInstantiator(), isisConfiguration); } public ServicesInstallerFromAnnotation( final ServiceInstantiator serviceInstantiator, final IsisConfigurationDefault isisConfiguration) { super(NAME, isisConfiguration); this.serviceInstantiator = serviceInstantiator; } //endregion //region > packagePrefixes private String packagePrefixes; /** * For integration testing. * * <p> * Otherwise these are read from the {@link org.apache.isis.core.commons.config.IsisConfiguration} * </p> */ public void withPackagePrefixes(final String... packagePrefixes) { this.packagePrefixes = Joiner.on(",").join(packagePrefixes); } //endregion //region > init, shutdown public void init() { initIfRequired(); } private boolean initialized = false; protected void initIfRequired() { if(initialized) { return; } if(getConfiguration() == null) { throw new IllegalStateException("No IsisConfiguration injected - aborting"); } try { // lazily copy over the configuration to the instantiator serviceInstantiator.setConfiguration(getConfiguration()); if(packagePrefixes == null) { this.packagePrefixes = PACKAGE_PREFIX_STANDARD; String packagePrefixes = getConfiguration().getString(PACKAGE_PREFIX_KEY); if(!Strings.isNullOrEmpty(packagePrefixes)) { this.packagePrefixes = this.packagePrefixes + "," + packagePrefixes; } } } finally { initialized = true; } } @PreDestroy public void shutdown() { } //endregion //region > helpers private Predicate<Class<?>> instantiatable() { return and(not(nullClass()), not(abstractClass())); } private static Function<String,String> trim() { return new Function<String,String>(){ @Override public String apply(final String input) { return input.trim(); } }; } private static Predicate<Class<?>> nullClass() { return new Predicate<Class<?>>() { @Override public boolean apply(final Class<?> input) { return input == null; } }; } private static Predicate<Class<?>> abstractClass() { return new Predicate<Class<?>>() { @Override public boolean apply(final Class<?> input) { return Modifier.isAbstract(input.getModifiers()); } }; } //endregion //region > getServices (API) private List<Object> services; @Override public List<Object> getServices() { initIfRequired(); if(this.services == null) { final SortedMap<String, SortedSet<String>> positionedServices = Maps.newTreeMap(new DeweyOrderComparator()); appendServices(positionedServices); this.services = ServicesInstallerUtils.instantiateServicesFrom(positionedServices, serviceInstantiator); } return services; } //endregion //region > appendServices public void appendServices(final SortedMap<String, SortedSet<String>> positionedServices) { initIfRequired(); final List<String> packagePrefixList = asList(packagePrefixes); Set<Class<?>> domainServiceTypes = AppManifest.Registry.instance().getDomainServiceTypes(); if(domainServiceTypes == null) { // if no appManifest Vfs.setDefaultURLTypes(ClassDiscoveryServiceUsingReflections.getUrlTypes()); final Reflections reflections = new Reflections(packagePrefixList); domainServiceTypes = reflections.getTypesAnnotatedWith(DomainService.class); } final List<Class<?>> domainServiceClasses = Lists.newArrayList(Iterables.filter(domainServiceTypes, instantiatable())); for (final Class<?> cls : domainServiceClasses) { final String order = orderOf(cls); // we want the class name in order to instantiate it // (and *not* the value of the @DomainServiceLayout(named=...) annotation attribute) final String fullyQualifiedClassName = cls.getName(); final String name = nameOf(cls); ServicesInstallerUtils.appendInPosition(positionedServices, order, fullyQualifiedClassName); } } //endregion //region > helpers: orderOf, nameOf, asList private static String orderOf(final Class<?> cls) { final DomainServiceLayout domainServiceLayout = cls.getAnnotation(DomainServiceLayout.class); String order = domainServiceLayout != null ? domainServiceLayout.menuOrder(): null; if(order == null || order.equals("" + Integer.MAX_VALUE)) { final DomainService domainService = cls.getAnnotation(DomainService.class); order = domainService != null ? domainService.menuOrder() : "" + Integer.MAX_VALUE; } return order; } private static String nameOf(final Class<?> cls) { final DomainServiceLayout domainServiceLayout = cls.getAnnotation(DomainServiceLayout.class); String name = domainServiceLayout != null ? domainServiceLayout.named(): null; if(name == null) { name = cls.getName(); } return name; } private static List<String> asList(final String csv) { return Lists.newArrayList(Iterables.transform(Splitter.on(",").split(csv), trim())); } //endregion //region > domain events public static abstract class PropertyDomainEvent<T> extends org.apache.isis.applib.services.eventbus.PropertyDomainEvent<ServicesInstallerFromAnnotation, T> { } public static abstract class CollectionDomainEvent<T> extends org.apache.isis.applib.services.eventbus.CollectionDomainEvent<ServicesInstallerFromAnnotation, T> { } public static abstract class ActionDomainEvent extends org.apache.isis.applib.services.eventbus.ActionDomainEvent<ServicesInstallerFromAnnotation> { } //endregion //region > getTypes (API) @Override public List<Class<?>> getTypes() { return listOf(List.class); // ie List<Object.class>, of services } //endregion }