| /* |
| * 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.jackrabbit.oak.spi.security; |
| |
| import com.google.common.base.Function; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.ObjectArrays; |
| import com.google.common.collect.Sets; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Tree; |
| import org.apache.jackrabbit.oak.plugins.tree.RootProvider; |
| import org.apache.jackrabbit.oak.plugins.tree.TreeLocation; |
| import org.apache.jackrabbit.oak.plugins.tree.TreeProvider; |
| import org.apache.jackrabbit.oak.spi.commit.CommitHook; |
| import org.apache.jackrabbit.oak.spi.commit.MoveTracker; |
| import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler; |
| import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider; |
| import org.apache.jackrabbit.oak.spi.lifecycle.CompositeInitializer; |
| import org.apache.jackrabbit.oak.spi.lifecycle.CompositeWorkspaceInitializer; |
| import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer; |
| import org.apache.jackrabbit.oak.spi.lifecycle.WorkspaceInitializer; |
| import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter; |
| import org.apache.jackrabbit.oak.stats.Monitor; |
| import org.apache.jackrabbit.oak.stats.StatisticsProvider; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.osgi.annotation.versioning.ProviderType; |
| import org.osgi.framework.Constants; |
| |
| import java.security.Principal; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| /** |
| * Abstract base implementation for {@link SecurityConfiguration}s that can |
| * combine different implementations. |
| */ |
| @ProviderType |
| public abstract class CompositeConfiguration<T extends SecurityConfiguration> implements SecurityConfiguration { |
| |
| /** |
| * Parameter used to define the ranking of a given configuration compared to |
| * other registered configuration in this aggregate. If the ranking parameter |
| * is missing a new configuration will be added at the end of the list. |
| */ |
| public static final String PARAM_RANKING = "configurationRanking"; |
| |
| /** |
| * Default ranking value used to insert a new configuration at the end of |
| * the list. |
| */ |
| private static final int NO_RANKING = Integer.MIN_VALUE; |
| |
| private final List<T> configurations = new CopyOnWriteArrayList<>(); |
| |
| private final Ranking rankings = new Ranking(); |
| |
| private final String name; |
| private final CompositeContext ctx = new CompositeContext(); |
| |
| private SecurityProvider securityProvider; |
| |
| private RootProvider rootProvider; |
| |
| private TreeProvider treeProvider; |
| |
| private T defaultConfig; |
| |
| public CompositeConfiguration(@NotNull String name) { |
| this.name = name; |
| } |
| |
| public CompositeConfiguration(@NotNull String name, @NotNull SecurityProvider securityProvider) { |
| this.name = name; |
| this.securityProvider = securityProvider; |
| } |
| |
| @Nullable |
| public T getDefaultConfig() { |
| return defaultConfig; |
| } |
| |
| public void setDefaultConfig(@NotNull T defaultConfig) { |
| this.defaultConfig = defaultConfig; |
| ctx.defaultCtx = defaultConfig.getContext(); |
| } |
| |
| public void addConfiguration(@NotNull T configuration) { |
| addConfiguration(configuration, ConfigurationParameters.EMPTY); |
| } |
| |
| public void addConfiguration(@NotNull T configuration, @NotNull ConfigurationParameters params) { |
| int ranking = configuration.getParameters().getConfigValue(PARAM_RANKING, NO_RANKING); |
| if (ranking == NO_RANKING) { |
| ranking = params.getConfigValue(Constants.SERVICE_RANKING, NO_RANKING); |
| } |
| if (ranking == NO_RANKING || configurations.isEmpty()) { |
| configurations.add(configuration); |
| } else { |
| int i = 0; |
| for (T c : configurations) { |
| int r = rankings.get(c); |
| if (ranking > r) { |
| break; |
| } else { |
| i++; |
| } |
| } |
| configurations.add(i, configuration); |
| } |
| rankings.set(configuration, ranking); |
| ctx.add(configuration); |
| } |
| |
| public void removeConfiguration(@NotNull T configuration) { |
| configurations.remove(configuration); |
| rankings.remove(configuration); |
| ctx.refresh(configurations); |
| } |
| |
| @NotNull |
| public List<T> getConfigurations() { |
| if (configurations.isEmpty() && defaultConfig != null) { |
| return ImmutableList.of(defaultConfig); |
| } else { |
| return ImmutableList.copyOf(configurations); |
| } |
| } |
| |
| public void setSecurityProvider(@NotNull SecurityProvider securityProvider) { |
| this.securityProvider = securityProvider; |
| } |
| |
| @NotNull |
| protected SecurityProvider getSecurityProvider() { |
| if (securityProvider == null) { |
| throw new IllegalStateException("SecurityProvider missing => CompositeConfiguration is not ready."); |
| } |
| return securityProvider; |
| } |
| |
| public void setRootProvider(@NotNull RootProvider rootProvider) { |
| this.rootProvider = rootProvider; |
| } |
| |
| @NotNull |
| protected RootProvider getRootProvider() { |
| if (rootProvider == null) { |
| throw new IllegalStateException("RootProvider missing."); |
| } |
| return rootProvider; |
| } |
| |
| public void setTreeProvider(@NotNull TreeProvider treeProvider) { |
| this.treeProvider = treeProvider; |
| } |
| |
| @NotNull |
| protected TreeProvider getTreeProvider() { |
| if (treeProvider == null) { |
| throw new IllegalStateException("TreeProvider missing."); |
| } |
| return treeProvider; |
| } |
| |
| //----------------------------------------------< SecurityConfiguration >--- |
| @NotNull |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| @NotNull |
| @Override |
| public ConfigurationParameters getParameters() { |
| List<T> configs = getConfigurations(); |
| ConfigurationParameters[] params = new ConfigurationParameters[configs.size()]; |
| for (int i = 0; i < configs.size(); i++) { |
| params[i] = configs.get(i).getParameters(); |
| } |
| return ConfigurationParameters.of(params); |
| } |
| |
| @NotNull |
| @Override |
| public WorkspaceInitializer getWorkspaceInitializer() { |
| return new CompositeWorkspaceInitializer(Lists.transform(getConfigurations(), new Function<T, WorkspaceInitializer>() { |
| @Override |
| public WorkspaceInitializer apply(T securityConfiguration) { |
| return securityConfiguration.getWorkspaceInitializer(); |
| } |
| })); |
| } |
| |
| @NotNull |
| @Override |
| public RepositoryInitializer getRepositoryInitializer() { |
| return new CompositeInitializer(Lists.transform(getConfigurations(), new Function<T, RepositoryInitializer>() { |
| @Override |
| public RepositoryInitializer apply(T securityConfiguration) { |
| return securityConfiguration.getRepositoryInitializer(); |
| } |
| })); |
| } |
| |
| @NotNull |
| @Override |
| public List<? extends CommitHook> getCommitHooks(@NotNull final String workspaceName) { |
| Iterable<CommitHook> t = Iterables.concat(Lists.transform(getConfigurations(), new Function<T, List<? extends CommitHook>>() { |
| @Override |
| public List<? extends CommitHook> apply(T securityConfiguration) { |
| return securityConfiguration.getCommitHooks(workspaceName); |
| } |
| })); |
| return ImmutableList.copyOf(t); |
| } |
| |
| @NotNull |
| @Override |
| public List<? extends ValidatorProvider> getValidators(@NotNull final String workspaceName, @NotNull final Set<Principal> principals, @NotNull final MoveTracker moveTracker) { |
| Iterable<ValidatorProvider> t = Iterables.concat(Lists.transform(getConfigurations(), securityConfiguration -> securityConfiguration.getValidators(workspaceName, principals, moveTracker))); |
| return ImmutableList.copyOf(t); |
| } |
| |
| @NotNull |
| @Override |
| public List<ThreeWayConflictHandler> getConflictHandlers() { |
| return ImmutableList.copyOf(Iterables.concat(Lists.transform(getConfigurations(), securityConfiguration -> securityConfiguration.getConflictHandlers()))); |
| } |
| |
| @NotNull |
| @Override |
| public List<ProtectedItemImporter> getProtectedItemImporters() { |
| Iterable<ProtectedItemImporter> t = Iterables.concat(Lists.transform(getConfigurations(), new Function<T, List<? extends ProtectedItemImporter>>() { |
| @Override |
| public List<? extends ProtectedItemImporter> apply(T securityConfiguration) { |
| return securityConfiguration.getProtectedItemImporters(); |
| } |
| })); |
| return ImmutableList.copyOf(t); |
| } |
| |
| @NotNull |
| @Override |
| public Context getContext() { |
| return ctx; |
| } |
| |
| @NotNull |
| @Override |
| public Iterable<Monitor<?>> getMonitors(@NotNull StatisticsProvider statisticsProvider) { |
| return Iterables.concat( |
| Iterables.transform(getConfigurations(), securityConfiguration -> securityConfiguration.getMonitors(statisticsProvider))); |
| } |
| |
| private static final class Ranking { |
| |
| private Map<SecurityConfiguration, Integer> m = new ConcurrentHashMap<>(); |
| |
| private int get(@NotNull SecurityConfiguration configuration) { |
| Integer ranking = m.get(configuration); |
| if (ranking == null) { |
| return NO_RANKING; |
| } else { |
| return ranking; |
| } |
| } |
| |
| private void set(@NotNull SecurityConfiguration configuration, int ranking) { |
| if (ranking != NO_RANKING) { |
| m.put(configuration, ranking); |
| } |
| } |
| |
| private void remove(@NotNull SecurityConfiguration configuration) { |
| m.remove(configuration); |
| } |
| } |
| |
| private static final class CompositeContext implements Context { |
| |
| @NotNull |
| private Context defaultCtx = DEFAULT; |
| @Nullable |
| private Context[] delegatees = null; |
| |
| private void refresh(@NotNull List<? extends SecurityConfiguration> configurations) { |
| Set<Context> s = Sets.newLinkedHashSetWithExpectedSize(configurations.size()); |
| for (Context c : Iterables.transform(configurations, ContextFunction.INSTANCE)) { |
| if (DEFAULT != c) { |
| s.add(c); |
| } |
| } |
| delegatees = (s.isEmpty()) ? null : s.toArray(new Context[0]); |
| } |
| |
| private void add(@NotNull SecurityConfiguration configuration) { |
| Context c = configuration.getContext(); |
| if (DEFAULT != c) { |
| if (delegatees == null) { |
| delegatees = new Context[] {c}; |
| } else { |
| for (Context ctx : delegatees) { |
| if (ctx.equals(c)) { |
| return; |
| } |
| } |
| delegatees = ObjectArrays.concat(delegatees, c); |
| } |
| } |
| } |
| |
| @Override |
| public boolean definesProperty(@NotNull Tree parent, @NotNull PropertyState property) { |
| if (delegatees == null) { |
| return defaultCtx.definesProperty(parent, property); |
| } |
| for (Context ctx : delegatees) { |
| if (ctx.definesProperty(parent, property)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean definesContextRoot(@NotNull Tree tree) { |
| if (delegatees == null) { |
| return defaultCtx.definesContextRoot(tree); |
| } |
| for (Context ctx : delegatees) { |
| if (ctx.definesContextRoot(tree)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean definesTree(@NotNull Tree tree) { |
| if (delegatees == null) { |
| return defaultCtx.definesTree(tree); |
| } |
| for (Context ctx : delegatees) { |
| if (ctx.definesTree(tree)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean definesLocation(@NotNull TreeLocation location) { |
| if (delegatees == null) { |
| return defaultCtx.definesLocation(location); |
| } |
| for (Context ctx : delegatees) { |
| if (ctx.definesLocation(location)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean definesInternal(@NotNull Tree tree) { |
| if (delegatees == null) { |
| return defaultCtx.definesInternal(tree); |
| } |
| for (Context ctx : delegatees) { |
| if (ctx.definesInternal(tree)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| private static final class ContextFunction implements Function<SecurityConfiguration, Context> { |
| |
| private static final ContextFunction INSTANCE = new ContextFunction(); |
| |
| private ContextFunction() {} |
| |
| @Override |
| public Context apply(SecurityConfiguration input) { |
| return input.getContext(); |
| } |
| } |
| } |