/* * ModeShape (http://www.modeshape.org) * * 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.modeshape.jcr; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Value; import org.modeshape.common.collection.Multimap; import org.modeshape.common.logging.Logger; import org.modeshape.common.util.IoUtil; import org.modeshape.common.util.StringUtil; import org.modeshape.jcr.cache.RepositoryCache; import org.modeshape.jcr.cache.document.WorkspaceCache; import org.modeshape.jcr.value.Path; import org.modeshape.jcr.value.PropertyType; import org.modeshape.jcr.xml.NodeImportDestination; import org.modeshape.jcr.xml.NodeImportXmlHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; /** * Class which handles the import of initial content files for new workspaces, using a {@link NodeImportXmlHandler}. It is * important that any new content is imported through the "JCR layer", so that the JCR specific validations are performed. * * @author Horia Chiorean (hchiorea@redhat.com) */ public final class InitialContentImporter { private static final Logger LOGGER = Logger.getLogger(InitialContentImporter.class); private final JcrRepository.RunningState repository; private final RepositoryConfiguration.InitialContent initialContentConfig; InitialContentImporter( RepositoryConfiguration.InitialContent initialContentConfig, JcrRepository.RunningState repository ) { this.initialContentConfig = initialContentConfig; this.repository = repository; } protected void initialize() { RepositoryCache repositoryCache = repository.repositoryCache(); if (!repositoryCache.isInitializingRepository()) { return; } Set<String> workspaceNames = repositoryCache.getWorkspaceNames(); if (workspaceNames.isEmpty()) { return; } repository.localDocumentStore().runInTransaction(() -> { for (String workspace : workspaceNames) { importInitialContent(workspace); } return null; }, 0, RepositoryCache.REPOSITORY_INFO_KEY); } protected void importInitialContent( String workspaceName ) throws RepositoryException { // check that there is something which should be imported if (!initialContentConfig.hasInitialContentFile(workspaceName)) { return; } RepositoryCache repositoryCache = repository.repositoryCache(); WorkspaceCache wsCache = repositoryCache.getWorkspaceCache(workspaceName); if (!wsCache.isEmpty()) { // the ws cache must be empty for initial content to be imported LOGGER.debug("Skipping import of initial content into workspace {0} as it is not empty", workspaceName); return; } InputStream stream = getInitialContentFileStream(workspaceName); if (stream == null) { return; } doImport(workspaceName, stream); } private void doImport( String workspaceName, InputStream initialContentFileStream ) throws RepositoryException { JcrSession internalSession = repository.loginInternalSession(workspaceName); ImportDestination importDestination = new ImportDestination(internalSession); NodeImportXmlHandler handler = new NodeImportXmlHandler(importDestination); try { XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setContentHandler(handler); reader.setErrorHandler(handler); reader.parse(new InputSource(initialContentFileStream)); internalSession.save(); } catch (SAXException e) { if (e.getCause() instanceof RepositoryException) { throw (RepositoryException)e.getCause(); } throw new RepositoryException(JcrI18n.errorWhileParsingInitialContentFile.text(e.getMessage()), e); } catch (IOException e) { throw new RepositoryException(JcrI18n.errorWhileReadingInitialContentFile.text(e.getMessage()), e); } finally { try { initialContentFileStream.close(); } catch (IOException e) { LOGGER.debug("Cannot close initial content file initialContentFileStream", e); } internalSession.logout(); } } private InputStream getInitialContentFileStream( String workspaceName ) { String initialContentFileString = initialContentConfig.getInitialContentFile(workspaceName); InputStream stream = IoUtil.getResourceAsStream(initialContentFileString, repository.environment().getClassLoader(this), null); if (stream == null) { repository.warn(JcrI18n.cannotLoadInitialContentFile, initialContentFileString); } return stream; } private class ImportDestination implements NodeImportDestination { private final JcrSession session; protected ImportDestination( JcrSession session ) { this.session = session; } @Override public ExecutionContext getExecutionContext() { return session.context(); } @SuppressWarnings( "synthetic-access" ) @Override public void submit( LinkedHashMap<Path, NodeImportXmlHandler.ImportElement> parseResults ) throws RepositoryException { List<NodeImportXmlHandler.ImportElement> elementsWithReferences = new ArrayList<>(); JcrValueFactory valueFactory = session.getValueFactory(); for (Path nodePath : parseResults.keySet()) { LOGGER.debug("Importing node at path {0}", nodePath); NodeImportXmlHandler.ImportElement element = parseResults.get(nodePath); Path parentPath = nodePath.getParent(); AbstractJcrNode parentNode = session.node(parentPath); // create the new node AbstractJcrNode newNode = null; // make sure the path is not encoded, because that's how the node xml handler generates it String newNodeRelativePath = nodePath.getLastSegment().getName().toString(); if (StringUtil.isBlank(element.getType())) { newNode = parentNode.addNode(newNodeRelativePath); } else { newNode = parentNode.addNode(newNodeRelativePath, element.getType()); } // add any mixins for (String mixin : element.getMixins()) { newNode.addMixin(mixin); } // set the properties for (String propertyName : element.getProperties().keySet()) { org.modeshape.jcr.value.PropertyType propertyType = element.getPropertyType(propertyName); if (isReference(propertyType)) { elementsWithReferences.add(element); // references will be processed later continue; } Collection<String> propertyValues = element.getProperties().get(propertyName); if (propertyValues.size() == 1) { String stringValue = propertyValues.iterator().next(); if (isBinary(propertyType)) { // we have a binary prop, try to resolve its stream InputStream inputStream = readBinaryStream(stringValue); if (inputStream != null) { newNode.setProperty(propertyName, inputStream); continue; } // we were unable to read its stream, to we'll set the binary value UTF-8 bytes (default behavior) } newNode.setProperty(propertyName, stringValue, propertyType.jcrType()); } else { String[] stringValues = propertyValues.toArray(new String[propertyValues.size()]); if (isBinary(propertyType)) { // try to parse each individual binary value and read its stream boolean allValuesRead = true; Value[] binaryValues = new Value[propertyValues.size()]; for (int i = 0; i < stringValues.length; i++) { String stringValue = stringValues[i]; InputStream inputStream = readBinaryStream(stringValue); if (inputStream == null) { // we were unable to read the stream, so we abort this approach altogether and we'll // set the binary value using the string bytes allValuesRead = false; break; } binaryValues[i] = valueFactory.createValue(inputStream); } if (allValuesRead) { newNode.setProperty(propertyName, binaryValues); // we managed to set the binary multi value streams, so move onto the next property continue; } } newNode.setProperty(propertyName, stringValues, propertyType.jcrType()); } } } if (!elementsWithReferences.isEmpty()) { // after we've processed all the nodes (and created them) set the reference properties setReferenceProperties(elementsWithReferences); } } private boolean isReference( org.modeshape.jcr.value.PropertyType propertyType ) { return org.modeshape.jcr.value.PropertyType.REFERENCE == propertyType || org.modeshape.jcr.value.PropertyType.WEAKREFERENCE == propertyType || org.modeshape.jcr.value.PropertyType.SIMPLEREFERENCE == propertyType; } private boolean isBinary( org.modeshape.jcr.value.PropertyType propertyType ) { return PropertyType.BINARY == propertyType; } private InputStream readBinaryStream( String resourcePath ) { // attempt to resolve the resource as the path to a file File file = new File(resourcePath); if (file.exists() && file.canRead()) { try { return new FileInputStream(file); } catch (FileNotFoundException e) { // we cannot locate/read the file } } // try resolving it via the classpath return InitialContentImporter.class.getClassLoader().getResourceAsStream(resourcePath); } private void setReferenceProperties( List<NodeImportXmlHandler.ImportElement> elementsWithReferences ) throws RepositoryException { for (NodeImportXmlHandler.ImportElement element : elementsWithReferences) { Node node = session.node(element.getPath()); Multimap<String, String> properties = element.getProperties(); for (String propertyName : properties.keySet()) { org.modeshape.jcr.value.PropertyType propertyType = element.getPropertyType(propertyName); if (!isReference(propertyType)) { continue; } setReferenceProperty(node, propertyName, properties.get(propertyName), propertyType); } } } private void setReferenceProperty( Node node, String propertyName, Collection<String> values, org.modeshape.jcr.value.PropertyType referenceType ) throws RepositoryException { List<Value> referenceValues = new ArrayList<>(); for (String absPath : values) { AbstractJcrNode referredNode = session.getNode(absPath); Value reference = null; switch (referenceType) { case REFERENCE: { reference = session.getValueFactory().createValue(referredNode, false); break; } case WEAKREFERENCE: { reference = session.getValueFactory().createValue(referredNode, true); break; } case SIMPLEREFERENCE: { reference = session.getValueFactory().createSimpleReference(referredNode); break; } default: { throw new IllegalArgumentException("Invalid reference type:" + referenceType); } } referenceValues.add(reference); } if (referenceValues.size() == 1) { node.setProperty(propertyName, referenceValues.get(0)); } else { node.setProperty(propertyName, referenceValues.toArray(new Value[referenceValues.size()])); } } } }