/******************************************************************************* * 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.ofbiz.base.container; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.ofbiz.base.component.ComponentConfig; import org.apache.ofbiz.base.component.ComponentException; import org.apache.ofbiz.base.component.ComponentLoaderConfig; import org.apache.ofbiz.base.start.Classpath; import org.apache.ofbiz.base.start.Start; import org.apache.ofbiz.base.start.StartupCommand; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.FileUtil; /** * ComponentContainer - StartupContainer implementation for Components * * The purpose of this container is to load the classpath for all components * defined in OFBiz. This container must run before any other containers to * allow components to access any necessary resources. Furthermore, the * ComponentContainer also builds up the <code>ComponentConfigCache</code> * defined in <code>ComponentConfig</code> to keep track of loaded components * */ public class ComponentContainer implements Container { public static final String module = ComponentContainer.class.getName(); private String name; private final AtomicBoolean loaded = new AtomicBoolean(false); private final List<Classpath> componentsClassPath = new ArrayList<Classpath>(); @Override public void init(List<StartupCommand> ofbizCommands, String name, String configFile) throws ContainerException { if (!loaded.compareAndSet(false, true)) { throw new ContainerException("Components already loaded, cannot start"); } this.name = name; // load the components from framework/base/config/component-load.xml (root components) try { for (ComponentLoaderConfig.ComponentDef def: ComponentLoaderConfig.getRootComponents()) { loadComponentFromConfig(Start.getInstance().getConfig().ofbizHome, def); } } catch (IOException | ComponentException e) { throw new ContainerException(e); } loadClassPathForAllComponents(componentsClassPath); Debug.logInfo("All components loaded", module); } /** * @see org.apache.ofbiz.base.container.Container#start() */ public boolean start() throws ContainerException { return loaded.get(); } /** * Iterate over all the components and load their classpath URLs into the classloader * and set the classloader as the context classloader * * @param componentsClassPath: a list of classpaths for all components * @throws ContainerException */ private void loadClassPathForAllComponents(List<Classpath> componentsClassPath) throws ContainerException { List<URL> allComponentUrls = new ArrayList<URL>(); for(Classpath classPath : componentsClassPath) { try { allComponentUrls.addAll(Arrays.asList(classPath.getUrls())); } catch (MalformedURLException e) { Debug.logError("Unable to load component classpath" + classPath.toString(), module); Debug.logError(e.getMessage(), module); } } URL[] componentURLs = allComponentUrls.toArray(new URL[allComponentUrls.size()]); URLClassLoader classLoader = new URLClassLoader(componentURLs, Thread.currentThread().getContextClassLoader()); Thread.currentThread().setContextClassLoader(classLoader); } /** * Checks if <code>ComponentDef.type</code> is a directory or a single component. * If it is a directory, load the directory, otherwise load a single component * * @param parentPath: the parent path of what is being loaded * @param def: the component or directory loader definition * @throws IOException */ private void loadComponentFromConfig(String parentPath, ComponentLoaderConfig.ComponentDef def) throws IOException { String location = def.location.startsWith("/") ? def.location : parentPath + "/" + def.location; if (def.type.equals(ComponentLoaderConfig.ComponentType.COMPONENT_DIRECTORY)) { loadComponentDirectory(location); } else if (def.type.equals(ComponentLoaderConfig.ComponentType.SINGLE_COMPONENT)) { ComponentConfig config = retrieveComponentConfig(def.name, location); if (config != null) { loadComponent(config); } } } /** * Checks to see if the directory contains a load file (component-load.xml) and * then delegates loading to the appropriate method * * @param directoryName: the name of component directory to load * @throws IOException */ private void loadComponentDirectory(String directoryName) throws IOException { Debug.logInfo("Auto-Loading component directory : [" + directoryName + "]", module); File directoryPath = FileUtil.getFile(directoryName); if (directoryPath.exists() && directoryPath.isDirectory()) { File componentLoadFile = new File(directoryPath, ComponentLoaderConfig.COMPONENT_LOAD_XML_FILENAME); if (componentLoadFile != null && componentLoadFile.exists()) { loadComponentsInDirectoryUsingLoadFile(directoryPath, componentLoadFile); } else { loadComponentsInDirectory(directoryPath); } } else { Debug.logError("Auto-Load Component directory not found : " + directoryName, module); } } /** * load components residing in a directory only if they exist in the component * load file (component-load.xml) and they are sorted in order from top to bottom * in the load file * * @param directoryPath: the absolute path of the directory * @param componentLoadFile: the name of the load file (i.e. component-load.xml) * @throws IOException */ private void loadComponentsInDirectoryUsingLoadFile(File directoryPath, File componentLoadFile) throws IOException { URL configUrl = null; try { configUrl = componentLoadFile.toURI().toURL(); List<ComponentLoaderConfig.ComponentDef> componentsToLoad = ComponentLoaderConfig.getComponentsFromConfig(configUrl); if (componentsToLoad != null) { for (ComponentLoaderConfig.ComponentDef def: componentsToLoad) { loadComponentFromConfig(directoryPath.toString(), def); } } } catch (MalformedURLException e) { Debug.logError(e, "Unable to locate URL for component loading file: " + componentLoadFile.getAbsolutePath(), module); } catch (ComponentException e) { Debug.logError(e, "Unable to load components from URL: " + configUrl.toExternalForm(), module); } } /** * Load all components in a directory because it does not contain * a load-components.xml file. The components are sorted alphabetically * for loading purposes * * @param directoryPath: the absolute path of the directory * @throws IOException */ private void loadComponentsInDirectory(File directoryPath) throws IOException { String[] sortedComponentNames = directoryPath.list(); Arrays.sort(sortedComponentNames); for (String componentName: sortedComponentNames) { File componentPath = FileUtil.getFile(directoryPath.getCanonicalPath() + File.separator + componentName); String componentLocation = componentPath.getCanonicalPath(); File configFile = FileUtil.getFile(componentLocation.concat(File.separator).concat(ComponentConfig.OFBIZ_COMPONENT_XML_FILENAME)); if (componentPath.isDirectory() && !componentName.startsWith(".") && configFile.exists()) { ComponentConfig config = retrieveComponentConfig(null, componentLocation); if (config != null) { loadComponent(config); } } } } /** * Fetch the <code>ComponentConfig</code> for a certain component * * @param name: component name * @param location: directory location of the component * @return The component configuration */ private ComponentConfig retrieveComponentConfig(String name, String location) { ComponentConfig config = null; try { config = ComponentConfig.getComponentConfig(name, location); } catch (ComponentException e) { Debug.logError("Cannot load component : " + name + " @ " + location + " : " + e.getMessage(), module); } if (config == null) { Debug.logError("Cannot load component : " + name + " @ " + location, module); } return config; } /** * Load a single component by adding all its classpath entries to * the list of classpaths to be loaded * * @param config: the component configuration * @throws IOException */ private void loadComponent(ComponentConfig config) throws IOException { if (config.enabled()) { Classpath classpath = buildClasspathFromComponentConfig(config); componentsClassPath.add(classpath); Debug.logInfo("Added class path for component : [" + config.getComponentName() + "]", module); } else { Debug.logInfo("Not loading component [" + config.getComponentName() + "] because it is disabled", module); } } /** * Construct a <code>Classpath</code> object for a certain component based * on its configuration defined in <code>ComponentConfig</code> * * @param config: the component configuration * @return the constructed classpath * @throws IOException */ private Classpath buildClasspathFromComponentConfig(ComponentConfig config) throws IOException { Classpath classPath = new Classpath(); String configRoot = config.getRootLocation().replace('\\', '/'); configRoot = configRoot.endsWith("/") ? configRoot : configRoot + "/"; List<ComponentConfig.ClasspathInfo> classpathInfos = config.getClasspathInfos(); for (ComponentConfig.ClasspathInfo cp: classpathInfos) { String location = cp.location.replace('\\', '/'); if (!"jar".equals(cp.type) && !"dir".equals(cp.type)) { Debug.logError("Classpath type '" + cp.type + "' is not supported; '" + location + "' not loaded", module); continue; } location = location.startsWith("/") ? location.substring(1) : location; String dirLoc = location.endsWith("/*") ? location.substring(0, location.length() - 2) : location; File path = FileUtil.getFile(configRoot + dirLoc); if (path.exists()) { classPath.addComponent(configRoot + location); if (path.isDirectory() && "dir".equals(cp.type)) { classPath.addFilesFromPath(path); } } else { Debug.logWarning("Location '" + configRoot + dirLoc + "' does not exist", module); } } return classPath; } /** * @see org.apache.ofbiz.base.container.Container#stop() */ public void stop() throws ContainerException { } public String getName() { return name; } }