blob: 4b93385481a2ed85a8692d5e07f16f6302da3327 [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.feature.cpconverter.vltpkg;
import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.vault.fs.api.FilterSet;
import org.apache.jackrabbit.vault.fs.api.PathFilter;
import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* Utility class to build a new {@link WorkspaceFilter} from a given base filter and a new set of paths corresponding
* to the content of the new converted content packages, which no longer contains entries that were moved to repo-init.
*/
class WorkspaceFilterBuilder {
private final WorkspaceFilter baseFilter;
private final Set<String> filteredPaths;
private final Set<String> cpPaths;
private final Set<String> extractedPaths;
/**
* Create a new {@link WorkspaceFilterBuilder}
*
* @param baseFilter The base {@link WorkspaceFilter} as computed from the original content package.
* @param filteredRepositoryPaths A set of repository paths that got moved from the original content package to repo
* init and which there should no longer be referenced in the new {@link WorkspaceFilter}.
* @param convertedRepositoryPaths A set of repository paths representing entries in the converted content packages.
* @param extractedRepositoryPaths A set of repository paths extracted from those {@link org.apache.jackrabbit.vault.util.Constants#DOT_CONTENT_XML .content.xml}
* files of the original content package that contain paths listed in the original {@link WorkspaceFilter}.
*/
WorkspaceFilterBuilder(@NotNull WorkspaceFilter baseFilter,
@NotNull Set<String> filteredRepositoryPaths,
@NotNull Set<String> convertedRepositoryPaths,
@NotNull Set<String> extractedRepositoryPaths) {
this.baseFilter = baseFilter;
this.filteredPaths = filteredRepositoryPaths;
this.cpPaths = convertedRepositoryPaths;
this.extractedPaths = extractedRepositoryPaths;
}
/**
* Build a new {@link WorkspaceFilter}
*
* @return a new {@link WorkspaceFilter}
* @throws IOException If an error occurs.
*/
@NotNull WorkspaceFilter build() throws IOException {
try {
DefaultWorkspaceFilter dwf = new DefaultWorkspaceFilter();
Map<String, PathFilterSet> propFilters = extractPropertyFilters(baseFilter);
for (PathFilterSet pfs : baseFilter.getFilterSets()) {
// add the filter to the new workspace filter if it either covers content from the converted package
// or doesn't match any of the content that has been removed to repo-init. the latter condition
// make sure filter sets without any corresponding content (i.e. removal) are not dropped.
if (coversConvertedPath(pfs) || !coversFilteredPath(pfs)) {
processPathFilterSet(dwf, pfs, propFilters);
}
}
return dwf;
} catch (ConfigurationException e) {
throw new IOException(e);
}
}
/**
* Extract all property-filters and remember them for later as DefaultWorkspaceFilter.addPropertyFilterSet
* is deprecated in favor of DefaultWorkspaceFilter.add(PathFilterSet nodeFilter, PathFilterSet propFilter).
* The map created then allows to process property-filters together with node filters.
*
* @param base A {@link WorkspaceFilter} from which to extract the property filters.
* @return A map of path (the root) to the corresponding property @{@link PathFilterSet}.
* @see WorkspaceFilter#getPropertyFilterSets()
*/
static @NotNull Map<String, PathFilterSet> extractPropertyFilters(@NotNull WorkspaceFilter base) {
Map<String, PathFilterSet> propFilters = new LinkedHashMap<>();
base.getPropertyFilterSets().forEach(pathFilterSet -> propFilters.put(pathFilterSet.getRoot(), pathFilterSet));
return propFilters;
}
private boolean coversFilteredPath(@NotNull PathFilterSet pfs) {
return filteredPaths.stream().anyMatch(pfs::covers);
}
/**
* Test if the given {@link PathFilterSet} is still required in the converted content package.
* The following two conditions are taking into account:
* - the given filter-set covers any of the paths included in the converted content package
* - the given filter-set covers any of the extracted paths hidden in a .content.xml
* - it's root path is a sibling of a manually extracted paths hidden in a .content.xml that get
* installed despite not being covered by the filter.
*
* @param pfs A {@link PathFilterSet} of the original base filter.
* @return {@code true} if the given {@code PathFilterSet} is still relevant for the new {@code WorkspaceFilter} given
* the new content of the converted package as reflected by the recorded paths.
*/
private boolean coversConvertedPath(@NotNull PathFilterSet pfs) {
return cpPaths.stream().anyMatch(pfs::covers) ||
// test if a extracted path matches or is a sibling of the filter root (see discussion in SLING-10760)
extractedPaths.stream().anyMatch(path -> path.equals(pfs.getRoot()) || Text.isSibling(path, pfs.getRoot()));
}
/**
* Populate the new {@link WorkspaceFilter} with an adjusted copy of the given {@link PathFilterSet}.
*
* @param newFilter The new {@link WorkspaceFilter} which gets populated with ethe copied (and adjusted) path filter sets.
* @param pfs A {@link PathFilterSet path filter set} extracted from the original base filter.
* @param propFilters The lookup map for {@link PathFilterSet property filters} as extracted before using {@link #extractPropertyFilters(WorkspaceFilter)}.
* @throws ConfigurationException If an error occurs while building the adjusted path filter sets.
*/
private void processPathFilterSet(@NotNull DefaultWorkspaceFilter newFilter, @NotNull PathFilterSet pfs,
@NotNull Map<String, PathFilterSet> propFilters) throws ConfigurationException {
// create a new node path-filter-set (and if existing the corresponding property filter)
PathFilterSet nodeFilterSet = copyPathFilterSet(pfs, filteredPaths);
PathFilterSet propPfs = propFilters.remove(pfs.getRoot());
if (propPfs != null) {
// note: no need to add additional exclude entries for property-filters
PathFilterSet propFilterSet = copyPathFilterSet(propPfs, Collections.emptySet());
newFilter.add(nodeFilterSet, propFilterSet);
} else {
newFilter.add(nodeFilterSet);
}
}
/**
* Create a copy of the original {@link PathFilterSet} adding additional exclude rules for all filter paths that are
* covered by the given {@link PathFilterSet}.
*
* @param pfs The original {@link PathFilterSet} as present on the original base {@link WorkspaceFilter}.
* @param filteredPaths A set of paths that have been moved from the original content package to repo-init.
* @return A copy of the given {@link PathFilterSet} plus additional exclude rules for covered filtered paths.
* @throws ConfigurationException If an error occurs while constructing the new {@link PathFilterSet}
*/
@NotNull
private static PathFilterSet copyPathFilterSet(@NotNull PathFilterSet pfs, @NotNull Set<String> filteredPaths) throws ConfigurationException {
// create a new path-filter-set
PathFilterSet filterSet = new PathFilterSet(pfs.getRoot());
filterSet.setType(pfs.getType());
filterSet.setImportMode(pfs.getImportMode());
// copy all entries to the new path-filter-set
for (FilterSet.Entry<PathFilter> entry : pfs.getEntries()) {
if (entry.isInclude()) {
filterSet.addInclude(entry.getFilter());
} else {
filterSet.addExclude(entry.getFilter());
}
}
// for all paths that got filtered out and moved to repo-init make sure they get explicitly excluded
for (String path : filteredPaths) {
if (pfs.covers(path)) {
filterSet.addExclude(new DefaultPathFilter(path));
}
}
return filterSet;
}
}