| /* |
| * 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.jcr.contentloader.internal; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.net.URLDecoder; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.StringTokenizer; |
| import java.util.function.Predicate; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| |
| import javax.jcr.Item; |
| import javax.jcr.NoSuchWorkspaceException; |
| import javax.jcr.Node; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.version.VersionManager; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.sling.jcr.contentloader.ContentReader; |
| import org.apache.sling.jcr.contentloader.PathEntry; |
| import org.osgi.framework.Bundle; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The <code>BundleContentLoader</code> loads initial content from the bundle. |
| */ |
| public class BundleContentLoader extends BaseImportLoader { |
| |
| public static final String PARENT_DESCRIPTOR = "ROOT"; |
| |
| private final Logger log = LoggerFactory.getLogger(BundleContentLoader.class); |
| |
| private BundleHelper bundleHelper; |
| |
| // bundles whose registration failed and should be retried |
| private List<Bundle> delayedBundles; |
| |
| private final Predicate<String> pathFilter; |
| |
| public BundleContentLoader(BundleHelper bundleHelper, ContentReaderWhiteboard contentReaderWhiteboard, |
| BundleContentLoaderConfiguration configuration) { |
| super(contentReaderWhiteboard); |
| this.bundleHelper = bundleHelper; |
| this.delayedBundles = new LinkedList<>(); |
| |
| List<Pattern> includes = Arrays |
| .stream(Optional.ofNullable(configuration).map(BundleContentLoaderConfiguration::includedTargets) |
| .orElse(new String[0])) |
| .filter(Objects::nonNull).map(Pattern::compile).collect(Collectors.toList()); |
| List<Pattern> excludes = Arrays |
| .stream(Optional.ofNullable(configuration).map(BundleContentLoaderConfiguration::excludedTargets) |
| .orElse(new String[0])) |
| .filter(Objects::nonNull).map(Pattern::compile).collect(Collectors.toList()); |
| this.pathFilter = path -> { |
| if (configuration == null || path == null) { |
| return true; |
| } else { |
| return includes.stream().anyMatch(p -> p.matcher(path).matches()) |
| && excludes.stream().noneMatch(p -> p.matcher(path).matches()); |
| } |
| }; |
| log.debug("Using includes: {} and excludes: {}", includes, excludes); |
| } |
| |
| public void dispose() { |
| if (delayedBundles != null) { |
| delayedBundles.clear(); |
| delayedBundles = null; |
| } |
| bundleHelper = null; |
| } |
| |
| /** |
| * Retry loading bundles that have previously been delayed |
| * @param metadataSession the JCR Session for reading/writing metadata |
| */ |
| public void retryDelayedBundles(final Session metadataSession) { |
| // handle delayed bundles, might help now |
| int currentSize = -1; |
| for (int i = delayedBundles.size(); i > 0 && currentSize != delayedBundles.size() |
| && !delayedBundles.isEmpty(); i--) { |
| for (Iterator<Bundle> di = delayedBundles.iterator(); di.hasNext();) { |
| Bundle delayed = di.next(); |
| if (registerBundleInternal(metadataSession, delayed, true, false)) { |
| di.remove(); |
| } |
| } |
| currentSize = delayedBundles.size(); |
| } |
| } |
| |
| /** |
| * Register a bundle and install its content. |
| * |
| * @param metadataSession the JCR Session for reading/writing metadata |
| * @param bundle the bundle to install |
| */ |
| public void registerBundle(final Session metadataSession, final Bundle bundle, final boolean isUpdate) { |
| |
| // if this is an update, we have to uninstall the old content first |
| if (isUpdate) { |
| this.unregisterBundle(metadataSession, bundle); |
| } |
| |
| log.debug("Registering bundle {} for content loading.", bundle.getSymbolicName()); |
| |
| if (registerBundleInternal(metadataSession, bundle, false, isUpdate)) { |
| retryDelayedBundles(metadataSession); |
| } else if (!isUpdate) { |
| // add to delayed bundles - if this is not an update! |
| delayedBundles.add(bundle); |
| } |
| } |
| |
| private boolean registerBundleInternal(final Session metadataSession, final Bundle bundle, final boolean isRetry, |
| final boolean isUpdate) { |
| |
| // check if bundle has initial content |
| final Iterator<PathEntry> pathIter = PathEntry.getContentPaths(bundle); |
| if (pathIter == null) { |
| log.debug("Bundle {} has no initial content", bundle.getSymbolicName()); |
| return true; |
| } |
| |
| try { |
| bundleHelper.createRepositoryPath(metadataSession, BundleContentLoaderListener.BUNDLE_CONTENT_NODE); |
| |
| // check if the content has already been loaded |
| final Map<String, Object> bundleContentInfo = bundleHelper.getBundleContentInfo(metadataSession, bundle, |
| true); |
| |
| // if we don't get an info, someone else is currently loading |
| if (bundleContentInfo == null) { |
| return false; |
| } |
| |
| boolean success = false; |
| List<String> createdNodes = null; |
| try { |
| final boolean contentAlreadyLoaded = ((Boolean) bundleContentInfo |
| .get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED)).booleanValue(); |
| boolean isBundleUpdated = false; |
| Calendar lastLoadedAt = (Calendar) bundleContentInfo |
| .get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED_AT); |
| if (lastLoadedAt != null && lastLoadedAt.getTimeInMillis() < bundle.getLastModified()) { |
| isBundleUpdated = true; |
| } |
| if (!isUpdate && !isBundleUpdated && contentAlreadyLoaded) { |
| log.info("Content of bundle already loaded {}.", bundle.getSymbolicName()); |
| } else { |
| createdNodes = installContent(metadataSession, bundle, pathIter, |
| contentAlreadyLoaded && !isBundleUpdated); |
| if (isRetry) { |
| // log success of retry |
| log.info("Retrying to load initial content for bundle {} succeeded.", bundle.getSymbolicName()); |
| } |
| } |
| |
| success = true; |
| return true; |
| } finally { |
| bundleHelper.unlockBundleContentInfo(metadataSession, bundle, success, createdNodes); |
| } |
| |
| } catch (ContentReaderUnavailableException crue) { |
| // if we are retrying we already logged this message once, so we |
| // won't log it again |
| if (!isRetry) { |
| log.warn("Cannot load initial content for bundle {} : {}", bundle.getSymbolicName(), crue.getMessage()); |
| } |
| } catch (RepositoryException re) { |
| // if we are retrying we already logged this message once, so we |
| // won't log it again |
| if (!isRetry) { |
| log.error( |
| "Cannot load initial content for bundle " + bundle.getSymbolicName() + " : " + re.getMessage(), |
| re); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Unregister a bundle. Remove installed content. |
| * |
| * @param session the session to read/write the metadata |
| * @param bundle The bundle. |
| */ |
| public void unregisterBundle(final Session session, final Bundle bundle) { |
| |
| if (delayedBundles.contains(bundle)) { |
| delayedBundles.remove(bundle); |
| } else { |
| try { |
| bundleHelper.createRepositoryPath(session, BundleContentLoaderListener.BUNDLE_CONTENT_NODE); |
| |
| final Map<String, Object> bundleContentInfo = bundleHelper.getBundleContentInfo(session, bundle, false); |
| |
| // if we don't get an info, someone else is currently loading or unloading |
| // or the bundle is already uninstalled |
| if (bundleContentInfo == null) { |
| return; |
| } |
| |
| try { |
| uninstallContent(session, bundle, |
| (String[]) bundleContentInfo.get(BundleContentLoaderListener.PROPERTY_UNINSTALL_PATHS)); |
| bundleHelper.contentIsUninstalled(session, bundle); |
| } finally { |
| bundleHelper.unlockBundleContentInfo(session, bundle, false, null); |
| } |
| } catch (RepositoryException re) { |
| log.error("Cannot remove initial content for bundle " + bundle.getSymbolicName() + " : " |
| + re.getMessage(), re); |
| } |
| } |
| } |
| |
| // ---------- internal ----------------------------------------------------- |
| |
| /** |
| * Install the content from the bundle. |
| * |
| * @return If the content should be removed on uninstall, a list of top nodes |
| */ |
| private List<String> installContent(final Session defaultSession, final Bundle bundle, |
| final Iterator<PathEntry> pathIter, final boolean contentAlreadyLoaded) throws RepositoryException, ContentReaderUnavailableException { |
| |
| final List<String> createdNodes = new ArrayList<>(); |
| final Map<String, Session> createdSessions = new HashMap<>(); |
| |
| log.debug("Installing initial content from bundle {}", bundle.getSymbolicName()); |
| final DefaultContentCreator contentCreator = new DefaultContentCreator(this.bundleHelper); |
| try { |
| while (pathIter.hasNext()) { |
| final PathEntry pathEntry = pathIter.next(); |
| |
| if (!pathFilter.test(pathEntry.getTarget())) { |
| log.debug("Path {} excluded by configuration", pathEntry.getPath()); |
| continue; |
| } |
| |
| if (pathEntry.isOverwrite() && (pathEntry.getTarget() == null || "/".equals(pathEntry.getTarget()))) { |
| log.error("Path {} tries to overwrite on the repository root level which is not allowed, only use overwrite with a dedicated path directive having any value but '/'", pathEntry.getPath()); |
| continue; |
| } |
| if (!contentAlreadyLoaded || pathEntry.isOverwrite()) { |
| String workspace = pathEntry.getWorkspace(); |
| final Session targetSession; |
| if (workspace != null) { |
| if (createdSessions.containsKey(workspace)) { |
| targetSession = createdSessions.get(workspace); |
| } else { |
| targetSession = createSession(workspace); |
| createdSessions.put(workspace, targetSession); |
| } |
| } else { |
| targetSession = defaultSession; |
| } |
| |
| final Node targetNode = getTargetNode(targetSession, pathEntry.getTarget(), pathEntry.isOverwrite()); |
| |
| if (targetNode != null) { |
| installFromPath(bundle, pathEntry.getPath(), pathEntry, targetNode, |
| pathEntry.isUninstall() ? createdNodes : null, contentCreator); |
| } |
| } |
| } |
| |
| // now optimize created nodes list |
| Collections.sort(createdNodes); |
| if (createdNodes.size() > 1) { |
| final Iterator<String> i = createdNodes.iterator(); |
| String previous = i.next() + '/'; |
| while (i.hasNext()) { |
| final String current = i.next(); |
| if (current.startsWith(previous)) { |
| i.remove(); |
| } else { |
| previous = current + '/'; |
| } |
| } |
| } |
| |
| // persist modifications now |
| defaultSession.refresh(true); |
| defaultSession.save(); |
| |
| for (Session session : createdSessions.values()) { |
| session.refresh(true); |
| session.save(); |
| } |
| |
| // finally check in versionable nodes |
| for (final Node versionable : contentCreator.getVersionables()) { |
| VersionManager versionManager = versionable.getSession().getWorkspace().getVersionManager(); |
| versionManager.checkin(versionable.getPath()); |
| } |
| } finally { |
| try { |
| if (defaultSession.hasPendingChanges()) { |
| defaultSession.refresh(false); |
| } |
| for (Session session : createdSessions.values()) { |
| if (session.hasPendingChanges()) { |
| session.refresh(false); |
| } |
| } |
| } catch (RepositoryException re) { |
| log.warn("Failure to rollback partial initial content for bundle {}", bundle.getSymbolicName(), re); |
| } |
| contentCreator.clear(); |
| for (Session session : createdSessions.values()) { |
| session.logout(); |
| } |
| } |
| log.debug("Done installing initial content from bundle {}", bundle.getSymbolicName()); |
| |
| return createdNodes; |
| } |
| |
| /** |
| * Handle content installation for a single path. |
| * |
| * @param bundle The bundle containing the content. |
| * @param path The path |
| * @param configuration |
| * @param parent The parent node. |
| * @param createdNodes An optional list to store all new nodes. This list is |
| * used for an uninstall |
| * @throws RepositoryException |
| */ |
| private void installFromPath(final Bundle bundle, final String path, final PathEntry configuration, |
| final Node parent, final List<String> createdNodes, final DefaultContentCreator contentCreator) |
| throws RepositoryException, ContentReaderUnavailableException { |
| |
| // init content creator |
| contentCreator.init(configuration, getContentReaders(), createdNodes, null); |
| |
| final Map<String, Node> processedEntries = new HashMap<>(); |
| |
| Enumeration<String> entries = bundle.getEntryPaths(path); |
| if (entries == null) { |
| // check for single content |
| final URL u = bundle.getEntry(path); |
| if (u == null) { |
| log.info("install: No initial content entries at {} in bundle {}", path, bundle.getSymbolicName()); |
| return; |
| } |
| // we have a single file content -> this should replace the target node fully, i.e. parent is one level above |
| handleFile(path, bundle, processedEntries, configuration, parent.getParent(), createdNodes, contentCreator); |
| return; |
| } |
| |
| // potential parent node import/extension |
| URL parentNodeDescriptor = importParentNode(bundle, path, parent, contentCreator); |
| if (parentNodeDescriptor != null) { |
| processedEntries.put(parentNodeDescriptor.toString(), parent); |
| } |
| |
| while (entries.hasMoreElements()) { |
| final String entry = entries.nextElement(); |
| log.debug("Processing initial content entry {} in bundle {}", entry, bundle.getSymbolicName()); |
| if (entry.endsWith("/")) { |
| |
| // dir, check for node descriptor, else create dir |
| final String base = entry.substring(0, entry.length() - 1); |
| |
| URL nodeDescriptor = null; |
| for (String ext : contentCreator.getContentReaders().keySet()) { |
| nodeDescriptor = bundle.getEntry(base + ext); |
| if (nodeDescriptor != null) { |
| break; |
| } |
| } |
| |
| // if we have a descriptor, which has not been processed yet, |
| // otherwise call createFolder, which creates an nt:folder or |
| // returns an existing node (created by a descriptor) |
| final String name = getName(base); |
| Node node = null; |
| if (nodeDescriptor != null) { |
| node = processedEntries.get(nodeDescriptor.toString()); |
| if (node == null) { |
| node = createNode(parent, name, nodeDescriptor, contentCreator, configuration); |
| processedEntries.put(nodeDescriptor.toString(), node); |
| } |
| } else { |
| node = createFolder(parent, name, configuration.isOverwrite()); |
| } |
| |
| // walk down the line |
| if (node != null) { |
| installFromPath(bundle, entry, configuration, node, createdNodes, contentCreator); |
| } |
| |
| } else { |
| // file => create file |
| handleFile(entry, bundle, processedEntries, configuration, parent, createdNodes, contentCreator); |
| } |
| } |
| } |
| |
| /** |
| * Handle a file entry. |
| * |
| * @param entry |
| * @param bundle |
| * @param processedEntries |
| * @param configuration |
| * @param parent |
| * @param createdNodes |
| * @throws RepositoryException |
| */ |
| private void handleFile(final String entry, final Bundle bundle, final Map<String, Node> processedEntries, |
| final PathEntry configuration, final Node parent, final List<String> createdNodes, |
| final DefaultContentCreator contentCreator) throws RepositoryException, ContentReaderUnavailableException { |
| |
| final URL file = bundle.getEntry(entry); |
| final String name = getName(entry); |
| try { |
| if (processedEntries.containsKey(file.toString())) { |
| // this is a consumed node descriptor |
| return; |
| } |
| |
| // check for node descriptor |
| URL nodeDescriptor = null; |
| for (String ext : contentCreator.getContentReaders().keySet()) { |
| nodeDescriptor = bundle.getEntry(entry + ext); |
| if (nodeDescriptor != null) { |
| break; |
| } |
| } |
| |
| // install if it is a descriptor |
| boolean foundReader = getContentReader(entry, configuration) != null; |
| |
| Node node = null; |
| if (foundReader) { |
| node = createNode(parent, name, file, contentCreator, configuration); |
| if (node != null) { |
| log.debug("Created node as {} {}", node.getPath(), name); |
| processedEntries.put(file.toString(), node); |
| } else { |
| log.warn("No node created for file {} {}", file, name); |
| } |
| } else { |
| // if we require a ContentReader for this entry but didn't find one |
| // then throw an exception to stop processing this bundle and put |
| // it into the delayedBundles list to retry later |
| if (configuration.isImportProviderRequired(name)) { |
| throw new ContentReaderUnavailableException(String.format("Unable to locate a required content reader for entry %s", entry)); |
| } else { |
| log.debug("Can't find content reader for entry {} at {}", entry, name); |
| } |
| } |
| |
| // otherwise just place as file |
| if (node == null) { |
| try { |
| createFile(configuration, parent, file, createdNodes, contentCreator); |
| node = parent.getNode(name); |
| } catch (IOException ioe) { |
| log.warn("Cannot create file node for {}", file, ioe); |
| } |
| } |
| // if we have a descriptor, which has not been processed yet, |
| // process it |
| if (nodeDescriptor != null && !processedEntries.containsKey(nodeDescriptor.toString())) { |
| try { |
| contentCreator.setIgnoreOverwriteFlag(true); |
| node = createNode(parent, name, nodeDescriptor, contentCreator, configuration); |
| processedEntries.put(nodeDescriptor.toString(), node); |
| } finally { |
| contentCreator.setIgnoreOverwriteFlag(false); |
| } |
| } |
| } catch (RepositoryException e) { |
| log.error("Failed to process file {} from {}", file, name); |
| throw e; |
| } |
| } |
| |
| /** |
| * Create a new node from a content resource found in the bundle. |
| * |
| * @param parent The parent node |
| * @param name The name of the new content node |
| * @param resourceUrl The resource url. |
| * @param contentCreator the content creator |
| * @param configuration the configuration for the node that needs to be created |
| * @return |
| * @throws RepositoryException |
| */ |
| private Node createNode(Node parent, String name, URL resourceUrl, final DefaultContentCreator contentCreator, |
| PathEntry configuration) throws RepositoryException { |
| |
| final String resourcePath = resourceUrl.getPath().toLowerCase(); |
| InputStream contentStream = null; |
| try { |
| // special treatment for system view imports |
| if (resourcePath.endsWith(EXT_JCR_XML)) { |
| contentStream = resourceUrl.openStream(); |
| return importJcrXml(parent, name, contentStream, false); |
| } |
| |
| // get the node reader for this resource |
| final ContentReader nodeReader = getContentReader(resourcePath, configuration); |
| |
| // cannot find out the type |
| if (nodeReader == null) { |
| return null; |
| } |
| |
| final String contentReaderExtension = getContentReaderExtension(name); |
| contentCreator.prepareParsing(parent, toPlainName(name, contentReaderExtension)); |
| nodeReader.parse(resourceUrl, contentCreator); |
| |
| return contentCreator.getCreatedRootNode(); |
| } catch (RepositoryException re) { |
| throw re; |
| } catch (Exception t) { |
| throw new RepositoryException(t.getMessage(), t); |
| } finally { |
| IOUtils.closeQuietly(contentStream); |
| } |
| } |
| |
| /** |
| * Create a folder |
| * |
| * @param parent The parent node. |
| * @param name The name of the folder |
| * @param overwrite If set to true, an existing folder is removed first. |
| * @return The node pointing to the folder. |
| * @throws RepositoryException |
| */ |
| private Node createFolder(Node parent, String name, final boolean overwrite) throws RepositoryException { |
| |
| if (parent.hasNode(name)) { |
| if (overwrite) { |
| parent.getNode(name).remove(); |
| } else { |
| return parent.getNode(name); |
| } |
| } |
| |
| return parent.addNode(name, "sling:Folder"); |
| } |
| |
| /** |
| * Create a file from the given url. |
| * |
| * @param configuration |
| * @param parent |
| * @param source |
| * @param createdNodes |
| * @param contentCreator |
| * @throws IOException |
| * @throws RepositoryException |
| */ |
| private void createFile(PathEntry configuration, Node parent, URL source, List<String> createdNodes, |
| final DefaultContentCreator contentCreator) throws IOException, RepositoryException { |
| |
| final String srcPath = source.getPath(); |
| int pos = srcPath.lastIndexOf('/'); |
| final String name = getName(source.getPath()); |
| final String path; |
| if (pos == -1) { |
| path = name; |
| } else { |
| path = srcPath.substring(0, pos + 1) + name; |
| } |
| |
| contentCreator.init(configuration, getContentReaders(), createdNodes, null); |
| contentCreator.prepareParsing(parent, name); |
| final URLConnection conn = source.openConnection(); |
| final long lastModified = Math.min(conn.getLastModified(), configuration.getLastModified()); |
| final String type = conn.getContentType(); |
| final InputStream data = conn.getInputStream(); |
| contentCreator.createFileAndResourceNode(path, data, type, lastModified); |
| contentCreator.finishNode(); |
| contentCreator.finishNode(); |
| } |
| |
| /** |
| * Gets and decodes the name part of the <code>path</code>. The name is the part |
| * of the path after the last slash (or the complete path if no slash is |
| * contained). To support names containing unsupported characters such as colon |
| * (<code>:</code>), names may be URL encoded (see |
| * <code>java.net.URLEncoder</code>) using the <i>UTF-8</i> character encoding. |
| * In this case, this method decodes the name using the |
| * <code>java.net.URLDecoder</code> class with the <i>UTF-8</i> character |
| * encoding. |
| * |
| * @param path The path from which to extract the name part. |
| * @return The URL decoded name part. |
| */ |
| private String getName(String path) { |
| |
| int lastSlash = path.lastIndexOf('/'); |
| String name = (lastSlash < 0) ? path : path.substring(lastSlash + 1); |
| |
| // check for encoded characters (%xx) |
| // has encoded characters, need to decode |
| if (name.indexOf('%') >= 0) { |
| try { |
| return URLDecoder.decode(name, "UTF-8"); |
| } catch (UnsupportedEncodingException uee) { |
| // actually unexpected because UTF-8 is required by the spec |
| log.error("Cannot decode " + name + " because the platform has no support for UTF-8, using undecoded"); |
| } catch (Exception e) { |
| // IllegalArgumentException or failure to decode |
| log.error("Cannot decode " + name + ", using undecoded", e); |
| } |
| } |
| |
| // not encoded or problems decoding, return the name unmodified |
| return name; |
| } |
| |
| private Node getTargetNode(Session session, String path, boolean overwrite) throws RepositoryException { |
| |
| // not specified path directive |
| if (path == null) { |
| return session.getRootNode(); |
| } |
| |
| if (!path.startsWith("/")) { |
| // make relative path absolute |
| path = "/" + path; |
| } |
| |
| if (!session.itemExists(path)) { |
| Node currentNode = session.getRootNode(); |
| final StringTokenizer st = new StringTokenizer(path.substring(1), "/"); |
| while (st.hasMoreTokens()) { |
| final String name = st.nextToken(); |
| if (!currentNode.hasNode(name)) { |
| currentNode.addNode(name, "sling:Folder"); |
| } |
| currentNode = currentNode.getNode(name); |
| } |
| return currentNode; |
| } else { |
| Item item = session.getItem(path); |
| if (!item.isNode()) { |
| log.warn("Cannot use item as target path {} as this is an existing property", path); |
| return null; |
| } |
| Node targetNode = session.getNode(path); |
| // overwrite target node itself? |
| if (overwrite) { |
| targetNode = createFolder(targetNode.getParent(), targetNode.getName(), true); |
| } |
| return targetNode; |
| } |
| } |
| |
| private void uninstallContent(final Session defaultSession, final Bundle bundle, final String[] uninstallPaths) { |
| |
| final Map<String, Session> createdSessions = new HashMap<>(); |
| |
| try { |
| log.debug("Uninstalling initial content from bundle {}", bundle.getSymbolicName()); |
| if (uninstallPaths != null && uninstallPaths.length > 0) { |
| for (String path : uninstallPaths) { |
| if (!pathFilter.test(path)) { |
| log.debug("Path {} excluded by configuration", path); |
| continue; |
| } |
| final Session targetSession; |
| |
| final int wsSepPos = path.indexOf(":/"); |
| if (wsSepPos != -1) { |
| final String workspaceName = path.substring(0, wsSepPos); |
| path = path.substring(wsSepPos + 1); |
| if (workspaceName.equals(defaultSession.getWorkspace().getName())) { |
| targetSession = defaultSession; |
| } else if (createdSessions.containsKey(workspaceName)) { |
| targetSession = createdSessions.get(workspaceName); |
| } else { |
| targetSession = createSession(workspaceName); |
| createdSessions.put(workspaceName, targetSession); |
| } |
| } else { |
| targetSession = defaultSession; |
| } |
| |
| if (targetSession.itemExists(path)) { |
| targetSession.getItem(path).remove(); |
| } |
| } |
| |
| // persist modifications now |
| defaultSession.save(); |
| |
| for (Session session : createdSessions.values()) { |
| session.save(); |
| } |
| } |
| log.debug("Done uninstalling initial content from bundle {}", bundle.getSymbolicName()); |
| } catch (RepositoryException re) { |
| log.error("Unable to uninstall initial content from bundle " + bundle.getSymbolicName(), re); |
| } finally { |
| try { |
| if (defaultSession.hasPendingChanges()) { |
| defaultSession.refresh(false); |
| } |
| for (Session session : createdSessions.values()) { |
| if (session.hasPendingChanges()) { |
| session.refresh(false); |
| } |
| } |
| } catch (RepositoryException re) { |
| log.warn("Failure to rollback uninstalling initial content for bundle {}", bundle.getSymbolicName(), |
| re); |
| } |
| |
| for (Session session : createdSessions.values()) { |
| session.logout(); |
| } |
| } |
| } |
| |
| protected static final class Descriptor { |
| |
| public URL url; |
| |
| private ContentReader contentReader; |
| |
| } |
| |
| /** |
| * Return the parent node descriptor (ROOT). |
| */ |
| private Descriptor getParentNodeDescriptor(final Bundle bundle, final String path, |
| final DefaultContentCreator contentCreator) { |
| |
| for (Map.Entry<String, ContentReader> entry : contentCreator.getContentReaders().entrySet()) { |
| if (entry.getValue() != null) { |
| final StringBuilder filePath = new StringBuilder(path); |
| if (!path.endsWith("/")) { |
| filePath.append("/"); |
| } |
| filePath.append(PARENT_DESCRIPTOR); |
| // add file extension, e.g. .jcr.xml, .xml, .zip (see BaseImportLoader) |
| filePath.append(entry.getKey()); |
| URL url = bundle.getEntry(filePath.toString()); |
| if (url != null) { |
| final Descriptor descriptor = new Descriptor(); |
| descriptor.url = url; |
| descriptor.contentReader = entry.getValue(); |
| return descriptor; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Imports mixin nodes and properties (and optionally child nodes) of the parent |
| * node. |
| */ |
| private URL importParentNode(Bundle bundle, String path, Node parent, final DefaultContentCreator contentCreator) |
| throws RepositoryException { |
| |
| final Descriptor descriptor = getParentNodeDescriptor(bundle, path, contentCreator); |
| // no parent descriptor (ROOT) found |
| if (descriptor == null) { |
| return null; |
| } |
| |
| try { |
| contentCreator.prepareParsing(parent, null); |
| descriptor.contentReader.parse(descriptor.url, contentCreator); |
| return descriptor.url; |
| } catch (RepositoryException re) { |
| throw re; |
| } catch (Exception t) { |
| throw new RepositoryException(t.getMessage(), t); |
| } |
| } |
| |
| private Session createSession(String workspace) throws RepositoryException { |
| try { |
| return bundleHelper.getSession(workspace); |
| } catch (NoSuchWorkspaceException e) { |
| Session temp = bundleHelper.getSession(); |
| temp.getWorkspace().createWorkspace(workspace); |
| temp.logout(); |
| return bundleHelper.getSession(workspace); |
| } |
| } |
| |
| } |