blob: 0aa02de4ad39c19d161adf93ef38db2cd04f1edd [file] [log] [blame]
/*******************************************************************************
* 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;
}
}