blob: 63ed2ed8a84dd366e764c2ca03d726bd43d343e9 [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.sling.testing.mock.sling;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.Workspace;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeDefinitionTemplate;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.NodeTypeIterator;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.NodeTypeTemplate;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.nodetype.PropertyDefinitionTemplate;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.commons.cnd.CndImporter;
import org.apache.jackrabbit.commons.cnd.CompactNodeTypeDefReader;
import org.apache.jackrabbit.commons.cnd.DefinitionBuilderFactory;
import org.apache.jackrabbit.commons.cnd.TemplateBuilderFactory;
import org.apache.sling.testing.mock.osgi.ManifestScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Singleton class that fetches all node type definitions from OSGi bundle MANIFEST.MF files
* with "Sling-Nodetypes" definitions in the classpath.
* Additionally it support registering them to a JCR repository.
*/
public final class NodeTypeDefinitionScanner {
private static final NodeTypeDefinitionScanner SINGLETON = new NodeTypeDefinitionScanner();
private static final int MAX_ITERATIONS = 5;
private static final Logger log = LoggerFactory.getLogger(NodeTypeDefinitionScanner.class);
private final List<String> nodeTypeDefinitions;
private NodeTypeDefinitionScanner() {
nodeTypeDefinitions = findeNodeTypeDefinitions();
}
/**
* @return Node type definitions found in classpath as registered in OSGi bundle headers
*/
public List<String> getNodeTypeDefinitions() {
return nodeTypeDefinitions;
}
/**
* Registers node types found in classpath in JCR repository.
* @param session Session
* @param nodeTypeMode Node type mode
* @throws RepositoryException Repository exception
*/
public void register(Session session, NodeTypeMode nodeTypeMode) throws RepositoryException {
List<String> nodeTypeResources = getNodeTypeDefinitions();
register(session, nodeTypeResources, nodeTypeMode);
}
/**
* Registers node types found in classpath in JCR repository.
* @param session Session
* @param nodeTypeResources List of classpath resource URLs pointing to node type definitions
* @param nodeTypeMode Node type mode
* @throws RepositoryException Repository exception
*/
public void register(Session session, List<String> nodeTypeResources, NodeTypeMode nodeTypeMode) throws RepositoryException {
switch (nodeTypeMode) {
case NOT_SUPPORTED:
// do nothing
break;
case NAMESPACES_ONLY:
registerNamespaces(session, nodeTypeResources);
break;
case NODETYPES_REQUIRED:
registerNodeTypes(session, nodeTypeResources);
break;
default:
throw new IllegalArgumentException("Node type mode not supported: " + nodeTypeMode);
}
}
/**
* Registers only the namespaces found in node type definitions in classpath in JCR repository.
* @param session Session
* @param nodeTypeResources List of classpath resource URLs pointing to node type definitions
*/
private void registerNamespaces(Session session, List<String> nodeTypeResources) throws RepositoryException {
ClassLoader classLoader = getClass().getClassLoader();
Workspace workspace = session.getWorkspace();
NamespaceRegistry namespaceRegistry = workspace.getNamespaceRegistry();
ValueFactory valueFactory = session.getValueFactory();
DefinitionBuilderFactory<NodeTypeTemplate, NamespaceRegistry> factory =
new TemplateBuilderFactory(new DummyNodeTypeManager(), valueFactory, namespaceRegistry);
for (String nodeTypeResource : nodeTypeResources) {
InputStream is = classLoader.getResourceAsStream(nodeTypeResource);
if (is == null) {
continue;
}
try {
Reader reader = new InputStreamReader(is);
CompactNodeTypeDefReader<NodeTypeTemplate, NamespaceRegistry> cndReader
= new CompactNodeTypeDefReader<NodeTypeTemplate, NamespaceRegistry>(reader, nodeTypeResource, factory);
NamespaceRegistry mapping = cndReader.getNamespaceMapping();
for (int i=0; i<mapping.getURIs().length; i++) {
String uri = mapping.getURIs()[i];
String prefix = mapping.getPrefix(uri);
try {
namespaceRegistry.registerNamespace(prefix, uri);
}
catch (RepositoryException ex) {
// ignore
}
}
}
catch (Throwable ex) {
log.warn("Unable to parse node type definition: " + nodeTypeResource, ex);
}
finally {
IOUtils.closeQuietly(is);
}
}
}
/**
* Registers node types found in classpath in JCR repository.
* @param session Session
* @param nodeTypeResources List of classpath resource URLs pointing to node type definitions
*/
private void registerNodeTypes(Session session, List<String> nodeTypeResources) throws RepositoryException {
ClassLoader classLoader = getClass().getClassLoader();
Workspace workspace = session.getWorkspace();
NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager();
NamespaceRegistry namespaceRegistry = workspace.getNamespaceRegistry();
ValueFactory valueFactory = session.getValueFactory();
// try registering node types multiple times because the exact order is not known
int iteration = 0;
List<String> remainingNodeTypeResources = new ArrayList<String>(nodeTypeResources);
while (!remainingNodeTypeResources.isEmpty()) {
registerNodeTypesAndRemoveSucceeds(remainingNodeTypeResources, classLoader, nodeTypeManager, namespaceRegistry, valueFactory, false);
iteration++;
if (iteration >= MAX_ITERATIONS) {
break;
}
}
if (!remainingNodeTypeResources.isEmpty()) {
registerNodeTypesAndRemoveSucceeds(remainingNodeTypeResources, classLoader, nodeTypeManager, namespaceRegistry, valueFactory, true);
}
}
/**
* Register node types found in classpath in JCR repository, and remove those that succeeded to register from the list.
* @param nodeTypeResources List of nodetype classpath resources
* @param classLoader
* @param nodeTypeManager
* @param namespaceRegistry
* @param valueFactory
* @param logError if true, and error is logged if node type registration failed. Otherwise it is ignored.
*/
private void registerNodeTypesAndRemoveSucceeds(List<String> nodeTypeResources, ClassLoader classLoader,
NodeTypeManager nodeTypeManager, NamespaceRegistry namespaceRegistry, ValueFactory valueFactory,
boolean logError) {
Iterator<String> nodeTypeResourcesIterator = nodeTypeResources.iterator();
while (nodeTypeResourcesIterator.hasNext()) {
String nodeTypeResource = nodeTypeResourcesIterator.next();
InputStream is = classLoader.getResourceAsStream(nodeTypeResource);
if (is == null) {
continue;
}
try {
Reader reader = new InputStreamReader(is);
CndImporter.registerNodeTypes(reader, nodeTypeResource, nodeTypeManager, namespaceRegistry, valueFactory, true);
nodeTypeResourcesIterator.remove();
}
catch (Throwable ex) {
if (logError) {
log.warn("Unable to register node type: " + nodeTypeResource, ex);
}
}
finally {
IOUtils.closeQuietly(is);
}
}
}
/**
* Find all node type definition classpath paths by searching all MANIFEST.MF files in the classpath and reading
* the paths from the "Sling-Nodetypes" entry.
* The order of the paths from each entry is preserved, but the overall order when multiple bundles define such an entry
* is not deterministic and may not be correct according to the dependencies between the node type definitions.
* @return List of node type definition class paths
*/
private static List<String> findeNodeTypeDefinitions() {
return new ArrayList<String>(ManifestScanner.getValues("Sling-Nodetypes"));
}
public static NodeTypeDefinitionScanner get() {
return SINGLETON;
}
/**
* Some dummy classes to allow usage of CompactNodeTypeDefReader with underlying JCR mock
*/
private static class DummyNodeTypeManager implements NodeTypeManager {
@Override
public NodeType getNodeType(String nodeTypeName) {
return null;
}
@Override
public boolean hasNodeType(String name) {
return false;
}
@Override
public NodeTypeIterator getAllNodeTypes() {
return null;
}
@Override
public NodeTypeIterator getPrimaryNodeTypes() {
return null;
}
@Override
public NodeTypeIterator getMixinNodeTypes() {
return null;
}
@Override
public NodeTypeTemplate createNodeTypeTemplate() {
return new DummyNodeTypeTemplate();
}
@Override
public NodeTypeTemplate createNodeTypeTemplate(NodeTypeDefinition ntd) {
return new DummyNodeTypeTemplate();
}
@Override
public NodeDefinitionTemplate createNodeDefinitionTemplate() {
return new DummyNodeDefinitionTemplate();
}
@Override
public PropertyDefinitionTemplate createPropertyDefinitionTemplate() {
return new DummyPropertyDefinitionTemplate();
}
@Override
public NodeType registerNodeType(NodeTypeDefinition ntd, boolean allowUpdate) {
return null;
}
@Override
public NodeTypeIterator registerNodeTypes(NodeTypeDefinition[] ntds, boolean allowUpdate) {
return null;
}
@Override
public void unregisterNodeType(String name) {
}
@Override
public void unregisterNodeTypes(String[] names) {
}
}
private static class DummyNodeTypeTemplate implements NodeTypeTemplate {
@Override
public String getName() {
return null;
}
@Override
public String[] getDeclaredSupertypeNames() {
return null;
}
@Override
public boolean isAbstract() {
return false;
}
@Override
public boolean isMixin() {
return false;
}
@Override
public boolean hasOrderableChildNodes() {
return false;
}
@Override
public boolean isQueryable() {
return false;
}
@Override
public String getPrimaryItemName() {
return null;
}
@Override
public PropertyDefinition[] getDeclaredPropertyDefinitions() {
return null;
}
@Override
public NodeDefinition[] getDeclaredChildNodeDefinitions() {
return null;
}
@Override
public void setName(String name) {
}
@Override
public void setDeclaredSuperTypeNames(String[] names) {
}
@Override
public void setAbstract(boolean abstractStatus) {
}
@Override
public void setMixin(boolean mixin) {
}
@Override
public void setOrderableChildNodes(boolean orderable) {
}
@Override
public void setPrimaryItemName(String name) {
}
@Override
public void setQueryable(boolean queryable) {
}
@Override
public List getPropertyDefinitionTemplates() {
return new ArrayList();
}
@Override
public List getNodeDefinitionTemplates() {
return new ArrayList();
}
}
private static class DummyNodeDefinitionTemplate implements NodeDefinitionTemplate {
@Override
public NodeType[] getRequiredPrimaryTypes() {
return null;
}
@Override
public String[] getRequiredPrimaryTypeNames() {
return null;
}
@Override
public NodeType getDefaultPrimaryType() {
return null;
}
@Override
public String getDefaultPrimaryTypeName() {
return null;
}
@Override
public boolean allowsSameNameSiblings() {
return false;
}
@Override
public NodeType getDeclaringNodeType() {
return null;
}
@Override
public String getName() {
return null;
}
@Override
public boolean isAutoCreated() {
return false;
}
@Override
public boolean isMandatory() {
return false;
}
@Override
public int getOnParentVersion() {
return 0;
}
@Override
public boolean isProtected() {
return false;
}
@Override
public void setName(String name) {
}
@Override
public void setAutoCreated(boolean autoCreated) {
}
@Override
public void setMandatory(boolean mandatory) {
}
@Override
public void setOnParentVersion(int opv) {
}
@Override
public void setProtected(boolean protectedStatus) {
}
@Override
public void setRequiredPrimaryTypeNames(String[] names) {
}
@Override
public void setDefaultPrimaryTypeName(String name) {
}
@Override
public void setSameNameSiblings(boolean allowSameNameSiblings) {
}
}
private static class DummyPropertyDefinitionTemplate implements PropertyDefinitionTemplate {
@Override
public int getRequiredType() {
return 0;
}
@Override
public String[] getValueConstraints() {
return null;
}
@Override
public Value[] getDefaultValues() {
return null;
}
@Override
public boolean isMultiple() {
return false;
}
@Override
public String[] getAvailableQueryOperators() {
return null;
}
@Override
public boolean isFullTextSearchable() {
return false;
}
@Override
public boolean isQueryOrderable() {
return false;
}
@Override
public NodeType getDeclaringNodeType() {
return null;
}
@Override
public String getName() {
return null;
}
@Override
public boolean isAutoCreated() {
return false;
}
@Override
public boolean isMandatory() {
return false;
}
@Override
public int getOnParentVersion() {
return 0;
}
@Override
public boolean isProtected() {
return false;
}
@Override
public void setName(String name) {
}
@Override
public void setAutoCreated(boolean autoCreated) {
}
@Override
public void setMandatory(boolean mandatory) {
}
@Override
public void setOnParentVersion(int opv) {
}
@Override
public void setProtected(boolean protectedStatus) {
}
@Override
public void setRequiredType(int type) {
}
@Override
public void setValueConstraints(String[] constraints) {
}
@Override
public void setDefaultValues(Value[] defaultValues) {
}
@Override
public void setMultiple(boolean multiple) {
}
@Override
public void setAvailableQueryOperators(String[] operators) {
}
@Override
public void setFullTextSearchable(boolean fullTextSearchable) {
}
@Override
public void setQueryOrderable(boolean queryOrderable) {
}
}
}