| /* |
| * 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; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.google.common.collect.Lists.newArrayList; |
| import static java.util.Collections.emptyMap; |
| import static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean; |
| |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.ScheduledThreadPoolExecutor; |
| import java.util.concurrent.SynchronousQueue; |
| import java.util.concurrent.ThreadFactory; |
| import java.util.concurrent.ThreadPoolExecutor; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.function.Supplier; |
| |
| import javax.jcr.NoSuchWorkspaceException; |
| import javax.management.JMException; |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| import javax.management.StandardMBean; |
| import javax.security.auth.login.LoginException; |
| |
| 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.io.Closer; |
| |
| import org.apache.jackrabbit.oak.api.CommitFailedException; |
| import org.apache.jackrabbit.oak.api.ContentRepository; |
| import org.apache.jackrabbit.oak.api.ContentSession; |
| import org.apache.jackrabbit.oak.api.Descriptors; |
| import org.apache.jackrabbit.oak.api.Root; |
| import org.apache.jackrabbit.oak.api.jmx.QueryEngineSettingsMBean; |
| import org.apache.jackrabbit.oak.api.jmx.RepositoryManagementMBean; |
| import org.apache.jackrabbit.oak.commons.IOUtils; |
| import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser; |
| import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean; |
| import org.apache.jackrabbit.oak.core.ContentRepositoryImpl; |
| import org.apache.jackrabbit.oak.management.RepositoryManager; |
| import org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.commit.ConflictHook; |
| import org.apache.jackrabbit.oak.plugins.commit.ConflictValidatorProvider; |
| import org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate; |
| import org.apache.jackrabbit.oak.plugins.index.CompositeIndexEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.index.IndexConstants; |
| import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.index.IndexMBeanRegistration; |
| import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; |
| import org.apache.jackrabbit.oak.plugins.index.counter.NodeCounterEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.index.counter.jmx.NodeCounter; |
| import org.apache.jackrabbit.oak.plugins.index.counter.jmx.NodeCounterMBean; |
| import org.apache.jackrabbit.oak.plugins.index.counter.jmx.NodeCounterOld; |
| import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider; |
| import org.apache.jackrabbit.oak.plugins.index.property.OrderedPropertyIndexEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider; |
| import org.apache.jackrabbit.oak.plugins.index.property.jmx.PropertyIndexAsyncReindex; |
| import org.apache.jackrabbit.oak.plugins.index.property.jmx.PropertyIndexAsyncReindexMBean; |
| import org.apache.jackrabbit.oak.plugins.index.reference.ReferenceEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.index.reference.ReferenceIndexProvider; |
| import org.apache.jackrabbit.oak.plugins.itemsave.ItemSaveValidatorProvider; |
| import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; |
| import org.apache.jackrabbit.oak.plugins.name.NameValidatorProvider; |
| import org.apache.jackrabbit.oak.plugins.name.NamespaceEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.nodetype.TypeEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.observation.ChangeCollectorProvider; |
| import org.apache.jackrabbit.oak.plugins.version.VersionHook; |
| import org.apache.jackrabbit.oak.query.QueryEngineSettings; |
| import org.apache.jackrabbit.oak.query.stats.QueryStatsMBean; |
| import org.apache.jackrabbit.oak.security.internal.SecurityProviderBuilder; |
| import org.apache.jackrabbit.oak.spi.commit.CompositeConflictHandler; |
| import org.apache.jackrabbit.oak.spi.commit.CommitHook; |
| import org.apache.jackrabbit.oak.spi.commit.CommitInfo; |
| import org.apache.jackrabbit.oak.spi.commit.CompositeEditorProvider; |
| import org.apache.jackrabbit.oak.spi.commit.CompositeHook; |
| import org.apache.jackrabbit.oak.spi.commit.ConflictHandler; |
| import org.apache.jackrabbit.oak.spi.commit.ConflictHandlers; |
| import org.apache.jackrabbit.oak.spi.commit.Editor; |
| import org.apache.jackrabbit.oak.spi.commit.EditorHook; |
| import org.apache.jackrabbit.oak.spi.commit.EditorProvider; |
| import org.apache.jackrabbit.oak.spi.commit.Observable; |
| import org.apache.jackrabbit.oak.spi.commit.Observer; |
| import org.apache.jackrabbit.oak.spi.commit.ResetCommitAttributeHook; |
| import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler; |
| import org.apache.jackrabbit.oak.spi.lifecycle.CompositeInitializer; |
| import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer; |
| import org.apache.jackrabbit.oak.spi.lifecycle.WorkspaceInitializer; |
| import org.apache.jackrabbit.oak.spi.query.CompositeQueryIndexProvider; |
| import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; |
| import org.apache.jackrabbit.oak.spi.query.QueryIndexProviderAware; |
| import org.apache.jackrabbit.oak.spi.query.QueryLimits; |
| import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration; |
| import org.apache.jackrabbit.oak.spi.security.SecurityProvider; |
| import org.apache.jackrabbit.oak.spi.state.Clusterable; |
| import org.apache.jackrabbit.oak.spi.state.NodeBuilder; |
| import org.apache.jackrabbit.oak.spi.state.NodeState; |
| import org.apache.jackrabbit.oak.spi.state.NodeStore; |
| import org.apache.jackrabbit.oak.spi.whiteboard.CompositeRegistration; |
| import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard; |
| import org.apache.jackrabbit.oak.spi.whiteboard.Registration; |
| import org.apache.jackrabbit.oak.spi.whiteboard.Tracker; |
| import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; |
| import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAware; |
| import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils; |
| import org.apache.jackrabbit.oak.spi.descriptors.AggregatingDescriptors; |
| import org.apache.jackrabbit.oak.stats.StatisticsProvider; |
| import org.jetbrains.annotations.NotNull; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Builder class for constructing {@link ContentRepository} instances with |
| * a set of specified plugin components. This class acts as a public facade |
| * that hides the internal implementation classes and the details of how |
| * they get instantiated and wired together. |
| * |
| * @since Oak 0.6 |
| */ |
| public class Oak { |
| private static final Logger LOG = LoggerFactory.getLogger(Oak.class); |
| |
| /** |
| * Constant for the default workspace name |
| */ |
| public static final String DEFAULT_WORKSPACE_NAME = "default"; |
| |
| private final NodeStore store; |
| |
| private final List<RepositoryInitializer> initializers = newArrayList(); |
| |
| private AnnotatedQueryEngineSettings queryEngineSettings = new AnnotatedQueryEngineSettings(); |
| |
| private final List<QueryIndexProvider> queryIndexProviders = newArrayList(); |
| |
| private final List<IndexEditorProvider> indexEditorProviders = newArrayList(); |
| |
| private final List<CommitHook> commitHooks = newArrayList(); |
| |
| private final List<Observer> observers = Lists.newArrayList(); |
| |
| private List<EditorProvider> editorProviders = newArrayList(); |
| |
| private CompositeConflictHandler conflictHandler; |
| |
| private SecurityProvider securityProvider; |
| |
| private ScheduledExecutorService scheduledExecutor; |
| |
| private Executor executor; |
| |
| private final Closer closer = Closer.create(); |
| |
| private ContentRepository contentRepository; |
| |
| private Clusterable clusterable; |
| |
| /** |
| * Default {@code ScheduledExecutorService} used for scheduling background tasks. |
| * This default spawns up to 32 background thread on an as need basis. Idle |
| * threads are pruned after one minute. |
| * @return fresh ScheduledExecutorService |
| */ |
| public static ScheduledExecutorService defaultScheduledExecutor() { |
| ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(32, new ThreadFactory() { |
| private final AtomicInteger counter = new AtomicInteger(); |
| |
| @Override |
| public Thread newThread(@NotNull Runnable r) { |
| Thread thread = new Thread(r, createName()); |
| thread.setDaemon(true); |
| return thread; |
| } |
| |
| private String createName() { |
| return "oak-scheduled-executor-" + counter.getAndIncrement(); |
| } |
| }); |
| executor.setKeepAliveTime(1, TimeUnit.MINUTES); |
| executor.allowCoreThreadTimeOut(true); |
| return executor; |
| } |
| |
| /** |
| * Default {@code ExecutorService} used for scheduling concurrent tasks. |
| * This default spawns as many threads as required with a priority of |
| * {@code Thread.MIN_PRIORITY}. Idle threads are pruned after one minute. |
| * @return fresh ExecutorService |
| */ |
| public static ExecutorService defaultExecutorService() { |
| ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, |
| new SynchronousQueue<Runnable>(), new ThreadFactory() { |
| private final AtomicInteger counter = new AtomicInteger(); |
| |
| @Override |
| public Thread newThread(@NotNull Runnable r) { |
| Thread thread = new Thread(r, createName()); |
| thread.setDaemon(true); |
| thread.setPriority(Thread.MIN_PRIORITY); |
| return thread; |
| } |
| |
| private String createName() { |
| return "oak-executor-" + counter.getAndIncrement(); |
| } |
| }); |
| executor.setKeepAliveTime(1, TimeUnit.MINUTES); |
| executor.allowCoreThreadTimeOut(true); |
| return executor; |
| } |
| |
| private synchronized ScheduledExecutorService getScheduledExecutor() { |
| if (scheduledExecutor == null) { |
| scheduledExecutor = defaultScheduledExecutor(); |
| closer.register(new ExecutorCloser(scheduledExecutor)); |
| } |
| return scheduledExecutor; |
| } |
| |
| private synchronized Executor getExecutor() { |
| if (executor == null) { |
| ExecutorService executorService = defaultExecutorService(); |
| executor = executorService; |
| closer.register(new ExecutorCloser(executorService)); |
| } |
| return executor; |
| } |
| |
| private MBeanServer mbeanServer; |
| |
| private String defaultWorkspaceName = DEFAULT_WORKSPACE_NAME; |
| |
| @SuppressWarnings("unchecked") |
| private static <T> T getValue( |
| Map<?, ?> properties, String name, Class<T> type, T def) { |
| Object value = properties.get(name); |
| if (type.isInstance(value)) { |
| return (T) value; |
| } else { |
| return def; |
| } |
| } |
| |
| private static <T> T getValue( |
| Map<?, ?> properties, String name, Class<T> type) { |
| return getValue(properties, name, type, null); |
| } |
| |
| private Whiteboard whiteboard = new DefaultWhiteboard() { |
| @Override |
| public <T> Registration register( |
| final Class<T> type, T service, Map<?, ?> properties) { |
| final Registration registration = |
| super.register(type, service, properties); |
| |
| final Closer observerSubscription = Closer.create(); |
| Future<?> future = null; |
| if (type == Runnable.class) { |
| Runnable runnable = (Runnable) service; |
| Long period = getValue(properties, "scheduler.period", Long.class); |
| if (period != null) { |
| Boolean concurrent = getValue( |
| properties, "scheduler.concurrent", |
| Boolean.class, Boolean.FALSE); |
| if (concurrent) { |
| future = getScheduledExecutor().scheduleAtFixedRate( |
| runnable, period, period, TimeUnit.SECONDS); |
| } else { |
| future = getScheduledExecutor().scheduleWithFixedDelay( |
| runnable, period, period, TimeUnit.SECONDS); |
| } |
| } |
| } else if (type == Observer.class && store instanceof Observable) { |
| observerSubscription.register(((Observable) store).addObserver((Observer) service)); |
| } |
| |
| ObjectName objectName = null; |
| Object name = properties.get("jmx.objectname"); |
| if (mbeanServer != null && name != null) { |
| try { |
| if (name instanceof ObjectName) { |
| objectName = (ObjectName) name; |
| } else { |
| objectName = new ObjectName(String.valueOf(name)); |
| } |
| |
| if (type.getName().equals(service.getClass().getName().concat("MBean")) |
| || service instanceof StandardMBean){ |
| mbeanServer.registerMBean(service, objectName); |
| } else { |
| //Wrap the MBean in std MBean |
| mbeanServer.registerMBean(new StandardMBean(service, type), objectName); |
| } |
| |
| } catch (JMException e) { |
| LOG.warn("Unexpected exception while registering MBean of type [{}] " + |
| "against name [{}]", type, objectName, e); |
| } |
| } |
| |
| final Future<?> f = future; |
| final ObjectName on = objectName; |
| return new Registration() { |
| @Override |
| public void unregister() { |
| if (f != null) { |
| f.cancel(false); |
| } |
| if (on != null) { |
| try { |
| mbeanServer.unregisterMBean(on); |
| } catch (JMException e) { |
| LOG.warn("Unexpected exception while unregistering MBean of type {} " + |
| "against name {} ", type, on, e); |
| } |
| } |
| try { |
| observerSubscription.close(); |
| } catch (IOException e) { |
| LOG.warn("Unexpected IOException while unsubscribing observer", e); |
| } |
| |
| registration.unregister(); |
| } |
| }; |
| } |
| }; |
| |
| /** |
| * Map containing the (names -> delayInSecods) of the background indexing |
| * tasks that need to be started with this repository. A {@code null} value |
| * means no background tasks will run. |
| */ |
| private Map<String, Long> asyncTasks; |
| |
| private boolean failOnMissingIndexProvider; |
| |
| public Oak(NodeStore store) { |
| this.store = checkNotNull(store); |
| } |
| |
| public Oak() { |
| this(new MemoryNodeStore()); |
| // this(new DocumentMK.Builder().open()); |
| // this(new LogWrapper(new DocumentMK.Builder().open())); |
| } |
| |
| /** |
| * Define the current repository as being a {@link Clusterable} one. |
| * |
| * @param c |
| * @return |
| */ |
| @NotNull |
| public Oak with(@NotNull Clusterable c) { |
| this.clusterable = checkNotNull(c); |
| return this; |
| } |
| |
| /** |
| * Sets the default workspace name that should be used in case of login |
| * with {@code null} workspace name. If this method has not been called |
| * some internal default value will be used. |
| * |
| * @param defaultWorkspaceName The name of the default workspace. |
| * @return this builder. |
| */ |
| @NotNull |
| public Oak with(@NotNull String defaultWorkspaceName) { |
| this.defaultWorkspaceName = checkNotNull(defaultWorkspaceName); |
| return this; |
| } |
| |
| @NotNull |
| public Oak with(@NotNull RepositoryInitializer initializer) { |
| initializers.add(checkNotNull(initializer)); |
| return this; |
| } |
| |
| @NotNull |
| public Oak with(@NotNull QueryLimits settings) { |
| this.queryEngineSettings.setFailTraversal(settings.getFailTraversal()); |
| this.queryEngineSettings.setFullTextComparisonWithoutIndex(settings.getFullTextComparisonWithoutIndex()); |
| this.queryEngineSettings.setLimitInMemory(settings.getLimitInMemory()); |
| this.queryEngineSettings.setLimitReads(settings.getLimitReads()); |
| this.queryEngineSettings.setStrictPathRestriction(settings.getStrictPathRestriction()); |
| return this; |
| } |
| |
| /** |
| * Associates the given query index provider with the repository to |
| * be created. |
| * |
| * @param provider query index provider |
| * @return this builder |
| */ |
| @NotNull |
| public Oak with(@NotNull QueryIndexProvider provider) { |
| queryIndexProviders.add(checkNotNull(provider)); |
| return this; |
| } |
| |
| /** |
| * Associates the given index hook provider with the repository to |
| * be created. |
| * |
| * @param provider index hook provider |
| * @return this builder |
| */ |
| @NotNull |
| public Oak with(@NotNull IndexEditorProvider provider) { |
| indexEditorProviders.add(checkNotNull(provider)); |
| return this; |
| } |
| |
| /** |
| * Associates the given commit hook with the repository to be created. |
| * |
| * @param hook commit hook |
| * @return this builder |
| */ |
| @NotNull |
| public Oak with(@NotNull CommitHook hook) { |
| checkNotNull(hook); |
| withEditorHook(); |
| commitHooks.add(hook); |
| return this; |
| } |
| |
| /** |
| * Turns all currently tracked editors to an editor commit hook and |
| * associates that hook with the repository to be created. This way |
| * a sequence of {@code with()} calls that alternates between editors |
| * and other commit hooks will have all the editors in the correct |
| * order while still being able to leverage the performance gains of |
| * multiple editors iterating over the changes simultaneously. |
| */ |
| private void withEditorHook() { |
| if (!editorProviders.isEmpty()) { |
| commitHooks.add(new EditorHook( |
| CompositeEditorProvider.compose(editorProviders))); |
| editorProviders = newArrayList(); |
| } |
| } |
| |
| /** |
| * Associates the given editor provider with the repository to be created. |
| * |
| * @param provider editor provider |
| * @return this builder |
| */ |
| @NotNull |
| public Oak with(@NotNull EditorProvider provider) { |
| editorProviders.add(checkNotNull(provider)); |
| return this; |
| } |
| |
| /** |
| * Associates the given editor with the repository to be created. |
| * |
| * @param editor editor |
| * @return this builder |
| */ |
| @NotNull |
| public Oak with(@NotNull final Editor editor) { |
| checkNotNull(editor); |
| return with(new EditorProvider() { |
| @Override @NotNull |
| public Editor getRootEditor( |
| NodeState before, NodeState after, |
| NodeBuilder builder, CommitInfo info) { |
| return editor; |
| } |
| }); |
| } |
| |
| @NotNull |
| public Oak with(@NotNull SecurityProvider securityProvider) { |
| this.securityProvider = checkNotNull(securityProvider); |
| return this; |
| } |
| |
| /** |
| * Associates the given conflict handler with the repository to be created. |
| * |
| * @param conflictHandler conflict handler |
| * @return this builder |
| * @deprecated Use {@link #with(ThreeWayConflictHandler)} instead |
| */ |
| @Deprecated |
| @NotNull |
| public Oak with(@NotNull ConflictHandler conflictHandler) { |
| return with(ConflictHandlers.wrap(conflictHandler)); |
| } |
| |
| @NotNull |
| public Oak with(@NotNull ThreeWayConflictHandler conflictHandler) { |
| checkNotNull(conflictHandler); |
| withEditorHook(); |
| |
| if (this.conflictHandler == null) { |
| if (conflictHandler instanceof CompositeConflictHandler) { |
| this.conflictHandler = (CompositeConflictHandler) conflictHandler; |
| } else { |
| this.conflictHandler = new CompositeConflictHandler(); |
| this.conflictHandler.addHandler(conflictHandler); |
| } |
| commitHooks.add(new ConflictHook(conflictHandler)); |
| } else { |
| this.conflictHandler.addHandler(conflictHandler); |
| } |
| return this; |
| } |
| |
| @NotNull |
| public Oak with(@NotNull ScheduledExecutorService scheduledExecutor) { |
| this.scheduledExecutor = checkNotNull(scheduledExecutor); |
| return this; |
| } |
| |
| @NotNull |
| public Oak with(@NotNull Executor executor) { |
| this.executor = checkNotNull(executor); |
| return this; |
| } |
| |
| @NotNull |
| public Oak with(@NotNull MBeanServer mbeanServer) { |
| this.mbeanServer = checkNotNull(mbeanServer); |
| return this; |
| } |
| |
| @NotNull |
| public Oak with(@NotNull Whiteboard whiteboard) { |
| this.whiteboard = checkNotNull(whiteboard); |
| QueryEngineSettings queryEngineSettings = WhiteboardUtils.getService(whiteboard, QueryEngineSettings.class); |
| if (queryEngineSettings != null) { |
| this.queryEngineSettings = new AnnotatedQueryEngineSettings(queryEngineSettings); |
| } |
| StatisticsProvider statisticsProvider = WhiteboardUtils.getService(whiteboard, StatisticsProvider.class); |
| if (statisticsProvider != null) { |
| QueryEngineSettings newSettings = new QueryEngineSettings(statisticsProvider); |
| newSettings.setFullTextComparisonWithoutIndex(this.queryEngineSettings.settings.getFullTextComparisonWithoutIndex()); |
| newSettings.setFailTraversal(this.queryEngineSettings.getFailTraversal()); |
| newSettings.setFastQuerySize(this.queryEngineSettings.isFastQuerySize()); |
| newSettings.setLimitInMemory(this.queryEngineSettings.getLimitInMemory()); |
| newSettings.setLimitReads(this.queryEngineSettings.getLimitReads()); |
| this.queryEngineSettings = new AnnotatedQueryEngineSettings(newSettings); |
| } |
| |
| return this; |
| } |
| |
| @NotNull |
| public Oak with(@NotNull Observer observer) { |
| observers.add(checkNotNull(observer)); |
| return this; |
| } |
| |
| /** |
| * <p> |
| * Enable the asynchronous (background) indexing behavior. |
| * </p> |
| * <p> |
| * Please note that when enabling the background indexer, you need to take |
| * care of calling |
| * <code>#shutdown</code> on the <code>executor</code> provided for this Oak instance. |
| * </p> |
| * @deprecated Use {@link Oak#withAsyncIndexing(String, long)} instead |
| */ |
| @Deprecated |
| public Oak withAsyncIndexing() { |
| return withAsyncIndexing("async", 5); |
| } |
| |
| public Oak withFailOnMissingIndexProvider(){ |
| failOnMissingIndexProvider = true; |
| return this; |
| } |
| |
| public Oak withAtomicCounter() { |
| return with(new AtomicCounterEditorProvider( |
| new Supplier<Clusterable>() { |
| @Override |
| public Clusterable get() { |
| return clusterable; |
| } |
| }, |
| new Supplier<ScheduledExecutorService>() { |
| @Override |
| public ScheduledExecutorService get() { |
| return scheduledExecutor; |
| } |
| }, |
| new Supplier<NodeStore>() { |
| @Override |
| public NodeStore get() { |
| return store; |
| } |
| }, |
| new Supplier<Whiteboard>() { |
| @Override |
| public Whiteboard get() { |
| return whiteboard; |
| } |
| })); |
| } |
| |
| /** |
| * <p> |
| * Enable the asynchronous (background) indexing behavior for the provided |
| * task name. |
| * </p> |
| * <p> |
| * Please note that when enabling the background indexer, you need to take |
| * care of calling |
| * <code>#shutdown</code> on the <code>executor</code> provided for this Oak instance. |
| * </p> |
| */ |
| public Oak withAsyncIndexing(@NotNull String name, long delayInSeconds) { |
| if (this.asyncTasks == null) { |
| asyncTasks = new HashMap<String, Long>(); |
| } |
| checkState(delayInSeconds > 0, "delayInSeconds value must be > 0"); |
| asyncTasks.put(AsyncIndexUpdate.checkValidName(name), delayInSeconds); |
| return this; |
| } |
| |
| @NotNull |
| public Whiteboard getWhiteboard() { |
| return this.whiteboard; |
| } |
| |
| /** |
| * Returns the content repository instance created with the given |
| * configuration. If the repository doesn't exist yet, a new instance will |
| * be created and returned for each subsequent call of this method. |
| * |
| * @return content repository |
| */ |
| public ContentRepository createContentRepository() { |
| if (contentRepository == null) { |
| try { |
| contentRepository = createNewContentRepository(); |
| } catch ( RuntimeException e ) { |
| IOUtils.closeQuietly(closer); |
| throw e; |
| } |
| } |
| |
| return contentRepository; |
| } |
| |
| private void initialContent(IndexEditorProvider indexEditors, QueryIndexProvider indexProvider) { |
| List<CommitHook> initHooks = new ArrayList<CommitHook>(commitHooks); |
| initHooks.add(0, ResetCommitAttributeHook.INSTANCE); |
| initHooks.add(new EditorHook(new IndexUpdateProvider(indexEditors))); |
| |
| CommitHook initHook = CompositeHook.compose(initHooks); |
| OakInitializer.initialize(store, new CompositeInitializer(initializers), initHook); |
| |
| // FIXME: OAK-810 move to proper workspace initialization |
| // initialize default workspace |
| Iterable<WorkspaceInitializer> workspaceInitializers = Iterables.transform(securityProvider.getConfigurations(), |
| new Function<SecurityConfiguration, WorkspaceInitializer>() { |
| @Override |
| public WorkspaceInitializer apply(SecurityConfiguration sc) { |
| WorkspaceInitializer wi = sc.getWorkspaceInitializer(); |
| if (wi instanceof QueryIndexProviderAware) { |
| ((QueryIndexProviderAware) wi).setQueryIndexProvider(indexProvider); |
| } |
| return wi; |
| } |
| }); |
| OakInitializer.initialize(workspaceInitializers, store, defaultWorkspaceName, initHook); |
| } |
| |
| private ContentRepository createNewContentRepository() { |
| if (securityProvider instanceof WhiteboardAware) { |
| ((WhiteboardAware) securityProvider).setWhiteboard(whiteboard); |
| } |
| for (SecurityConfiguration sc : securityProvider.getConfigurations()) { |
| RepositoryInitializer ri = sc.getRepositoryInitializer(); |
| if (ri != RepositoryInitializer.DEFAULT) { |
| initializers.add(ri); |
| } |
| for (ThreeWayConflictHandler tch : sc.getConflictHandlers()) { |
| with(tch); |
| } |
| } |
| |
| final RepoStateCheckHook repoStateCheckHook = new RepoStateCheckHook(); |
| closer.register(repoStateCheckHook); |
| final List<Registration> regs = Lists.newArrayList(); |
| closer.register( () -> new CompositeRegistration(regs).unregister() ); |
| regs.add(whiteboard.register(Executor.class, getExecutor(), Collections.emptyMap())); |
| |
| IndexEditorProvider indexEditors = CompositeIndexEditorProvider.compose(indexEditorProviders); |
| QueryIndexProvider indexProvider = CompositeQueryIndexProvider.compose(queryIndexProviders); |
| |
| // force serialize editors |
| withEditorHook(); |
| commitHooks.add(repoStateCheckHook); |
| |
| initialContent(indexEditors, indexProvider); |
| |
| if (asyncTasks != null) { |
| IndexMBeanRegistration indexRegistration = new IndexMBeanRegistration( |
| whiteboard); |
| regs.add(indexRegistration); |
| for (Entry<String, Long> t : asyncTasks.entrySet()) { |
| AsyncIndexUpdate task = new AsyncIndexUpdate(t.getKey(), store, |
| indexEditors); |
| indexRegistration.registerAsyncIndexer(task, t.getValue()); |
| closer.register(task); |
| } |
| |
| PropertyIndexAsyncReindex asyncPI = new PropertyIndexAsyncReindex( |
| new AsyncIndexUpdate(IndexConstants.ASYNC_REINDEX_VALUE, |
| store, indexEditors, true), getExecutor()); |
| regs.add(registerMBean(whiteboard, |
| PropertyIndexAsyncReindexMBean.class, asyncPI, |
| PropertyIndexAsyncReindexMBean.TYPE, "async")); |
| } |
| |
| if (NodeCounter.USE_OLD_COUNTER) { |
| regs.add(registerMBean(whiteboard, NodeCounterMBean.class, |
| new NodeCounterOld(store), NodeCounterMBean.TYPE, "nodeCounter")); |
| } else { |
| regs.add(registerMBean(whiteboard, NodeCounterMBean.class, |
| new NodeCounter(store), NodeCounterMBean.TYPE, "nodeCounter")); |
| } |
| |
| regs.add(registerMBean(whiteboard, QueryEngineSettingsMBean.class, |
| queryEngineSettings, QueryEngineSettingsMBean.TYPE, "settings")); |
| |
| regs.add(registerMBean(whiteboard, QueryStatsMBean.class, |
| queryEngineSettings.getQueryStats(), QueryStatsMBean.TYPE, "Oak Query Statistics (Extended)")); |
| |
| queryEngineSettings.unwrap().getQueryValidator().init(store); |
| |
| // add index hooks later to prevent the OakInitializer to do excessive indexing |
| commitHooks.add(new EditorHook(new IndexUpdateProvider(indexEditors, failOnMissingIndexProvider))); |
| |
| // Register observer last to prevent sending events while initialising |
| for (Observer observer : observers) { |
| regs.add(whiteboard.register(Observer.class, observer, emptyMap())); |
| } |
| |
| RepositoryManager repositoryManager = new RepositoryManager(whiteboard); |
| regs.add(registerMBean(whiteboard, RepositoryManagementMBean.class, repositoryManager, |
| RepositoryManagementMBean.TYPE, repositoryManager.getName())); |
| |
| CommitHook composite = CompositeHook.compose(commitHooks); |
| regs.add(whiteboard.register(CommitHook.class, composite, Collections.emptyMap())); |
| |
| final Tracker<Descriptors> t = whiteboard.track(Descriptors.class); |
| |
| return new ContentRepositoryImpl( |
| store, |
| composite, |
| defaultWorkspaceName, |
| queryEngineSettings.unwrap(), |
| indexProvider, |
| securityProvider, |
| new AggregatingDescriptors(t)) { |
| @Override |
| public void close() throws IOException { |
| super.close(); |
| closer.close(); |
| } |
| }; |
| } |
| |
| /** |
| * Creates a content repository with the given configuration |
| * and logs in to the default workspace with no credentials, |
| * returning the resulting content session. |
| * <p> |
| * This method exists mostly as a convenience for one-off tests, |
| * as there's no way to create other sessions for accessing the |
| * same repository. |
| * <p> |
| * There is typically no need to explicitly close the returned |
| * session unless the repository has explicitly been configured |
| * to reserve some resources until all sessions have been closed. |
| * The repository will be garbage collected once the session is no |
| * longer used. |
| * |
| * @return content session |
| */ |
| public ContentSession createContentSession() { |
| try { |
| return createContentRepository().login(null, null); |
| } catch (NoSuchWorkspaceException e) { |
| throw new IllegalStateException("Default workspace not found", e); |
| } catch (LoginException e) { |
| throw new IllegalStateException("Anonymous login not allowed", e); |
| } |
| } |
| |
| /** |
| * Creates a content repository with the given configuration |
| * and returns a {@link Root} instance after logging in to the |
| * default workspace with no credentials. |
| * <p> |
| * This method exists mostly as a convenience for one-off tests, as |
| * the returned root is the only way to access the session or the |
| * repository. |
| * <p> |
| * Note that since there is no way to close the underlying content |
| * session, this method should only be used when no components that |
| * require sessions to be closed have been configured. The repository |
| * and the session will be garbage collected once the root is no longer |
| * used. |
| * |
| * @return root instance |
| */ |
| public Root createRoot() { |
| return createContentSession().getLatestRoot(); |
| } |
| |
| /** |
| * CommitHook to ensure that commit only go through till repository is not |
| * closed. Once repository is closed the commits would be failed |
| */ |
| private static class RepoStateCheckHook implements CommitHook, Closeable { |
| private volatile boolean closed; |
| |
| @NotNull |
| @Override |
| public NodeState processCommit(NodeState before, NodeState after, CommitInfo info) throws CommitFailedException { |
| if (closed){ |
| throw new CommitFailedException( |
| CommitFailedException.OAK, 2, "ContentRepository closed"); |
| } |
| return after; |
| } |
| |
| @Override |
| public void close() throws IOException { |
| this.closed = true; |
| } |
| } |
| |
| /** |
| * Settings of the query engine. This instance is an AnnotatedStandardMBean. |
| */ |
| private static final class AnnotatedQueryEngineSettings extends AnnotatedStandardMBean implements QueryEngineSettingsMBean { |
| |
| private final QueryEngineSettings settings; |
| |
| /** |
| * Create a new query engine settings object. Creating the object is |
| * relatively slow, and at runtime, as few such objects as possible should |
| * be created (ideally, only one per Oak instance). Creating new instances |
| * also means they can not be configured using JMX, as one would expect. |
| */ |
| private AnnotatedQueryEngineSettings(QueryEngineSettings settings) { |
| super(QueryEngineSettingsMBean.class); |
| this.settings = settings; |
| } |
| |
| /** |
| * Create a new query engine settings object. Creating the object is |
| * relatively slow, and at runtime, as few such objects as possible should |
| * be created (ideally, only one per Oak instance). Creating new instances |
| * also means they can not be configured using JMX, as one would expect. |
| */ |
| private AnnotatedQueryEngineSettings() { |
| this(new QueryEngineSettings()); |
| } |
| |
| @Override |
| public long getLimitInMemory() { |
| return settings.getLimitInMemory(); |
| } |
| |
| @Override |
| public void setLimitInMemory(long limitInMemory) { |
| settings.setLimitInMemory(limitInMemory); |
| } |
| |
| @Override |
| public long getLimitReads() { |
| return settings.getLimitReads(); |
| } |
| |
| @Override |
| public void setLimitReads(long limitReads) { |
| settings.setLimitReads(limitReads); |
| } |
| |
| @Override |
| public boolean getFailTraversal() { |
| return settings.getFailTraversal(); |
| } |
| |
| @Override |
| public void setFailTraversal(boolean failQueriesWithoutIndex) { |
| settings.setFailTraversal(failQueriesWithoutIndex); |
| } |
| |
| @Override |
| public boolean isFastQuerySize() { |
| return settings.isFastQuerySize(); |
| } |
| |
| @Override |
| public void setFastQuerySize(boolean fastQuerySize) { |
| settings.setFastQuerySize(fastQuerySize); |
| } |
| |
| public String getStrictPathRestriction() { |
| return settings.getStrictPathRestriction(); |
| } |
| |
| public void setStrictPathRestriction(String strictPathRestriction) { |
| settings.setStrictPathRestriction(strictPathRestriction); |
| } |
| |
| @Override |
| public void setQueryValidatorPattern(String key, String pattern, String comment, boolean failQuery) { |
| settings.getQueryValidator().setPattern(key, pattern, comment, failQuery); |
| } |
| |
| @Override |
| public String getQueryValidatorJson() { |
| return settings.getQueryValidator().getJson(); |
| } |
| |
| public QueryStatsMBean getQueryStats() { |
| return settings.getQueryStats(); |
| } |
| |
| public QueryEngineSettings unwrap() { |
| return settings; |
| } |
| |
| @Override |
| public String toString() { |
| return settings.toString(); |
| } |
| |
| void setFullTextComparisonWithoutIndex(boolean fullTextComparisonWithoutIndex) { |
| this.settings.setFullTextComparisonWithoutIndex(fullTextComparisonWithoutIndex); |
| } |
| } |
| |
| public static class OakDefaultComponents { |
| |
| /** |
| * @deprecated create a new instance of {@link OakDefaultComponents} instead. |
| */ |
| @Deprecated |
| public static final OakDefaultComponents INSTANCE = new OakDefaultComponents(); |
| |
| private final Iterable<CommitHook> commitHooks = ImmutableList.of(new VersionHook()); |
| |
| private final Iterable<RepositoryInitializer> repositoryInitializers = ImmutableList |
| .of(new InitialContent()); |
| |
| private final Iterable<EditorProvider> editorProviders = ImmutableList.of( |
| new ItemSaveValidatorProvider(), new NameValidatorProvider(), new NamespaceEditorProvider(), |
| new TypeEditorProvider(), new ConflictValidatorProvider(), new ChangeCollectorProvider()); |
| |
| private final Iterable<IndexEditorProvider> indexEditorProviders = ImmutableList.of( |
| new ReferenceEditorProvider(), new PropertyIndexEditorProvider(), new NodeCounterEditorProvider(), |
| new OrderedPropertyIndexEditorProvider()); |
| |
| private final Iterable<QueryIndexProvider> queryIndexProviders = ImmutableList |
| .of(new ReferenceIndexProvider(), new PropertyIndexProvider(), new NodeTypeIndexProvider()); |
| |
| private final SecurityProvider securityProvider = SecurityProviderBuilder.newBuilder().build(); |
| |
| public OakDefaultComponents() { |
| } |
| |
| public Iterable<CommitHook> commitHooks() { |
| return commitHooks; |
| } |
| |
| public Iterable<RepositoryInitializer> repositoryInitializers() { |
| return repositoryInitializers; |
| } |
| |
| public Iterable<EditorProvider> editorProviders() { |
| return editorProviders; |
| } |
| |
| public Iterable<IndexEditorProvider> indexEditorProviders() { |
| return indexEditorProviders; |
| } |
| |
| public Iterable<QueryIndexProvider> queryIndexProviders() { |
| return queryIndexProviders; |
| } |
| |
| public SecurityProvider securityProvider() { |
| return securityProvider; |
| } |
| } |
| |
| } |