/* * 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.applib; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLDecoder; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import javax.jdo.annotations.PersistenceCapable; import com.google.common.collect.Lists; import org.reflections.vfs.SystemDir; import org.reflections.vfs.Vfs; import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.fixturescripts.FixtureScript; /** * Programmatic specification of the constituent parts of an application, most specifically the modules that contain * domain services and possibly entities. * * <p> * The modules are specified as classes; the framework uses the packages of these classes as the locations on the * classpath to search for domain services (annotated with {@link org.apache.isis.applib.annotation.DomainService}) * and entities (annotated with {@link PersistenceCapable}). * </p> * * <p> * The interface also allows certain other aspects of the runtime to be specified: * <ul> * <li>which authentication and/or authorization mechanism to be used (useful to disable security when developing/demoing)</li> * <li>which fixtures to run by default</li> * <li>overriding of arbitrary other configuration properties</li> * <li>which modules to load (useful when developing, to reduce startup times; just load the modules required)</li> * </ul> * </p> * * <p> * To use, implement this interface and then define as the <tt>isis.appManifest</tt> key within the * <tt>isis.properties</tt> configuration file, or specify programmatically when running integration tests. * </p> * * <p> * By convention the class implementing this interface reside in a <tt>xxx-home</tt> Maven module. This * can be referenced by both the <tt>xxx-integtests</tt> (Maven) module and the <tt>xxx-webapp</tt> (Maven) module, * allowing configuration to be centralized. * </p> * * <p> * There are a number of subsidiary goals (not yet implemented): * <ul> * <li> * <p>Allow different integration tests to run with different manifests. Normally the running application is * shared (on a thread-local) between integration tests. What the framework could do is to be intelligent * enough to keep track of the manifest in use for each integration test and tear down * the shared state if the "next" test uses a different manifest</p> * </li> * <li> * Speed up bootstrapping by only scanning for classes annotated by * {@link org.apache.isis.applib.annotation.DomainService} and {@link javax.jdo.annotations.PersistenceCapable} * once. * </li> * <li> * Provide a programmatic way to contribute elements of `web.xml`. * </li> * <li> * Provide a programmatic way to configure Shiro security. * </li> * <li> * <p>Anticipate the module changes forthcoming in Java 9. Eventually we see that the AppManifest class acting * as an "aggregator", with the list of modules will become Java 9 modules each advertising the types that they * export. It might even be possible for AppManifests to be switched on and off dynamically (eg if Java9 is * compatible with OSGi, being one of the design goals).</p> * </li> * </ul> * </p> */ public interface AppManifest { /** * A list of classes, each of which representing the root of one of the modules containing services and possibly * entities, which together makes up the running application. * * <p> * The package of each such class is used as the basis for searching for domain services and registered * entities. As such it replaces and overrides both the * <tt>isis.services.ServicesInstallerFromAnnotation.packagePrefix</tt> key (usually found in the * <tt>isis.properties</tt> file) and the * <tt>isis.persistor.datanucleus.RegisterEntities.packagePrefix</tt> key (usually found in the * <tt>persistor_datanucleus.properties</tt> file). The value of the <tt>isis.services-installer</tt> * configuration property is also ignored. * </p> */ public List<Class<?>> getModules(); /** * If non-null, overrides the value of <tt>isis.services</tt> configuration property to specify a list of * additional classes to be instantiated as domain services (over and above the {@link DomainService}-annotated * services defined via {@link #getModules()}. * * <p> * Normally we recommend services are defined exclusively through {@link #getModules()}, and that this method * should therefore return an empty list. However, this method exists to support those use cases where either the * service required does not have a {@link DomainService} annotation, or where it does have the annotation * but its containing module cannot (for whatever reason) be listed under {@link #getModules()}. * </p> */ public List<Class<?>> getAdditionalServices(); /** * If non-null, overrides the value of <tt>isis.authentication</tt> configuration property to specify the * authentication mechanism. * * <p> * Ignored for integration tests (which always uses the 'bypass' mechanism). * </p> */ public String getAuthenticationMechanism(); /** * If non-null, overrides the value of <tt>isis.authorization</tt> configuration property. * * <p> * Ignored for integration tests (which always uses the 'bypass' mechanism). * </p> */ public String getAuthorizationMechanism(); /** * If non-null, overrides the value of <tt>isis.fixtures</tt> configuration property. */ public List<Class<? extends FixtureScript>> getFixtures(); /** * Overrides for any other configuration properties. */ public Map<String,String> getConfigurationProperties(); /** * Holds the set of domain services, persistent entities and fixture scripts.services */ public static class Registry { public final static List<String> FRAMEWORK_PROVIDED_SERVICES = Collections.unmodifiableList(Arrays.asList( "org.apache.isis.applib", "org.apache.isis.core.wrapper" , "org.apache.isis.core.metamodel.services" , "org.apache.isis.core.runtime.services" , "org.apache.isis.schema.services" , "org.apache.isis.objectstore.jdo.applib.service" , "org.apache.isis.viewer.restfulobjects.rendering.service" , "org.apache.isis.objectstore.jdo.datanucleus.service.support" , "org.apache.isis.objectstore.jdo.datanucleus.service.eventbus" , "org.apache.isis.viewer.wicket.viewer.services")); private static Registry instance = new Registry(); public static Registry instance() { return instance; } //region > persistenceCapableTypes private Set<Class<?>> persistenceCapableTypes; /** * @return <tt>null</tt> if no appManifest is defined */ public Set<Class<?>> getPersistenceCapableTypes() { return persistenceCapableTypes; } public void setPersistenceCapableTypes(final Set<Class<?>> persistenceCapableTypes) { this.persistenceCapableTypes = persistenceCapableTypes; } //endregion //region > mixinTypes private Set<Class<?>> mixinTypes; /** * @return <tt>null</tt> if no appManifest is defined */ public Set<Class<?>> getMixinTypes() { return mixinTypes; } public void setMixinTypes(final Set<Class<?>> mixinTypes) { this.mixinTypes = mixinTypes; } //endregion //region > fixtureScriptTypes private Set<Class<? extends FixtureScript>> fixtureScriptTypes; /** * @return <tt>null</tt> if no appManifest is defined */ public Set<Class<? extends FixtureScript>> getFixtureScriptTypes() { return fixtureScriptTypes; } public void setFixtureScriptTypes(final Set<Class<? extends FixtureScript>> fixtureScriptTypes) { this.fixtureScriptTypes = fixtureScriptTypes; } //endregion //region > domainServiceTypes private Set<Class<?>> domainServiceTypes; /** * @return <tt>null</tt> if no appManifest is defined */ public Set<Class<?>> getDomainServiceTypes() { return domainServiceTypes; } public void setDomainServiceTypes(final Set<Class<?>> domainServiceTypes) { this.domainServiceTypes = domainServiceTypes; } //endregion //region > urlTypes public List<Vfs.UrlType> getUrlTypes() { final List<Vfs.UrlType> urlTypes = Lists.newArrayList(); urlTypes.add(new EmptyIfFileEndingsUrlType(".pom", ".jnilib", "QTJava.zip")); urlTypes.add(new JettyConsoleUrlType()); urlTypes.addAll(Arrays.asList(Vfs.DefaultUrlTypes.values())); return urlTypes; } //endregion private static class EmptyIfFileEndingsUrlType implements Vfs.UrlType { private final List<String> fileEndings; private EmptyIfFileEndingsUrlType(final String... fileEndings) { this.fileEndings = Lists.newArrayList(fileEndings); } public boolean matches(URL url) { final String protocol = url.getProtocol(); final String externalForm = url.toExternalForm(); if (!protocol.equals("file")) { return false; } for (String fileEnding : fileEndings) { if (externalForm.endsWith(fileEnding)) return true; } return false; } public Vfs.Dir createDir(final URL url) throws Exception { return emptyVfsDir(url); } private static Vfs.Dir emptyVfsDir(final URL url) { return new Vfs.Dir() { @Override public String getPath() { return url.toExternalForm(); } @Override public Iterable<Vfs.File> getFiles() { return Collections.emptyList(); } @Override public void close() { // } }; } } private static class JettyConsoleUrlType implements Vfs.UrlType { public boolean matches(URL url) { final String protocol = url.getProtocol(); final String externalForm = url.toExternalForm(); final boolean matches = protocol.equals("file") && externalForm.contains("jetty-console") && externalForm.contains("-any-") && externalForm.endsWith("webapp/WEB-INF/classes/"); return matches; } public Vfs.Dir createDir(final URL url) throws Exception { return new SystemDir(getFile(url)); } /** * try to get {@link java.io.File} from url * * <p> * Copied from {@link Vfs} (not publicly accessible) * </p> */ static java.io.File getFile(URL url) { java.io.File file; String path; try { path = url.toURI().getSchemeSpecificPart(); if ((file = new java.io.File(path)).exists()) return file; } catch (URISyntaxException e) { } try { path = URLDecoder.decode(url.getPath(), "UTF-8"); if (path.contains(".jar!")) path = path.substring(0, path.lastIndexOf(".jar!") + ".jar".length()); if ((file = new java.io.File(path)).exists()) return file; } catch (UnsupportedEncodingException e) { } try { path = url.toExternalForm(); if (path.startsWith("jar:")) path = path.substring("jar:".length()); if (path.startsWith("file:")) path = path.substring("file:".length()); if (path.contains(".jar!")) path = path.substring(0, path.indexOf(".jar!") + ".jar".length()); if ((file = new java.io.File(path)).exists()) return file; path = path.replace("%20", " "); if ((file = new java.io.File(path)).exists()) return file; } catch (Exception e) { } return null; } } } public static class Util { public static final String ISIS_PERSISTOR = "isis.persistor."; public static final String ISIS_PERSISTOR_DATANUCLEUS = ISIS_PERSISTOR + "datanucleus."; public static final String ISIS_PERSISTOR_DATANUCLEUS_IMPL = ISIS_PERSISTOR_DATANUCLEUS + "impl."; public static Map<String,String> withJavaxJdoRunInMemoryProperties(final Map<String, String> map) { map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "javax.jdo.option.ConnectionURL", "jdbc:hsqldb:mem:test"); map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "javax.jdo.option.ConnectionDriverName", "org.hsqldb.jdbcDriver"); map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "javax.jdo.option.ConnectionUserName", "sa"); map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "javax.jdo.option.ConnectionPassword", ""); return map; } public static Map<String,String> withDataNucleusProperties(final Map<String, String> map) { // Don't do validations that consume setup time. map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "datanucleus.schema.autoCreateAll", "true"); map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "datanucleus.schema.validateAll", "false"); // other properties as per WEB-INF/persistor_datanucleus.properties map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "datanucleus.persistenceByReachabilityAtCommit", "false"); map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "datanucleus.identifier.case", "MixedCase"); map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "datanucleus.cache.level2.type" ,"none"); map.put(ISIS_PERSISTOR_DATANUCLEUS_IMPL + "datanucleus.cache.level2.mode", "ENABLE_SELECTIVE"); return map; } public static Map<String,String> withIsisIntegTestProperties(final Map<String, String> map) { // automatically install any fixtures that might have been registered map.put(ISIS_PERSISTOR_DATANUCLEUS + "install-fixtures", "true"); map.put(ISIS_PERSISTOR + "enforceSafeSemantics", "false"); map.put("isis.deploymentType", "server_prototype"); map.put("isis.services.eventbus.allowLateRegistration", "true"); return map; } } }