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

org.apache.sling.provisioning.model.MergeUtility Maven / Gradle / Ivy

There is a newer version: 6.5.21
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.provisioning.model;

import static org.apache.sling.provisioning.model.ModelResolveUtility.getProcessedConfiguration;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Utility for merging two models.
 *
 * @since 1.4
 */
public abstract class MergeUtility {

    /**
     * Options for specifying some parts of the merge operation.
     */
    public static class MergeOptions {

        private boolean handleRemoveRunMode = true;

        private boolean latestArtifactWins = true;

        /**
         * Returns {@code true} if the remove run mode should be respected.
         * @return {@code true} or {@code false}
         */
        public boolean isHandleRemoveRunMode() {
            return handleRemoveRunMode;
        }

        /**
         * Set to {@code true} if the remove run mode should be respected.
         * @param handleRemoveRunMode Whether the remove run mode should be respected.
         * @return This instance.
         */
        public MergeOptions setHandleRemoveRunMode(boolean handleRemoveRunMode) {
            this.handleRemoveRunMode = handleRemoveRunMode;
            return this;
        }

        /**
         * Returns {@code true} if the latest artifact should win on a merge.
         * @return {@code true} or {@code false} if the artifact with the
         *         highest version should win
         */
        public boolean isLatestArtifactWins() {
            return latestArtifactWins;
        }

        /**
         * Set to {@code true} if the latest artifact should win on a merge.
         * Set to {@code false} if the artifact with the highest version should win
         * @param latestArtifactWins Whether the latest artifact should win
         * @return This instance.
         */
        public MergeOptions setLatestArtifactWins(boolean latestArtifactWins) {
            this.latestArtifactWins = latestArtifactWins;
            return this;
        }
    }

    /**
     * Merge the additional model into the base model.
     * @param base The base model.
     * @param additional The additional model.
     */
    public static void merge(final Model base, final Model additional) {
        merge(base, additional, new MergeOptions());
    }

    /**
     * Merge the additional model into the base model.
     * 

* Merging is performed feature by feature. Each feature is treated separately. * If the base model does not have a feature from the additional model, the complete * feature is added. If the base model has a feature which is not in the additional model, * the feature is left as is. *

* For each feature, the following actions are performed: *

    *
  • If either the base feature or the additional feature has a version, then * the one with the higher version is used, the other one is skipped. A missing * version is considered the lowest possible version.
  • *
  • The feature type of the base feature is set to the type of the additional feature.
  • *
  • All additional sections of the additional feature are added to the base feature.
  • *
  • All variables from the additional feature are set on the base feature, overriding * values if already present.
  • *
  • Each run mode of the additional feature is merged into the base feature.
  • *
*

* @param base The base model. * @param additional The additional model. * @param options The merge options */ public static void merge(final Model base, final Model additional, final MergeOptions options) { // features for(final Feature feature : additional.getFeatures()) { final Feature baseFeature = base.getOrCreateFeature(feature.getName()); // version check first boolean overwrite = false; if ( baseFeature.getVersion() != null ) { if ( feature.getVersion() == null ) { continue; } final Version baseVersion = new Version(baseFeature.getVersion()); final Version addVersion = new Version(feature.getVersion()); if ( baseVersion.compareTo(addVersion) >= 0 ) { continue; } overwrite = true; } else { if ( feature.getVersion() != null ) { overwrite = true; } } if ( overwrite ) { // set version baseFeature.setVersion(feature.getVersion()); // remove everything from base feature baseFeature.getRunModes().clear(); baseFeature.getAdditionalSections().clear(); baseFeature.getVariables().clear(); baseFeature.setComment(null); } mergeComments(baseFeature, feature); baseFeature.setType(feature.getType()); // additional sections (sections are not cloned, therefore comments do not need to be merged) baseFeature.getAdditionalSections().addAll(feature.getAdditionalSections()); // variables baseFeature.getVariables().putAll(feature.getVariables()); mergeComments(baseFeature.getVariables(), feature.getVariables()); // run modes for(final RunMode runMode : feature.getRunModes()) { // check for special remove run mode final String names[] = runMode.getNames(); if ( options.isHandleRemoveRunMode() && names != null ) { if ( handleRemoveRunMode(baseFeature, runMode) ) { continue; } } final RunMode baseRunMode = baseFeature.getOrCreateRunMode(names); // artifact groups for(final ArtifactGroup group : runMode.getArtifactGroups()) { final ArtifactGroup baseGroup = baseRunMode.getOrCreateArtifactGroup(group.getStartLevel()); mergeComments(baseGroup, group); int foundStartLevel = 0; for(final Artifact artifact : group) { boolean addArtifact = true; for(final ArtifactGroup searchGroup : baseRunMode.getArtifactGroups()) { final Artifact found = searchGroup.search(artifact); if ( found != null ) { if ( options.isLatestArtifactWins() ) { searchGroup.remove(found); foundStartLevel = searchGroup.getStartLevel(); } else { try { final Version baseVersion = new Version(found.getVersion()); final Version mergeVersion = new Version(artifact.getVersion()); if ( baseVersion.compareTo(mergeVersion) <= 0 ) { searchGroup.remove(found); foundStartLevel = searchGroup.getStartLevel(); } else { addArtifact = false; } } catch ( final IllegalArgumentException iae) { // if at least one version is not a valid maven version if ( found.getVersion().compareTo(artifact.getVersion()) <= 0 ) { searchGroup.remove(found); foundStartLevel = searchGroup.getStartLevel(); } else { addArtifact = false; } } } } } if ( addArtifact ) { // artifacts are not cloned, therefore comments do not need to be merged if ( group.getStartLevel() == 0 && foundStartLevel != 0 ) { baseRunMode.getOrCreateArtifactGroup(foundStartLevel).add(artifact); } else { baseGroup.add(artifact); } } } } // configurations for(final Configuration config : runMode.getConfigurations()) { final Configuration found = baseRunMode.getOrCreateConfiguration(config.getPid(), config.getFactoryPid()); mergeConfiguration(found, config); mergeComments(found, config); } // settings for(final Map.Entry entry : runMode.getSettings() ) { baseRunMode.getSettings().put(entry.getKey(), entry.getValue()); } mergeComments(baseRunMode.getSettings(), runMode.getSettings()); } } } /** * Handle the remove run mode * @param baseFeature The base feature * @param runMode The current run mode * @return {@code true} if the current run mode is a remove run mode */ private static boolean handleRemoveRunMode(final Feature baseFeature, final RunMode runMode) { String names[] = runMode.getNames(); int removeIndex = -1; int index = 0; for(final String name : names) { if ( name.equals(ModelConstants.RUN_MODE_REMOVE) ) { removeIndex = index; break; } index++; } if ( removeIndex != -1 ) { String[] newNames = null; if ( names.length > 1 ) { newNames = new String[names.length - 1]; index = 0; for(final String name : names) { if ( !name.equals(ModelConstants.RUN_MODE_REMOVE) ) { newNames[index++] = name; } } } names = newNames; final RunMode baseRunMode = baseFeature.getRunMode(names); if ( baseRunMode != null ) { // artifact groups for(final ArtifactGroup group : runMode.getArtifactGroups()) { for(final Artifact artifact : group) { for(final ArtifactGroup searchGroup : baseRunMode.getArtifactGroups()) { final Artifact found = searchGroup.search(artifact); if ( found != null ) { searchGroup.remove(found); } } } } // configurations for(final Configuration config : runMode.getConfigurations()) { final Configuration found = baseRunMode.getConfiguration(config.getPid(), config.getFactoryPid()); if ( found != null ) { baseRunMode.getConfigurations().remove(found); } } // settings for(final Map.Entry entry : runMode.getSettings() ) { baseRunMode.getSettings().remove(entry.getKey()); } } return true; } return false; } /** * Merge two configurations * @param baseConfig The base configuration. * @param mergeConfig The merge configuration. */ private static void mergeConfiguration(final Configuration baseConfig, final Configuration mergeConfig) { // check for merge mode final boolean isNew = baseConfig.getProperties().isEmpty(); if ( isNew ) { copyConfigurationProperties(baseConfig, mergeConfig); final Object mode = mergeConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED_MODE); if ( mode != null ) { baseConfig.getProperties().put(ModelConstants.CFG_UNPROCESSED_MODE, mode); } } else { final boolean baseIsRaw = baseConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED) != null; final boolean mergeIsRaw = mergeConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED) != null; // simplest case, both are raw if ( baseIsRaw && mergeIsRaw ) { final String cfgMode = (String)mergeConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED_MODE); if ( cfgMode == null || ModelConstants.CFG_MODE_OVERWRITE.equals(cfgMode) ) { copyConfigurationProperties(baseConfig, mergeConfig); } else { final Configuration newConfig = new Configuration(baseConfig.getPid(), baseConfig.getFactoryPid()); getProcessedConfiguration(null, newConfig, baseConfig, false, null); clearConfiguration(baseConfig); copyConfigurationProperties(baseConfig, newConfig); clearConfiguration(newConfig); getProcessedConfiguration(null, newConfig, mergeConfig, false, null); if ( baseConfig.isSpecial() ) { final String baseValue = baseConfig.getProperties().get(baseConfig.getPid()).toString(); final String mergeValue = newConfig.getProperties().get(baseConfig.getPid()).toString(); baseConfig.getProperties().put(baseConfig.getPid(), baseValue + "\n" + mergeValue); } else { copyConfigurationProperties(baseConfig, newConfig); } } // another simple case, both are not raw } else if ( !baseIsRaw && !mergeIsRaw ) { // merge mode is always overwrite clearConfiguration(baseConfig); copyConfigurationProperties(baseConfig, mergeConfig); // base is not raw but merge is } else if ( !baseIsRaw && mergeIsRaw ) { final String cfgMode = (String)mergeConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED_MODE); if ( cfgMode == null || ModelConstants.CFG_MODE_OVERWRITE.equals(cfgMode) ) { clearConfiguration(baseConfig); copyConfigurationProperties(baseConfig, mergeConfig); } else { final Configuration newMergeConfig = new Configuration(mergeConfig.getPid(), mergeConfig.getFactoryPid()); getProcessedConfiguration(null, newMergeConfig, mergeConfig, false, null); if ( baseConfig.isSpecial() ) { final String baseValue = baseConfig.getProperties().get(baseConfig.getPid()).toString(); final String mergeValue = newMergeConfig.getProperties().get(baseConfig.getPid()).toString(); baseConfig.getProperties().put(baseConfig.getPid(), baseValue + "\n" + mergeValue); } else { copyConfigurationProperties(baseConfig, newMergeConfig); } } // base is raw, but merge is not raw } else { // merge mode is always overwrite clearConfiguration(baseConfig); copyConfigurationProperties(baseConfig, mergeConfig); } } } private static void clearConfiguration(final Configuration cfg) { final Set keys = new HashSet(); final Enumeration e = cfg.getProperties().keys(); while ( e.hasMoreElements() ) { keys.add(e.nextElement()); } for(final String key : keys) { cfg.getProperties().remove(key); } } private static void copyConfigurationProperties(final Configuration baseConfig, final Configuration mergeConfig) { final Enumeration e = mergeConfig.getProperties().keys(); while ( e.hasMoreElements() ) { final String key = e.nextElement(); if ( !key.equals(ModelConstants.CFG_UNPROCESSED_MODE) ) { baseConfig.getProperties().put(key, mergeConfig.getProperties().get(key)); } } } /** * Merge the comments * @param base The base model object * @param additional The additional model object * @since 1.9.0 */ public static void mergeComments(final Commentable base, final Commentable additional) { if ( base.getComment() == null ) { base.setComment(additional.getComment()); } else if ( additional.getComment() != null ) { base.setComment(base.getComment() + "\n" + additional.getComment()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy