All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sling.jcr.contentloader.PathEntry Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * 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;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Function;
import java.util.jar.Manifest;
import java.util.stream.Collectors;

import org.apache.sling.commons.osgi.ManifestHeader;
import org.apache.sling.commons.osgi.ManifestHeader.NameValuePair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A path entry from the manifest for initial content.
 */
public class PathEntry extends ImportOptions {

    private static final Logger log = LoggerFactory.getLogger(PathEntry.class);

    /** The manifest header to specify initial content to be loaded. */
    public static final String CONTENT_HEADER = "Sling-Initial-Content";

    /**
     * The overwrite directive specifying if content should be overwritten or
     * just initially added.
     */
    public static final String OVERWRITE_DIRECTIVE = "overwrite";

    /**
     * The overwriteProperties directive specifying if content properties 
     * should be overwritten or just initially added.
     */
    public static final String OVERWRITE_PROPERTIES_DIRECTIVE = "overwriteProperties";
    
    /**
     * The overwriteProperties directive specifying if content properties 
     * should be overwritten or just initially added.
     */
    public static final String MERGE_PROPERTIES_DIRECTIVE = "mergeProperties";
    
    /**
     * The overwriteProperties directive specifying if content properties 
     * should be overwritten or just initially added.
     */
    public static final String MERGE_NODES_DIRECTIVE = "merge";

    /** The uninstall directive specifying if content should be uninstalled. */
    public static final String UNINSTALL_DIRECTIVE = "uninstall";

    /**
     * The path directive specifying the target node where initial content will
     * be loaded.
     */
    public static final String PATH_DIRECTIVE = "path";

    /**
     * The workspace into which the content will be loaded.
     */
    public static final String WORKSPACE_DIRECTIVE = "workspace";

    /**
     * The checkin directive specifying whether versionable nodes should be
     * checked in
     */
    public static final String CHECKIN_DIRECTIVE = "checkin";

    /**
     * The autoCheckout directive specifying whether versionable nodes should be
     * checked out when necessary
     */
    public static final String AUTOCHECKOUT_DIRECTIVE = "autoCheckout";

    /**
     * The ignore content readers directive specifying whether the available 
     * {@link org.apache.sling.jcr.contentloader.ContentReader}s should be used during
     * content loading. This is a string value that defaults to the emptystring.
     * @since 2.0.4
     */
    public static final String IGNORE_CONTENT_READERS_DIRECTIVE = "ignoreImportProviders";

    /**
     * The flag "maven:mount" is not actually used by the JCR Content Loader. It can be used
     * to signal to the "mount" goal of the sling-maven-plugin to ignore a certain Sling-Initial-Content entry
     * of a Maven project when "sling:mount" is executed on the command line.
     * Reference: https://github.com/apache/sling-maven-plugin/blob/d67d5c1900782c68c1bfcbdc499c02cf31224916/sling-maven-plugin/src/main/java/org/apache/sling/maven/bundlesupport/fsresource/SlingInitialContentMounter.java#L97
     */
    private static final String MAVEN_MOUNT_DIRECTIVE = "maven:mount";

    /** All directive names which are valid for header Sling-Initial-Content */
    public Set VALID_DIRECTIVES = new HashSet<>(Arrays.asList(
        OVERWRITE_DIRECTIVE,
        OVERWRITE_PROPERTIES_DIRECTIVE,
        MERGE_PROPERTIES_DIRECTIVE,
        MERGE_NODES_DIRECTIVE,
        UNINSTALL_DIRECTIVE,
        PATH_DIRECTIVE,
        WORKSPACE_DIRECTIVE,
        CHECKIN_DIRECTIVE,
        AUTOCHECKOUT_DIRECTIVE,
        IGNORE_CONTENT_READERS_DIRECTIVE,
        MAVEN_MOUNT_DIRECTIVE
    ));

    private final boolean propertyMerge;
    
    private final boolean nodeMerge;

    /** The path for the initial content. */
    private final String path;

    /** Should existing content be overwritten? */
    private final boolean overwrite;

    /** Should existing content properties be overwritten? */
    private final boolean overwriteProperties;

    /** Should existing content be uninstalled? */
    private final boolean uninstall;

    /** Should versionable nodes be checked in? */
    private final boolean checkin;

    /** Should versionable nodes be auto checked out when necessary? */
    private final boolean autoCheckout;
    
    /** Which content readers should be ignored? @since 2.0.4 */
    private final List ignoreContentReaders;

    /**
     * Target path where initial content will be loaded. If it´s null then
     * target node is the root node
     */
    private final String target;

    /** Workspace to import into. */
    private final String workspace;

    private long lastModified;

    /** 
     * Parses the "Sling-Initial-Content" header from the given manifest and returns the resolved PathEntries
     * 
     * @param manifest the manifest
     * @param bundleLastModified the timestamp when the bundle has been last modified or -1 if not known
     * @return an iterator over the parsed {@code PathEntry} items or {@code null} in case no "Sling-Initial-Content" header was found in the manifest
     */
    public static @Nullable Iterator getContentPaths(@NotNull final Manifest manifest, long bundleLastModified) {
        // convert to map of right type (Attributes has Attributes.Name as key type, not String), so simple casting won't do
        Map headers = manifest.getMainAttributes().entrySet().stream().collect(Collectors.toMap( e -> e.getKey().toString(),  e-> e.getValue().toString()));
        return getContentPaths(headers, bundleLastModified);
    }

    /** 
     * Parses the "Sling-Initial-Content" header from the given bundle and returns the resolved PathEntries
     * 
     * @param bundle the bundle
     * @return an iterator over the parsed {@code PathEntry} items or {@code null} in case no "Sling-Initial-Content" header was found in the bundle's manifest
     */
    public static @Nullable Iterator getContentPaths(final Bundle bundle) {
        return getContentPaths(toMap(bundle.getHeaders()), bundle.getLastModified(), bundle.getSymbolicName());
    }

    /** 
     * Parses the "Sling-Initial-Content" header from the given headers and returns the resolved PathEntries
     * 
     * @param headers the manifest headers
     * @param bundleLastModified the timestamp when the bundle has been last modified or -1 if not known
     * @return an iterator over the parsed {@code PathEntry} items or {@code null} in case no "Sling-Initial-Content" header was found
     */
    public static @Nullable Iterator getContentPaths(final Map headers, long bundleLastModified) {
        return getContentPaths(headers, bundleLastModified, null);
    }

    private static @Nullable Iterator getContentPaths(final Map headers, long bundleLastModified, @Nullable String bundleSymblicName) {
        final List entries = new ArrayList<>();
        String bundleLastModifiedStamp = headers.get("Bnd-LastModified");
        if ( bundleLastModifiedStamp != null ) {
            bundleLastModified = Math.min(bundleLastModified, Long.parseLong(bundleLastModifiedStamp));
        }
        final String root = headers.get(CONTENT_HEADER);
        if (root != null) {
            final ManifestHeader header = ManifestHeader.parse(root);
            for (final ManifestHeader.Entry entry : header.getEntries()) {
                entries.add(new PathEntry(entry, bundleLastModified, bundleSymblicName));
            }
        }

        if (entries.isEmpty()) {
            return null;
        }
        return entries.iterator();
    }

    private static Map toMap(Dictionary dict) {
        List keys = Collections.list(dict.keys());
        return keys.stream()
                   .collect(Collectors.toMap(Function.identity(), dict::get));
    }

    public PathEntry(ManifestHeader.Entry entry, long bundleLastModified) {
        this(entry, bundleLastModified, null);
    }

    public PathEntry(ManifestHeader.Entry entry, long bundleLastModified, @Nullable String bundleSymbolicName) {
        this.path = entry.getValue();
        this.lastModified = bundleLastModified;

        final String logPrefix;
        if (bundleSymbolicName != null  && !bundleSymbolicName.isEmpty()) {
            logPrefix = "Bundle '" + bundleSymbolicName + "': ";
        } else {
            logPrefix = "";
        }
        // check for attributes
        if (entry.getAttributes().length > 0) {
            log.warn("{}Attributes are not supported in header {} but this header used attributes '{}'. Maybe a directive (with key/value separator ':=') was meant instead?", logPrefix, CONTENT_HEADER, 
                    Arrays.stream(entry.getAttributes()).map(attr -> attr.getName() + "=" + attr.getValue()).collect(Collectors.joining(", ")));
        }
        // check for invalid directives
        for (NameValuePair directive : entry.getDirectives()) {
            if (!VALID_DIRECTIVES.contains(directive.getName())) {
                log.warn("{}Directive '{}' not supported in header {} but it is used with value '{}'", logPrefix, directive.getName(), CONTENT_HEADER, directive.getValue());
            }
        }
        // merge directive
        final String mergeProperties = entry.getDirectiveValue(MERGE_PROPERTIES_DIRECTIVE);
        if (mergeProperties != null) {
            this.propertyMerge = Boolean.valueOf(mergeProperties);
        } else {
            this.propertyMerge = false;
        }
        
        // merge directive
        final String mergeNodes = entry.getDirectiveValue(MERGE_NODES_DIRECTIVE);
        if (mergeNodes != null) {
            this.nodeMerge = Boolean.valueOf(mergeProperties);
        } else {
            this.nodeMerge = false;
        }
        
        // overwrite directive
        final String overwriteValue = entry.getDirectiveValue(OVERWRITE_DIRECTIVE);
        if (overwriteValue != null) {
            this.overwrite = Boolean.valueOf(overwriteValue);
        } else {
            this.overwrite = false;
        }

        // overwriteProperties directive
        final String overwritePropertiesValue = entry.getDirectiveValue(OVERWRITE_PROPERTIES_DIRECTIVE);
        if (overwritePropertiesValue != null) {
            this.overwriteProperties = Boolean.valueOf(overwritePropertiesValue);
        } else {
            this.overwriteProperties = false;
        }
        
 
        // uninstall directive
        final String uninstallValue = entry.getDirectiveValue(UNINSTALL_DIRECTIVE);
        if (uninstallValue != null) {
            this.uninstall = Boolean.valueOf(uninstallValue);
        } else {
            this.uninstall = this.overwrite;
        }

        // path directive
        final String pathValue = entry.getDirectiveValue(PATH_DIRECTIVE);
        if (pathValue != null) {
            this.target = pathValue;
        } else {
            this.target = null;
        }

        // checkin directive
        final String checkinValue = entry.getDirectiveValue(CHECKIN_DIRECTIVE);
        if (checkinValue != null) {
            this.checkin = Boolean.valueOf(checkinValue);
        } else {
            this.checkin = false;
        }

        // autoCheckout directive
        final String autoCheckoutValue = entry.getDirectiveValue(AUTOCHECKOUT_DIRECTIVE);
        if (autoCheckoutValue != null) {
            this.autoCheckout = Boolean.valueOf(autoCheckoutValue);
        } else {
            this.autoCheckout = true;
        }

        // expand directive
        this.ignoreContentReaders = new ArrayList<>();
        final String expandValue = entry.getDirectiveValue(IGNORE_CONTENT_READERS_DIRECTIVE);
        if ( expandValue != null && expandValue.length() > 0 ) {
            final StringTokenizer st = new StringTokenizer(expandValue, ",");
            while ( st.hasMoreTokens() ) {
                this.ignoreContentReaders.add(st.nextToken());
            }
        }

        // workspace directive
        final String workspaceValue = entry.getDirectiveValue(WORKSPACE_DIRECTIVE);
        if (pathValue != null) {
            this.workspace = workspaceValue;
        } else {
            this.workspace = null;
        }
    }
    
    public long getLastModified() {
        return lastModified;
    }
    
    public String getPath() {
        return this.path;
    }

    /* (non-Javadoc)
     * @see org.apache.sling.jcr.contentloader.ImportOptions#isOverwrite()
     */
    public boolean isOverwrite() {
        return this.overwrite;
    }

    /* (non-Javadoc)
     * @see org.apache.sling.jcr.contentloader.ImportOptions#isPropertyOverwrite()
     */

    public boolean isPropertyOverwrite() {
        return this.overwriteProperties;
    }

    public boolean isUninstall() {
        return this.uninstall;
    }

    /* (non-Javadoc)
     * @see org.apache.sling.jcr.contentloader.ImportOptions#isCheckin()
     */
    public boolean isCheckin() {
        return this.checkin;
    }
    
    /* (non-Javadoc)
     * @see org.apache.sling.jcr.contentloader.ImportOptions#isAutoCheckout()
     */

    public boolean isAutoCheckout() {
        return this.autoCheckout;
    }

    /* (non-Javadoc)
     * @see org.apache.sling.jcr.contentloader.ImportOptions#isIgnoredImportProvider(java.lang.String)
     */
    public boolean isIgnoredImportProvider(String extension) {
        if ( extension.startsWith(".") ) {
            extension = extension.substring(1);
        }
        return this.ignoreContentReaders.contains(extension);
    }

    public Set getIgnoredContentReaders() {
        return new HashSet<>(ignoreContentReaders);
    }

    public String getTarget() {
        return target;
    }

    public String getWorkspace() {
        return workspace;
    }


    public boolean isPropertyMerge() {
        return this.propertyMerge;
    }


    public boolean isMerge() {
        return this.nodeMerge;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy