blob: 06a2e846252a98dcc20d3161a61a316593e5d808 [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.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();
}
}
}