/*
* 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.jackrabbit.core.config;
import org.apache.commons.io.FileUtils;
import org.apache.jackrabbit.core.cluster.ClusterNode;
import org.apache.jackrabbit.core.data.DataStore;
import org.apache.jackrabbit.core.data.DataStoreFactory;
import org.apache.jackrabbit.core.fs.FileSystem;
import org.apache.jackrabbit.core.fs.FileSystemException;
import org.apache.jackrabbit.core.fs.FileSystemFactory;
import org.apache.jackrabbit.core.journal.AbstractJournal;
import org.apache.jackrabbit.core.journal.Journal;
import org.apache.jackrabbit.core.journal.JournalException;
import org.apache.jackrabbit.core.journal.JournalFactory;
import org.apache.jackrabbit.core.query.QueryHandler;
import org.apache.jackrabbit.core.query.QueryHandlerContext;
import org.apache.jackrabbit.core.query.QueryHandlerFactory;
import org.apache.jackrabbit.core.state.DefaultISMLocking;
import org.apache.jackrabbit.core.state.ISMLocking;
import org.apache.jackrabbit.core.state.ISMLockingFactory;
import org.apache.jackrabbit.core.util.RepositoryLock;
import org.apache.jackrabbit.core.util.RepositoryLockMechanism;
import org.apache.jackrabbit.core.util.RepositoryLockMechanismFactory;
import org.apache.jackrabbit.core.util.db.ConnectionFactory;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import java.util.UUID;
import java.util.List;
import java.util.ArrayList;
import javax.jcr.RepositoryException;
/**
* Configuration parser. This class is used to parse the repository and
* workspace configuration files.
* <p>
* The following code sample outlines the usage of this class:
* <pre>
* Properties variables = ...; // parser variables
* RepositoryConfigurationParser parser =
* new RepositoryConfigurationParser(variables);
* RepositoryConfig rc = parser.parseRepositoryConfig(...);
* WorkspaceConfig wc = parser.parseWorkspaceConfig(...);
* </pre>
* <p>
* Note that the configuration objects returned by this parser are not
* initialized. The caller needs to initialize the configuration objects
* before using them.
*/
public class RepositoryConfigurationParser extends ConfigurationParser {
/** Name of the repository home directory parser variable. */
public static final String REPOSITORY_HOME_VARIABLE = "rep.home";
/** Name of the repository configuration file parser variable. */
public static final String REPOSITORY_CONF_VARIABLE = "rep.conf";
/** Name of the workspace home directory parser variable. */
public static final String WORKSPACE_HOME_VARIABLE = "wsp.home";
/** Name of the repository name parser variable. */
public static final String WORKSPACE_NAME_VARIABLE = "wsp.name";
/** Name of the security configuration element. */
public static final String SECURITY_ELEMENT = "Security";
/** Name of the security manager configuration element. */
public static final String SECURITY_MANAGER_ELEMENT = "SecurityManager";
/** Name of the access manager configuration element. */
public static final String ACCESS_MANAGER_ELEMENT = "AccessManager";
/** Name of the login module configuration element. */
public static final String LOGIN_MODULE_ELEMENT = "LoginModule";
/**
* Name of the optional WorkspaceAccessManager element defining which
* implementation of WorkspaceAccessManager to be used.
*/
private static final String WORKSPACE_ACCESS_ELEMENT = "WorkspaceAccessManager";
/**
* Name of the optional UserManagerConfig element that defines the
* configuration options for the user manager.
*/
private static final String USER_MANAGER_ELEMENT = "UserManager";
/** Name of the general workspace configuration element. */
public static final String WORKSPACES_ELEMENT = "Workspaces";
/** Name of the workspace configuration element. */
public static final String WORKSPACE_ELEMENT = "Workspace";
/** Name of the versioning configuration element. */
public static final String VERSIONING_ELEMENT = "Versioning";
/** Name of the file system configuration element. */
public static final String FILE_SYSTEM_ELEMENT = "FileSystem";
/** Name of the cluster configuration element. */
public static final String CLUSTER_ELEMENT = "Cluster";
/** Name of the data source configuration element. */
public static final String DATASOURCES_ELEMENT = "DataSources";
/** Name of the data source configuration element. */
public static final String DATASOURCE_ELEMENT = "DataSource";
/** Name of the journal configuration element. */
public static final String JOURNAL_ELEMENT = "Journal";
/** Name of the data store configuration element. */
public static final String DATA_STORE_ELEMENT = "DataStore";
/** Name of the repository lock mechanism configuration element. */
public static final String REPOSITORY_LOCK_MECHANISM_ELEMENT =
"RepositoryLockMechanism";
/** Name of the persistence manager configuration element. */
public static final String PERSISTENCE_MANAGER_ELEMENT =
"PersistenceManager";
/** Name of the search index configuration element. */
public static final String SEARCH_INDEX_ELEMENT = "SearchIndex";
/** Name of the ism locking configuration element. */
public static final String ISM_LOCKING_ELEMENT = "ISMLocking";
/** Name of the application name configuration attribute. */
public static final String APP_NAME_ATTRIBUTE = "appName";
/** Name of the workspace containing security data. */
public static final String WSP_NAME_ATTRIBUTE = "workspaceName";
/** Name of the root path configuration attribute. */
public static final String ROOT_PATH_ATTRIBUTE = "rootPath";
/** Name of the config root path configuration attribute. */
public static final String CONFIG_ROOT_PATH_ATTRIBUTE = "configRootPath";
/** Name of the maximum idle time configuration attribute. */
public static final String MAX_IDLE_TIME_ATTRIBUTE = "maxIdleTime";
/** Name of the default workspace configuration attribute. */
public static final String DEFAULT_WORKSPACE_ATTRIBUTE =
"defaultWorkspace";
/** Name of the id configuration attribute. */
public static final String ID_ATTRIBUTE = "id";
/** Name of the syncDelay configuration attribute. */
public static final String SYNC_DELAY_ATTRIBUTE = "syncDelay";
/** Name of the default search index implementation class. */
public static final String DEFAULT_QUERY_HANDLER =
"org.apache.jackrabbit.core.query.lucene.SearchIndex";
/** Name of the clustered configuration attribute. */
public static final String CLUSTERED_ATTRIBUTE = "clustered";
/** Default synchronization delay, in milliseconds. */
public static final String DEFAULT_SYNC_DELAY = "5000";
/** Name of the workspace specific security configuration element */
private static final String WSP_SECURITY_ELEMENT = "WorkspaceSecurity";
/**
* Name of the optional AccessControlProvider element defining which
* implementation of AccessControlProvider should be used.
*/
private static final String AC_PROVIDER_ELEMENT = "AccessControlProvider";
/**
* Optional configuration elements with the user manager configuration.
* @see org.apache.jackrabbit.core.security.user.action.AuthorizableAction
*/
private static final String AUTHORIZABLE_ACTION = "AuthorizableAction";
/**
* The repositories {@link ConnectionFactory}.
*/
protected final ConnectionFactory connectionFactory;
protected BeanFactory beanFactory = new SimpleBeanFactory();
protected BeanConfigVisitor configVisitor = new NoOpConfigVisitor();
/**
* Element specifying the class of principals used to retrieve the userID
* in the 'class' attribute.
*/
private static final String USERID_CLASS_ELEMENT = "UserIdClass";
/**
* Name of the optional XmlImport config entry inside the workspace configuration.
*/
private static final String IMPORT_ELEMENT = "Import";
private static final String IMPORT_PII_ELEMENT = "ProtectedItemImporter";
private static final String IMPORT_PNI_ELEMENT = "ProtectedNodeImporter";
private static final String IMPORT_PPI_ELEMENT = "ProtectedPropertyImporter";
/**
* Name of the cluster node id file.
*/
private static final String CLUSTER_NODE_ID_FILE = "cluster_node.id";
/**
* Creates a new configuration parser with the given parser variables
* and connection factory.
*
* @param variables parser variables
* @param connectionFactory connection factory
*/
protected RepositoryConfigurationParser(
Properties variables, ConnectionFactory connectionFactory) {
super(variables);
this.connectionFactory = connectionFactory;
}
/**
* Creates a new configuration parser with the given parser variables.
*
* @param variables parser variables
*/
public RepositoryConfigurationParser(Properties variables) {
this(variables, new ConnectionFactory());
}
/**
* Parses repository configuration. Repository configuration uses the
* following format:
* <pre>
* <Repository>
* <FileSystem ...>
* <Security appName="...">
* <SecurityManager ...>
* <AccessManager ...>
* <LoginModule ... (optional)>
* </Security>
* <Workspaces rootPath="..." defaultWorkspace="..."/>
* <Workspace ...>
* <Versioning ...>
* </Repository>
* </pre>
* <p>
* The <code>FileSystem</code> element is a
* {@link #parseBeanConfig(Element,String) bean configuration} element,
* that specifies the file system implementation for storing global
* repository information. The <code>Security</code> element contains
* an <code>AccessManager</code> bean configuration element and the
* JAAS name of the repository application. The <code>Workspaces</code>
* element contains general workspace parameters, and the
* <code>Workspace</code> element is a template for the individual
* workspace configuration files. The <code>Versioning</code> element
* contains
* {@link #parseVersioningConfig(Element) versioning configuration} for
* the repository.
* <p>
* In addition to the configured information, the returned repository
* configuration object also contains the repository home directory path
* that is given as the ${rep.home} parser variable. Note that the
* variable <em>must</em> be available for the configuration document to
* be correctly parsed.
* <p>
* {@link #replaceVariables(String) Variable replacement} is performed
* on the security application name attribute, the general workspace
* configuration attributes, and on the file system, access manager,
* and versioning configuration information.
* <p>
* Note that the returned repository configuration object has not been
* initialized.
*
* @param xml repository configuration document
* @return repository configuration
* @throws ConfigurationException if the configuration is broken
* @see #parseBeanConfig(Element, String)
* @see #parseVersioningConfig(Element)
*/
public RepositoryConfig parseRepositoryConfig(InputSource xml)
throws ConfigurationException {
Element root = parseXML(xml, true);
// Repository home directory
String home = getVariables().getProperty(REPOSITORY_HOME_VARIABLE);
// File system implementation
FileSystemFactory fsf = getFileSystemFactory(root, FILE_SYSTEM_ELEMENT);
// Security configuration and access manager implementation
Element security = getElement(root, SECURITY_ELEMENT);
SecurityConfig securityConfig = parseSecurityConfig(security);
// General workspace configuration
Element workspaces = getElement(root, WORKSPACES_ELEMENT);
String workspaceDirectory = replaceVariables(
getAttribute(workspaces, ROOT_PATH_ATTRIBUTE));
String workspaceConfigDirectory =
getAttribute(workspaces, CONFIG_ROOT_PATH_ATTRIBUTE, null);
String defaultWorkspace = replaceVariables(
getAttribute(workspaces, DEFAULT_WORKSPACE_ATTRIBUTE));
int maxIdleTime = Integer.parseInt(
getAttribute(workspaces, MAX_IDLE_TIME_ATTRIBUTE, "0"));
// Workspace configuration template
Element template = getElement(root, WORKSPACE_ELEMENT);
// Versioning configuration
VersioningConfig vc = parseVersioningConfig(root);
// Query handler implementation
QueryHandlerFactory qhf = getQueryHandlerFactory(root);
// Optional journal configuration
ClusterConfig cc = parseClusterConfig(root, new File(home));
// Optional data store factory
DataStoreFactory dsf = getDataStoreFactory(root, home);
RepositoryLockMechanismFactory rlf = getRepositoryLockMechanismFactory(root);
// Optional data source configuration
DataSourceConfig dsc = parseDataSourceConfig(root);
return new RepositoryConfig(home, securityConfig, fsf,
workspaceDirectory, workspaceConfigDirectory, defaultWorkspace,
maxIdleTime, template, vc, qhf, cc, dsf, rlf, dsc, connectionFactory, this);
}
/**
* {@inheritDoc}
*/
@Override
protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException {
BeanConfig cfg = super.parseBeanConfig(parent, name);
cfg.setConnectionFactory(connectionFactory);
cfg.setInstanceFactory(beanFactory);
configVisitor.visit(cfg);
return cfg;
}
/**
* {@inheritDoc}
*/
@Override
protected BeanConfig parseBeanConfig(Element element) throws ConfigurationException {
BeanConfig cfg = super.parseBeanConfig(element);
cfg.setConnectionFactory(connectionFactory);
cfg.setInstanceFactory(beanFactory);
configVisitor.visit(cfg);
return cfg;
}
/**
* Parses security configuration. Security configuration
* uses the following format:
* <pre>
* <Security appName="...">
* <SecurityManager ...>
* <AccessManager ...>
* <LoginModule ... (optional)>
* </Security>
* </pre>
* <p/>
* The <code>SecurityManager</code>, the <code>AccessManager</code>
* and <code>LoginModule</code> are all
* {@link #parseBeanConfig(Element,String) bean configuration}
* elements.
* <p/>
* The login module is an optional feature of repository configuration.
*
* @param security the <security> element.
* @return the security configuration.
* @throws ConfigurationException if the configuration is broken
*/
public SecurityConfig parseSecurityConfig(Element security)
throws ConfigurationException {
String appName = getAttribute(security, APP_NAME_ATTRIBUTE);
SecurityManagerConfig smc = parseSecurityManagerConfig(security);
AccessManagerConfig amc = parseAccessManagerConfig(security);
LoginModuleConfig lmc = parseLoginModuleConfig(security);
return new SecurityConfig(appName, smc, amc, lmc);
}
/**
* Parses the security manager configuration.
*
* @param security the <security> element.
* @return the security manager configuration or <code>null</code>.
* @throws ConfigurationException if the configuration is broken
*/
public SecurityManagerConfig parseSecurityManagerConfig(Element security)
throws ConfigurationException {
// Optional security manager config entry
Element smElement = getElement(security, SECURITY_MANAGER_ELEMENT, false);
if (smElement != null) {
BeanConfig bc = parseBeanConfig(smElement);
String wspAttr = getAttribute(smElement, WSP_NAME_ATTRIBUTE, null);
BeanConfig wac = null;
Element element = getElement(smElement, WORKSPACE_ACCESS_ELEMENT, false);
if (element != null) {
wac = parseBeanConfig(smElement, WORKSPACE_ACCESS_ELEMENT);
}
UserManagerConfig umc = null;
element = getElement(smElement, USER_MANAGER_ELEMENT, false);
if (element != null) {
Element[] acElements = getElements(element, AUTHORIZABLE_ACTION, false);
BeanConfig[] aaConfig = new BeanConfig[acElements.length];
for (int i = 0; i < acElements.length; i++) {
aaConfig[i] = parseBeanConfig(acElements[i]);
}
umc = new UserManagerConfig(parseBeanConfig(element), aaConfig);
}
BeanConfig uidcc = null;
element = getElement(smElement, USERID_CLASS_ELEMENT, false);
if (element != null) {
uidcc = parseBeanConfig(element);
}
return new SecurityManagerConfig(bc, wspAttr, wac, umc, uidcc);
} else {
return null;
}
}
/**
* Parses the access manager configuration.
*
* @param security the <security> element.
* @return the access manager configuration or <code>null</code>.
* @throws ConfigurationException if the configuration is broken
*/
public AccessManagerConfig parseAccessManagerConfig(Element security)
throws ConfigurationException {
// Optional access manager config entry
Element accessMgr = getElement(security, ACCESS_MANAGER_ELEMENT, false);
if (accessMgr != null) {
return new AccessManagerConfig(parseBeanConfig(accessMgr));
} else {
return null;
}
}
/**
* Parses the login module configuration.
*
* @param security the <security> element.
* @return the login module configuration or <code>null</code>.
* @throws ConfigurationException if the configuration is broken
*/
public LoginModuleConfig parseLoginModuleConfig(Element security)
throws ConfigurationException {
// Optional login module
Element loginModule = getElement(security, LOGIN_MODULE_ELEMENT, false);
if (loginModule != null) {
return new LoginModuleConfig(parseBeanConfig(security, LOGIN_MODULE_ELEMENT));
} else {
return null;
}
}
/**
* Parses workspace configuration. Workspace configuration uses the
* following format:
* <pre>
* <Workspace name="...">
* <FileSystem ...>
* <PersistenceManager ...>
* <SearchIndex ...>
* <ISMLocking ...>
* <WorkspaceSecurity ...>
* <ISMLocking ...>
* </Workspace>
* </pre>
* <p>
* All the child elements (<code>FileSystem</code>,
* <code>PersistenceManager</code>, and <code>SearchIndex</code>) are
* {@link #parseBeanConfig(Element,String) bean configuration} elements.
* In addition to bean configuration, the
* {@link #parseSearchConfig(Element) search element} also contains
* configuration for the search file system.
* <p>
* In addition to the configured information, the returned workspace
* configuration object also contains the workspace home directory path
* that is given as the ${wsp.home} parser variable. Note that the
* variable <em>must</em> be available for the configuration document to
* be correctly parsed.
* <p>
* Variable replacement is performed on the optional workspace name
* attribute. If the name is not given, then the name of the workspace
* home directory is used as the workspace name. Once the name has been
* determined, it will be added as the ${wsp.name} variable in a temporary
* configuration parser that is used to parse the contained configuration
* elements.
* <p>
* The search index configuration element is optional. If it is not given,
* then the workspace will not have search capabilities.
* <p>
* The ism locking configuration element is optional. If it is not given,
* then a default implementation is used.
* <p>
* Note that the returned workspace configuration object has not been
* initialized.
*
* @param xml workspace configuration document
* @return workspace configuration
* @throws ConfigurationException if the configuration is broken
* @see #parseBeanConfig(Element, String)
* @see #parseWorkspaceSecurityConfig(Element)
*/
public WorkspaceConfig parseWorkspaceConfig(InputSource xml)
throws ConfigurationException {
Element root = parseXML(xml);
return parseWorkspaceConfig(root);
}
/**
* Parse workspace config.
*
* @param root root element of the workspace configuration
* @return The workspace configuration
* @throws ConfigurationException
* @see #parseWorkspaceConfig(InputSource)
*/
protected WorkspaceConfig parseWorkspaceConfig(Element root)
throws ConfigurationException {
// Workspace home directory
String home = getVariables().getProperty(WORKSPACE_HOME_VARIABLE);
// Workspace name
String name = getAttribute(root, "name", new File(home).getName());
// Clustered attribute
boolean clustered = Boolean.valueOf(
getAttribute(root, CLUSTERED_ATTRIBUTE, "true"));
// Create a temporary parser that contains the ${wsp.name} variable
Properties tmpVariables = (Properties) getVariables().clone();
tmpVariables.put(WORKSPACE_NAME_VARIABLE, name);
RepositoryConfigurationParser tmpParser = createSubParser(tmpVariables);
// File system implementation
FileSystemFactory fsf =
tmpParser.getFileSystemFactory(root, FILE_SYSTEM_ELEMENT);
// Persistence manager implementation
PersistenceManagerConfig pmc = tmpParser.parsePersistenceManagerConfig(root);
// Query handler implementation
QueryHandlerFactory qhf = tmpParser.getQueryHandlerFactory(root);
// Item state manager locking configuration (optional)
ISMLockingFactory ismLockingFactory =
tmpParser.getISMLockingFactory(root);
// workspace specific security configuration
WorkspaceSecurityConfig workspaceSecurityConfig = tmpParser.parseWorkspaceSecurityConfig(root);
// optional config for import handling
ImportConfig importConfig = tmpParser.parseImportConfig(root);
// default lock timeout
String to = getAttribute(root, "defaultLockTimeout", new Long(Long.MAX_VALUE).toString());
long defaultLockTimeout;
try {
defaultLockTimeout = Long.parseLong(to);
}
catch (NumberFormatException ex) {
throw new ConfigurationException("defaultLockTimeout must be an integer value", ex);
}
return new WorkspaceConfig(
home, name, clustered, fsf, pmc, qhf,
ismLockingFactory, workspaceSecurityConfig, importConfig, defaultLockTimeout);
}
/**
* Parses search index configuration. Search index configuration
* uses the following format:
* <pre>
* <SearchIndex class="...">
* <param name="..." value="...">
* ...
* <FileSystem ...>
* </Search>
* </pre>
* <p/>
* Both the <code>SearchIndex</code> and <code>FileSystem</code>
* elements are {@link #parseBeanConfig(Element,String) bean configuration}
* elements. If the search implementation class is not given, then
* a default implementation is used.
* <p/>
* The search index is an optional feature of workspace configuration.
* If the search configuration element is not found, then this method
* returns <code>null</code>.
* <p/>
* The FileSystem element in a search index configuration is optional.
* However some implementations may require a FileSystem.
*
* @param parent parent of the <code>SearchIndex</code> element
* @return query handler factory
*/
protected QueryHandlerFactory getQueryHandlerFactory(final Element parent) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
final Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE
&& SEARCH_INDEX_ELEMENT.equals(child.getNodeName())) {
return new QueryHandlerFactory() {
public QueryHandler getQueryHandler(QueryHandlerContext context)
throws RepositoryException {
Element element = (Element) child;
// Optional file system implementation
FileSystem fs = null;
if (getElement(element, FILE_SYSTEM_ELEMENT, false) != null) {
fs = getFileSystemFactory(
element, FILE_SYSTEM_ELEMENT).getFileSystem();
}
// Search implementation class
String className = getAttribute(
element, CLASS_ATTRIBUTE, DEFAULT_QUERY_HANDLER);
BeanConfig config = new BeanConfig(
className, parseParameters(element));
QueryHandler handler =
config.newInstance(QueryHandler.class);
try {
handler.init(fs, context);
return handler;
} catch (IOException e) {
throw new RepositoryException(
"Unable to initialize query handler: " + handler, e);
}
}
};
}
}
return null;
}
/**
* Read the optional WorkspaceSecurity Element of Workspace's configuration.
* It uses the following format:
* <pre>
* <WorkspaceSecurity>
* <AccessControlProvider class="..." (optional)>
* </WorkspaceSecurity>
* </pre>
*
* @param parent Workspace-Root-Element
* @return a new <code>WorkspaceSecurityConfig</code> or <code>null</code>
* if none is configured.
* @throws ConfigurationException
*/
public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent)
throws ConfigurationException {
BeanConfig acProviderConfig = null;
Element element = getElement(parent, WSP_SECURITY_ELEMENT, false);
if (element != null) {
Element provFact = getElement(element, AC_PROVIDER_ELEMENT, false);
if (provFact != null) {
acProviderConfig = parseBeanConfig(element, AC_PROVIDER_ELEMENT);
acProviderConfig.setValidate(false); // JCR-1920
}
return new WorkspaceSecurityConfig(acProviderConfig);
}
return null;
}
/**
* Read the optional Import Element of Workspace's configuration. It uses
* the following format:
* <pre>
* <Import>
* <ProtectedNodeImporter class="..." (optional)>
* <ProtectedNodeImporter class="..." (optional)>
* ...
* <ProtectedPropertyImporter class="..." (optional)>
* </Import>
* </pre>
*
* @param parent Workspace-Root-Element
* @return a new <code>ImportConfig</code> or <code>null</code> if none is
* configured.
* @throws ConfigurationException
*/
public ImportConfig parseImportConfig(Element parent) throws ConfigurationException {
List<BeanConfig> protectedItemImporters = new ArrayList<BeanConfig>();
Element element = getElement(parent, IMPORT_ELEMENT, false);
if (element != null) {
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
if (IMPORT_PNI_ELEMENT.equals(child.getNodeName()) ||
IMPORT_PPI_ELEMENT.equals(child.getNodeName()) ||
IMPORT_PII_ELEMENT.equals(child.getNodeName())) {
BeanConfig bc = parseBeanConfig((Element) child);
bc.setValidate(false);
protectedItemImporters.add(bc);
} // else: some other entry -> ignore.
}
}
return new ImportConfig(protectedItemImporters);
}
return null;
}
/**
* Returns an ISM locking factory that creates {@link ISMLocking} instances
* based on the given configuration. ISM locking configuration uses the
* following format:
* <pre>
* <ISMLocking class="...">
* <param name="..." value="...">
* ...
* </ISMLocking>
* </pre>
* <p>
* The <code>ISMLocking</code> is a
* {@link #parseBeanConfig(Element,String) bean configuration} element.
* <p>
* ISM locking is an optional part of the workspace configuration. If
* the ISM locking element is not found, then the returned factory will
* create instances of the {@link DefaultISMLocking} class.
*
* @param parent parent of the <code>ISMLocking</code> element
* @return ISM locking factory
*/
protected ISMLockingFactory getISMLockingFactory(final Element parent) {
return new ISMLockingFactory() {
public ISMLocking getISMLocking() throws RepositoryException {
Element element = getElement(parent, ISM_LOCKING_ELEMENT, false);
if (element != null) {
return parseBeanConfig(element).newInstance(ISMLocking.class);
} else {
return new DefaultISMLocking();
}
}
};
}
/**
* Parses versioning configuration. Versioning configuration uses the
* following format:
* <pre>
* <Versioning rootPath="...">
* <FileSystem ...>
* <PersistenceManager ...>
* </Versioning>
* </pre>
* <p>
* Both the <code>FileSystem</code> and <code>PersistenceManager</code>
* elements are {@link #parseBeanConfig(Element,String) bean configuration}
* elements. In addition to the bean parameter values,
* {@link #replaceVariables(String) variable replacement} is performed
* also on the versioning root path attribute.
*
* @param parent parent of the <code>Versioning</code> element
* @return versioning configuration
* @throws ConfigurationException if the configuration is broken
*/
protected VersioningConfig parseVersioningConfig(Element parent)
throws ConfigurationException {
Element element = getElement(parent, VERSIONING_ELEMENT);
// Versioning home directory
String home =
replaceVariables(getAttribute(element, ROOT_PATH_ATTRIBUTE));
// File system implementation
FileSystemFactory fsf =
getFileSystemFactory(element, FILE_SYSTEM_ELEMENT);
// Persistence manager implementation
PersistenceManagerConfig pmc = parsePersistenceManagerConfig(element);
// Item state manager locking configuration (optional)
ISMLockingFactory ismLockingFactory =
getISMLockingFactory(element);
return new VersioningConfig(home, fsf, pmc, ismLockingFactory);
}
/**
* Parses cluster configuration. Cluster configuration uses the following format:
* <pre>
* <Cluster>
* <Journal ...>
* </Journal>
* </pre>
* <p/>
* <code>Cluster</code> is a {@link #parseBeanConfig(Element,String) bean configuration}
* element.
* <p/>
* Clustering is an optional feature. If the cluster element is not found, then this
* method returns <code>null</code>.
*
* @param parent parent of the <code>Journal</code> element
* @param home repository home directory
* @return cluster configuration, or <code>null</code>
* @throws ConfigurationException if the configuration is broken
*/
protected ClusterConfig parseClusterConfig(Element parent, File home)
throws ConfigurationException {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE
&& CLUSTER_ELEMENT.equals(child.getNodeName())) {
Element element = (Element) child;
// Find the cluster node id
String id =
System.getProperty(ClusterNode.SYSTEM_PROPERTY_NODE_ID);
String value = getAttribute(element, ID_ATTRIBUTE, null);
if (value != null) {
id = replaceVariables(value);
} else if (id == null) {
File file = new File(home, CLUSTER_NODE_ID_FILE);
try {
if (file.exists() && file.canRead()) {
id = FileUtils.readFileToString(file).trim();
} else {
id = UUID.randomUUID().toString();
FileUtils.writeStringToFile(file, id);
}
} catch (IOException e) {
throw new ConfigurationException(
"Failed to access cluster node id: " + file, e);
}
}
long syncDelay = Long.parseLong(replaceVariables(getAttribute(
element, SYNC_DELAY_ATTRIBUTE, DEFAULT_SYNC_DELAY)));
JournalFactory jf = getJournalFactory(element, home, id);
return new ClusterConfig(id, syncDelay, jf);
}
}
return null;
}
/**
* Parses journal configuration. Journal configuration uses the following format:
* <pre>
* <Journal class="...">
* <param name="..." value="...">
* ...
* </Journal>
* </pre>
* <p/>
* <code>Journal</code> is a {@link #parseBeanConfig(Element,String) bean configuration}
* element.
*
* @param cluster parent cluster element
* @param home repository home directory
* @param id cluster node id
* @return journal factory
* @throws ConfigurationException if the configuration is broken
*/
protected JournalFactory getJournalFactory(
final Element cluster, final File home, final String id)
throws ConfigurationException {
return new JournalFactory() {
public Journal getJournal(NamespaceResolver resolver)
throws RepositoryException {
BeanConfig config = parseBeanConfig(cluster, JOURNAL_ELEMENT);
Journal journal = config.newInstance(Journal.class);
if (journal instanceof AbstractJournal) {
((AbstractJournal) journal).setRepositoryHome(home);
}
try {
journal.init(id, resolver);
} catch (JournalException e) {
// TODO: Should JournalException extend RepositoryException?
throw new RepositoryException(
"Journal initialization failed: " + journal, e);
}
return journal;
}
};
}
/**
* Parses the DataSources configuration under the given parent. It has the following format:
* <pre>
* <DataSources>
* <DataSource name="...">
* <param name="..." value="...">
* ...
* </DataSource>
* <DataSource name="...">
* <param name="..." value="...">
* ...
* </DataSource>
* </DataSources>
* </pre>
* <p/>
* @param parent the parent of the DataSources element
* @return a {@link DataSourceConfig} for the repository
* @throws ConfigurationException on error
*/
protected DataSourceConfig parseDataSourceConfig(Element parent)
throws ConfigurationException {
DataSourceConfig dsc = new DataSourceConfig();
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE
&& DATASOURCES_ELEMENT.equals(child.getNodeName())) {
Element element = (Element) child;
NodeList children2 = element.getChildNodes();
// Process the DataSource entries:
for (int j = 0; j < children2.getLength(); j++) {
Node child2 = children2.item(j);
if (child2.getNodeType() == Node.ELEMENT_NODE
&& DATASOURCE_ELEMENT.equals(child2.getNodeName())) {
Element dsdef = (Element) child2;
String logicalName = getAttribute(dsdef, "name");
Properties props = parseParameters(dsdef);
dsc.addDataSourceDefinition(logicalName, props);
}
}
}
}
return dsc;
}
/**
* Parses data store configuration. Data store configuration uses the following format:
* <pre>
* <DataStore class="...">
* <param name="..." value="...">
* ...
* </DataStore>
* </pre>
* <p/>
* <code>DataStore</code> is a {@link #parseBeanConfig(Element,String) bean configuration}
* element.
*
* @param parent configuration element
* @param directory the repository directory
* @return data store factory
* @throws ConfigurationException if the configuration is broken
*/
protected DataStoreFactory getDataStoreFactory(
final Element parent, final String directory)
throws ConfigurationException {
return new DataStoreFactory() {
public DataStore getDataStore() throws RepositoryException {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE
&& DATA_STORE_ELEMENT.equals(child.getNodeName())) {
BeanConfig bc =
parseBeanConfig(parent, DATA_STORE_ELEMENT);
DataStore store = bc.newInstance(DataStore.class);
store.init(directory);
return store;
}
}
return null;
}
};
}
/**
* Parses repository lock mechanism configuration. Repository lock mechanism
* configuration uses the following format:
* <pre>
* <RepositoryLockMechanism class="..." >
* <param name="..." value="...">
* ...
* </RepositoryLockMechanism>
* </pre>
* <p/>
* <code>RepositoryLockMechanism</code> is a
* {@link #parseBeanConfig(Element,String) bean configuration} element.
*
* @param root the root configuration element
* @return repository lock mechanism factory
* @throws ConfigurationException if the configuration is broken
*/
protected RepositoryLockMechanismFactory getRepositoryLockMechanismFactory(final Element root) {
return new RepositoryLockMechanismFactory() {
public RepositoryLockMechanism getRepositoryLockMechanism() throws RepositoryException {
NodeList children = root.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE
&& REPOSITORY_LOCK_MECHANISM_ELEMENT.equals(child.getNodeName())) {
BeanConfig bc =
parseBeanConfig(root, REPOSITORY_LOCK_MECHANISM_ELEMENT);
return bc.newInstance(RepositoryLockMechanism.class);
}
}
return new RepositoryLock();
}
};
}
/**
* Parses the PersistenceManager config.
*
* @param parent parent of the <code>PersistenceManager</code> element
* @return persistence manager configuration
* @throws ConfigurationException if the configuration is broken
*/
protected PersistenceManagerConfig parsePersistenceManagerConfig(
Element parent) throws ConfigurationException {
return new PersistenceManagerConfig(
parseBeanConfig(parent, PERSISTENCE_MANAGER_ELEMENT));
}
/**
* Creates a new instance of a configuration parser but with overlayed
* variables and the same connection factory as this parser.
*
* @param variables the variables overlay
* @return a new configuration parser instance
*/
protected RepositoryConfigurationParser createSubParser(Properties variables) {
// overlay the properties
Properties props = new Properties(getVariables());
props.putAll(variables);
return new RepositoryConfigurationParser(props, connectionFactory);
}
/**
* Creates and returns a factory object that creates {@link FileSystem}
* instances based on the bean configuration at the named element.
*
* @param parent parent element
* @param name name of the bean configuration element
* @return file system factory
* @throws ConfigurationException if the bean configuration is invalid
*/
protected FileSystemFactory getFileSystemFactory(Element parent, String name)
throws ConfigurationException {
final BeanConfig config = parseBeanConfig(parent, name);
return new FileSystemFactory() {
public FileSystem getFileSystem() throws RepositoryException {
try {
FileSystem fs = config.newInstance(FileSystem.class);
fs.init();
return fs;
} catch (FileSystemException e) {
throw new RepositoryException(
"File system initialization failure.", e);
}
}
};
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void setConfigVisitor(BeanConfigVisitor configVisitor) {
this.configVisitor = configVisitor;
}
}