/* * Copyright 2008-2017 the original author or authors. * * Licensed 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.codehaus.griffon.runtime.core; import griffon.core.ApplicationBootstrapper; import griffon.core.GriffonApplication; import griffon.core.artifact.GriffonService; import griffon.core.env.GriffonEnvironment; import griffon.core.injection.Binding; import griffon.core.injection.Injector; import griffon.core.injection.InjectorFactory; import griffon.core.injection.Key; import griffon.core.injection.Module; import griffon.util.GriffonClassUtils; import griffon.util.ServiceLoaderUtils; import org.codehaus.griffon.runtime.core.injection.AbstractModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import static griffon.core.GriffonExceptionHandler.sanitize; import static griffon.util.AnnotationUtils.sortByDependencies; import static griffon.util.ServiceLoaderUtils.load; import static java.util.Collections.unmodifiableCollection; import static java.util.Objects.requireNonNull; /** * @author Andres Almiray * @since 2.0.0 */ public abstract class AbstractApplicationBootstrapper implements ApplicationBootstrapper { private static final Logger LOG = LoggerFactory.getLogger(DefaultApplicationBootstrapper.class); private static final String INJECTOR = "injector"; private static final String GRIFFON_PATH = "META-INF/griffon"; private static final String PROPERTIES = ".properties"; protected final GriffonApplication application; public AbstractApplicationBootstrapper(@Nonnull GriffonApplication application) { this.application = requireNonNull(application, "Argument 'application' must not be null"); } @Override public void bootstrap() throws Exception { // 1 initialize environment settings LOG.info("Griffon {}", GriffonEnvironment.getGriffonVersion()); LOG.info("Build: {}", GriffonEnvironment.getBuildDateTime()); LOG.info("Revision: {}", GriffonEnvironment.getBuildRevision()); LOG.info("JVM: {}", GriffonEnvironment.getJvmVersion()); LOG.info("OS: {}", GriffonEnvironment.getOsVersion()); // 2 create bindings LOG.debug("Creating module bindings"); Iterable<Binding<?>> bindings = createBindings(); if (LOG.isTraceEnabled()) { for (Binding<?> binding : bindings) { LOG.trace(binding.toString()); } } // 3 create injector LOG.debug("Creating application injector"); createInjector(bindings); } @Override public void run() { application.initialize(); application.startup(); application.ready(); } @Nonnull protected Iterable<Binding<?>> createBindings() { Map<Key, Binding<?>> map = new LinkedHashMap<>(); List<Module> modules = new ArrayList<>(); createApplicationModule(modules); createArtifactsModule(modules); collectModuleBindings(modules); for (Module module : modules) { for (Binding<?> binding : module.getBindings()) { map.put(Key.of(binding), binding); } } return unmodifiableCollection(map.values()); } protected void createArtifactsModule(@Nonnull List<Module> modules) { final List<Class<?>> classes = new ArrayList<>(); load(getClass().getClassLoader(), GRIFFON_PATH, new ServiceLoaderUtils.PathFilter() { @Override public boolean accept(@Nonnull String path) { return !path.endsWith(PROPERTIES); } }, new ServiceLoaderUtils.ResourceProcessor() { @Override public void process(@Nonnull ClassLoader classLoader, @Nonnull String line) { line = line.trim(); try { classes.add(classLoader.loadClass(line)); } catch (ClassNotFoundException e) { LOG.warn("'" + line + "' could not be resolved as a Class"); } } }); modules.add(new AbstractModule() { @Override protected void doConfigure() { for (Class<?> clazz : classes) { if (GriffonService.class.isAssignableFrom(clazz)) { bind(clazz).asSingleton(); } else { bind(clazz); } } } }); } protected void createApplicationModule(@Nonnull List<Module> modules) { modules.add(new AbstractModule() { @Override protected void doConfigure() { bind(GriffonApplication.class) .toInstance(application); } }); } protected void collectModuleBindings(@Nonnull Collection<Module> modules) { List<Module> moduleInstances = loadModules(); moduleInstances.add(0, new DefaultApplicationModule()); Map<String, Module> sortedModules = sortModules(moduleInstances); for (Map.Entry<String, Module> entry : sortedModules.entrySet()) { LOG.debug("Loading module bindings from {}:{}", entry.getKey(), entry.getValue()); modules.add(entry.getValue()); } } @Nonnull protected Map<String, Module> sortModules(@Nonnull List<Module> moduleInstances) { return sortByDependencies(moduleInstances, "Module", "module"); } @Nonnull protected abstract List<Module> loadModules(); private void createInjector(@Nonnull Iterable<Binding<?>> bindings) throws Exception { ServiceLoader<InjectorFactory> serviceLoader = ServiceLoader.load(InjectorFactory.class); try { Iterator<InjectorFactory> iterator = serviceLoader.iterator(); InjectorFactory injectorFactory = iterator.next(); LOG.debug("Injector will be created by {}", injectorFactory); Injector<?> injector = injectorFactory.createInjector(application, bindings); GriffonClassUtils.setProperty(application, INJECTOR, injector); } catch (Exception e) { LOG.error("An error occurred while initializing the injector", sanitize(e)); throw e; } } }