/* * ModeShape (http://www.modeshape.org) * * Licensed 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.modeshape.jboss.subsystem; import java.util.ArrayList; import java.util.List; import org.jboss.as.controller.AbstractAddStepHandler; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.services.path.RelativePathService; import org.jboss.as.naming.ManagedReferenceFactory; import org.jboss.as.naming.ServiceBasedNamingStore; import org.jboss.as.naming.deployment.ContextNames; import org.jboss.as.naming.service.BinderService; import org.jboss.as.security.service.SecurityManagementService; import org.jboss.as.server.Services; import org.jboss.dmr.ModelNode; import org.jboss.dmr.Property; import org.jboss.modules.ModuleLoader; import org.jboss.msc.service.ServiceBuilder; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.ServiceTarget; import org.jboss.security.ISecurityManagement; import org.modeshape.common.util.StringUtil; import org.modeshape.jboss.metric.ModelMetrics; import org.modeshape.jboss.metric.MonitorService; import org.modeshape.jboss.security.JBossDomainAuthenticationProvider; import org.modeshape.jboss.service.BinaryStorage; import org.modeshape.jboss.service.BinaryStorageService; import org.modeshape.jboss.service.ReferenceFactoryService; import org.modeshape.jboss.service.RepositoryService; import org.modeshape.jcr.JcrRepository; import org.modeshape.jcr.ModeShapeEngine; import org.modeshape.jcr.RepositoryConfiguration; import org.modeshape.jcr.RepositoryConfiguration.FieldName; import org.modeshape.jcr.api.monitor.RepositoryMonitor; import org.modeshape.schematic.Schematic; import org.modeshape.schematic.document.EditableArray; import org.modeshape.schematic.document.EditableDocument; import org.wildfly.clustering.jgroups.ChannelFactory; public class AddRepository extends AbstractAddStepHandler { public static final AddRepository INSTANCE = new AddRepository(); private static final org.jboss.logging.Logger LOG = org.jboss.logging.Logger.getLogger(AddRepository.class.getPackage() .getName()); private AddRepository() { } @Override protected void populateModel( ModelNode operation, ModelNode model ) throws OperationFailedException { // attributes for (AttributeDefinition attribute : ModelAttributes.REPOSITORY_ATTRIBUTES) { attribute.validateAndSet(operation, model); } // metrics for (final AttributeDefinition metric : ModelMetrics.ALL_METRICS) { metric.validateAndSet(operation, model); } } private ModelNode attribute( OperationContext context, ModelNode model, AttributeDefinition defn ) throws OperationFailedException { assert defn.getDefaultValue() != null && defn.getDefaultValue().isDefined(); return defn.resolveModelAttribute(context, model); } private String attribute( OperationContext context, ModelNode model, AttributeDefinition defn, String defaultValue ) throws OperationFailedException { ModelNode value = defn.resolveModelAttribute(context, model); return value.isDefined() ? value.asString() : defaultValue; } private Integer intAttribute( OperationContext context, ModelNode model, AttributeDefinition defn, Integer defaultValue ) throws OperationFailedException { ModelNode value = defn.resolveModelAttribute(context, model); if (value == null || !value.isDefined()) return defaultValue; return value.asInt(); } private Long longAttribute(OperationContext context, ModelNode model, AttributeDefinition defn, Long defaultValue) throws OperationFailedException { ModelNode value = defn.resolveModelAttribute(context, model); if (value == null || !value.isDefined()) { return defaultValue; } return value.asLong(); } @Override protected void performRuntime( final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException { final ServiceTarget target = context.getServiceTarget(); final AddressContext addressContext = AddressContext.forOperation(operation); final String repositoryName = addressContext.repositoryName(); final String clusterName = attribute(context, model, ModelAttributes.CLUSTER_NAME, null); final boolean enableMonitoring = attribute(context, model, ModelAttributes.ENABLE_MONITORING).asBoolean(); final String gcThreadPool = attribute(context, model, ModelAttributes.GARBAGE_COLLECTION_THREAD_POOL, null); final String gcInitialTime = attribute(context, model, ModelAttributes.GARBAGE_COLLECTION_INITIAL_TIME, null); final int gcIntervalInHours = attribute(context, model, ModelAttributes.GARBAGE_COLLECTION_INTERVAL).asInt(); final String optThreadPool = attribute(context, model, ModelAttributes.DOCUMENT_OPTIMIZATION_THREAD_POOL, null); final String optInitialTime = attribute(context, model, ModelAttributes.DOCUMENT_OPTIMIZATION_INITIAL_TIME, null); final int optIntervalInHours = attribute(context, model, ModelAttributes.DOCUMENT_OPTIMIZATION_INTERVAL).asInt(); final Integer optTarget = intAttribute(context, model, ModelAttributes.DOCUMENT_OPTIMIZATION_CHILD_COUNT_TARGET, null); final Integer eventBusSize = intAttribute(context, model, ModelAttributes.EVENT_BUS_SIZE, null); final Integer optTolerance = intAttribute(context, model, ModelAttributes.DOCUMENT_OPTIMIZATION_CHILD_COUNT_TOLERANCE, null); final Long lockTimeoutMillis = longAttribute(context, model, ModelAttributes.LOCK_TIMEOUT_MILLIS, null); // Create a document for the repository configuration ... EditableDocument configDoc = Schematic.newDocument(); configDoc.set(FieldName.NAME, repositoryName); // Determine the JNDI name ... configDoc.set(FieldName.JNDI_NAME, "");// always set to empty string, since we'll register in JNDI here ... final String jndiName = ModeShapeJndiNames.JNDI_BASE_NAME + repositoryName; String jndiAlias = ModeShapeJndiNames.jndiNameFrom(model, repositoryName); if (jndiName.equals(jndiAlias)) { jndiAlias = null; } if (eventBusSize != null) { configDoc.setNumber(FieldName.EVENT_BUS_SIZE, eventBusSize); } if (lockTimeoutMillis != null) { configDoc.setNumber(FieldName.LOCK_TIMEOUT_MILLIS, lockTimeoutMillis); } List<String> additionalClasspathEntries = new ArrayList<>(); // Always set whether monitoring is enabled ... enableMonitoring(enableMonitoring, configDoc); // Initial node-types if configured parseCustomNodeTypes(model, configDoc, additionalClasspathEntries); // Workspace information is on the repository model node (unlike the XML) ... EditableDocument workspacesDoc = parseWorkspaces(context, model, configDoc, additionalClasspathEntries); // security parseSecurity(context, model, configDoc); // Now create the repository service that manages the lifecycle of the JcrRepository instance ... RepositoryConfiguration repositoryConfig = new RepositoryConfiguration(configDoc, repositoryName); String additionalModuleDependencies = attribute(context, model, ModelAttributes.REPOSITORY_MODULE_DEPENDENCIES, null); RepositoryService repositoryService = new RepositoryService(repositoryConfig, additionalModuleDependencies); ServiceName repositoryServiceName = ModeShapeServiceNames.repositoryServiceName(repositoryName); // Sequencing parseSequencing(model, configDoc); // Text Extraction parseTextExtraction(model, configDoc); // Reindexing parseReindexing(model, configDoc); // Journaling parseJournaling(repositoryService, context, model, configDoc); // Add the EngineService's dependencies ... ServiceBuilder<JcrRepository> repositoryServiceBuilder = target.addService(repositoryServiceName, repositoryService); // Add dependency to the ModeShape engine service ... repositoryServiceBuilder.addDependency(ModeShapeServiceNames.ENGINE, ModeShapeEngine.class, repositoryService.getEngineInjector()); repositoryServiceBuilder.setInitialMode(ServiceController.Mode.ACTIVE); // Add garbage collection information ... if (gcThreadPool != null) { configDoc.getOrCreateDocument(FieldName.GARBAGE_COLLECTION).setString(FieldName.THREAD_POOL, gcThreadPool); } if (gcInitialTime != null) { configDoc.getOrCreateDocument(FieldName.GARBAGE_COLLECTION).setString(FieldName.INITIAL_TIME, gcInitialTime); } configDoc.getOrCreateDocument(FieldName.GARBAGE_COLLECTION).setNumber(FieldName.INTERVAL_IN_HOURS, gcIntervalInHours); // Add document optimization information ... if (optTarget != null) { EditableDocument docOpt = configDoc.getOrCreateDocument(FieldName.STORAGE) .getOrCreateDocument(FieldName.DOCUMENT_OPTIMIZATION); if (optThreadPool != null) { docOpt.setString(FieldName.THREAD_POOL, optThreadPool); } if (optInitialTime != null) { docOpt.setString(FieldName.INITIAL_TIME, optInitialTime); } docOpt.setNumber(FieldName.INTERVAL_IN_HOURS, optIntervalInHours); docOpt.setNumber(FieldName.OPTIMIZATION_CHILD_COUNT_TARGET, optTarget.intValue()); if (optTolerance != null) { docOpt.setNumber(FieldName.OPTIMIZATION_CHILD_COUNT_TOLERANCE, optTolerance.intValue()); } } if (!StringUtil.isBlank(clusterName)) { final String clusterConfig = attribute(context, model, ModelAttributes.CLUSTER_CONFIG, null); final String clusterLocking = attribute(context, model, ModelAttributes.CLUSTER_LOCKING, null); parseClustering(clusterName, clusterConfig, clusterLocking, configDoc); final String clusterStackName = attribute(context, model, ModelAttributes.CLUSTER_STACK, null); if (!StringUtil.isBlank(clusterStackName)) { repositoryServiceBuilder.addDependency(ServiceName.JBOSS.append("jgroups", "factory", clusterStackName), ChannelFactory.class, repositoryService.getChannelFactoryInjector()); } } // Add the dependency to the Security Manager repositoryServiceBuilder.addDependency(SecurityManagementService.SERVICE_NAME, ISecurityManagement.class, repositoryService.getSecurityManagementServiceInjector()); repositoryServiceBuilder.addDependency(Services.JBOSS_SERVICE_MODULE_LOADER, ModuleLoader.class, repositoryService.getModuleLoaderInjector()); // Set up the JNDI binder service ... final ReferenceFactoryService<JcrRepository> referenceFactoryService = new ReferenceFactoryService<JcrRepository>(); ServiceName referenceFactoryServiceName = ModeShapeServiceNames.referenceFactoryServiceName(repositoryName); final ServiceBuilder<?> referenceBuilder = target.addService(referenceFactoryServiceName, referenceFactoryService); referenceBuilder.addDependency(repositoryServiceName, JcrRepository.class, referenceFactoryService.getInjector()); referenceBuilder.setInitialMode(ServiceController.Mode.ACTIVE); ContextNames.BindInfo bindInfo = ContextNames.bindInfoFor(jndiName); BinderService binder = new BinderService(bindInfo.getBindName()); ServiceBuilder<?> binderBuilder = target.addService(bindInfo.getBinderServiceName(), binder); if (jndiAlias != null) { ContextNames.BindInfo aliasInfo = ContextNames.bindInfoFor(jndiAlias); ServiceName alias = aliasInfo.getBinderServiceName(); binderBuilder.addAliases(alias); LOG.debugv("Binding repository {0} to JNDI name {1} and {2}", repositoryName, bindInfo.getAbsoluteJndiName(), aliasInfo.getAbsoluteJndiName()); } else { LOG.debugv("Binding repository {0} to JNDI name {1}", repositoryName, bindInfo.getAbsoluteJndiName()); } binderBuilder.addDependency(referenceFactoryServiceName, ManagedReferenceFactory.class, binder.getManagedObjectInjector()); binderBuilder.addDependency(bindInfo.getParentContextServiceName(), ServiceBasedNamingStore.class, binder.getNamingStoreInjector()); binderBuilder.setInitialMode(ServiceController.Mode.ACTIVE); // Add dependency to the data directory ... ServiceName dataDirServiceName = ModeShapeServiceNames.dataDirectoryServiceName(repositoryName); RelativePathService.addService(dataDirServiceName, "modeshape/" + repositoryName, ModeShapeExtension.JBOSS_DATA_DIR_VARIABLE, target); repositoryServiceBuilder.addDependency(dataDirServiceName, String.class, repositoryService.getDataDirectoryPathInjector()); // Add the default binary storage service which will provide the binary configuration BinaryStorageService defaultBinaryService = BinaryStorageService.createDefault(); ServiceName defaultBinaryStorageServiceName = ModeShapeServiceNames.binaryStorageDefaultServiceName(repositoryName); ServiceBuilder<BinaryStorage> binaryStorageBuilder = target.addService(defaultBinaryStorageServiceName, defaultBinaryService); binaryStorageBuilder.setInitialMode(ServiceController.Mode.ACTIVE); // Add dependency to the binaries storage service, which captures the properties for the binaries storage repositoryServiceBuilder.addDependency(defaultBinaryStorageServiceName, BinaryStorage.class, repositoryService.getBinaryStorageInjector()); // Add monitor service final MonitorService monitorService = new MonitorService(); final ServiceBuilder<RepositoryMonitor> monitorBuilder = target.addService(ModeShapeServiceNames.monitorServiceName(repositoryName), monitorService); monitorBuilder.addDependency(ModeShapeServiceNames.repositoryServiceName(repositoryName), JcrRepository.class, monitorService.getJcrRepositoryInjector()); monitorBuilder.setInitialMode(ServiceController.Mode.ACTIVE); // Now add the controller for the RepositoryService ... repositoryServiceBuilder.install(); referenceBuilder.install(); binderBuilder.install(); binaryStorageBuilder.install(); monitorBuilder.install(); } private void parseClustering(String clusterName, String clusterConfig, String clusterLocking, EditableDocument configDoc) { EditableDocument clustering = configDoc.getOrCreateDocument(FieldName.CLUSTERING); clustering.setString(FieldName.CLUSTER_NAME, clusterName); if (!StringUtil.isBlank(clusterConfig)) { clustering.setString(FieldName.CLUSTER_CONFIGURATION, clusterConfig); } if (!StringUtil.isBlank(clusterLocking)) { clustering.setString(FieldName.CLUSTER_LOCKING, clusterLocking); } } private void parseTextExtraction( ModelNode model, EditableDocument configDoc ) { if (model.hasDefined(ModelKeys.TEXT_EXTRACTORS_THREAD_POOL_NAME)) { EditableDocument extractors = configDoc.getOrCreateDocument(FieldName.TEXT_EXTRACTION); String poolName = model.get(ModelKeys.TEXT_EXTRACTORS_THREAD_POOL_NAME).asString(); extractors.set(FieldName.THREAD_POOL, poolName); } if (model.hasDefined(ModelKeys.TEXT_EXTRACTORS_MAX_POOL_SIZE)) { EditableDocument sequencing = configDoc.getOrCreateDocument(FieldName.TEXT_EXTRACTION); int maxPoolSize = model.get(ModelKeys.TEXT_EXTRACTORS_MAX_POOL_SIZE).asInt(); sequencing.set(FieldName.MAX_POOL_SIZE, maxPoolSize); } } private void parseReindexing( ModelNode model, EditableDocument configDoc ) { if (model.hasDefined(ModelKeys.REINDEXING_ASYNC)) { EditableDocument reindexing = configDoc.getOrCreateDocument(FieldName.REINDEXING); boolean async = model.get(ModelKeys.REINDEXING_ASYNC).asBoolean(); reindexing.set(FieldName.REINDEXING_ASYNC, async); } if (model.hasDefined(ModelKeys.REINDEXING_MODE)) { EditableDocument reindexing = configDoc.getOrCreateDocument(FieldName.REINDEXING); String mode = model.get(ModelKeys.REINDEXING_MODE).asString(); reindexing.set(FieldName.REINDEXING_MODE, mode); } } private void parseSequencing( ModelNode model, EditableDocument configDoc ) { if (model.hasDefined(ModelKeys.SEQUENCERS_THREAD_POOL_NAME)) { EditableDocument sequencing = configDoc.getOrCreateDocument(FieldName.SEQUENCING); String sequencingThreadPool = model.get(ModelKeys.SEQUENCERS_THREAD_POOL_NAME).asString(); sequencing.set(FieldName.THREAD_POOL, sequencingThreadPool); } if (model.hasDefined(ModelKeys.SEQUENCERS_MAX_POOL_SIZE)) { EditableDocument sequencing = configDoc.getOrCreateDocument(FieldName.SEQUENCING); int maxPoolSize = model.get(ModelKeys.SEQUENCERS_MAX_POOL_SIZE).asInt(); sequencing.set(FieldName.MAX_POOL_SIZE, maxPoolSize); } } private void parseSecurity( OperationContext context, ModelNode model, EditableDocument configDoc ) throws OperationFailedException { EditableDocument security = configDoc.getOrCreateDocument(FieldName.SECURITY); // Anonymous ... EditableDocument anon = security.getOrCreateDocument(FieldName.ANONYMOUS); String anonUsername = attribute(context, model, ModelAttributes.ANONYMOUS_USERNAME).asString(); boolean useAnonIfFailed = attribute(context, model, ModelAttributes.USE_ANONYMOUS_IF_AUTH_FAILED).asBoolean(); anon.set(FieldName.ANONYMOUS_USERNAME, anonUsername); anon.set(FieldName.USE_ANONYMOUS_ON_FAILED_LOGINS, useAnonIfFailed); List<ModelNode> modelNodes = model.hasDefined(ModelKeys.ANONYMOUS_ROLES) ? model.get(ModelKeys.ANONYMOUS_ROLES).asList(): ModelAttributes.ANONYMOUS_ROLES.getDefaultValue().asList(); for (ModelNode roleNode : modelNodes) { EditableArray anonymousRolesArray = anon.getOrCreateArray(FieldName.ANONYMOUS_ROLES); String roleName = roleNode.asString(); if (!StringUtil.isBlank(roleName)) { anonymousRolesArray.addString(roleName); } } EditableArray providers = security.getOrCreateArray(FieldName.PROVIDERS); // JBoss authenticator ... String securityDomain = attribute(context, model, ModelAttributes.SECURITY_DOMAIN).asString(); EditableDocument jboss = Schematic.newDocument(); jboss.set(FieldName.CLASSNAME, JBossDomainAuthenticationProvider.class.getName()); jboss.set(FieldName.SECURITY_DOMAIN, securityDomain); providers.add(jboss); // Servlet authenticator ... EditableDocument servlet = Schematic.newDocument(); servlet.set(FieldName.CLASSNAME, "servlet"); providers.add(servlet); } private EditableDocument parseWorkspaces(OperationContext context, ModelNode model, EditableDocument configDoc, List<String> additionalClasspathEntries) throws OperationFailedException { EditableDocument workspacesDoc = configDoc.getOrCreateDocument(FieldName.WORKSPACES); boolean allowWorkspaceCreation = attribute(context, model, ModelAttributes.ALLOW_WORKSPACE_CREATION).asBoolean(); String defaultWorkspaceName = attribute(context, model, ModelAttributes.DEFAULT_WORKSPACE).asString(); workspacesDoc.set(FieldName.ALLOW_CREATION, allowWorkspaceCreation); workspacesDoc.set(FieldName.DEFAULT, defaultWorkspaceName); if (model.hasDefined(ModelKeys.WORKSPACES_CACHE_SIZE)) { workspacesDoc.set(FieldName.WORKSPACE_CACHE_SIZE, model.get(ModelKeys.WORKSPACES_CACHE_SIZE).asInt()); } if (model.hasDefined(ModelKeys.PREDEFINED_WORKSPACE_NAMES)) { for (ModelNode name : model.get(ModelKeys.PREDEFINED_WORKSPACE_NAMES).asList()) { workspacesDoc.getOrCreateArray(FieldName.PREDEFINED).add(name.asString()); } if (model.hasDefined(ModelKeys.WORKSPACES_INITIAL_CONTENT)) { EditableDocument initialContentDocument = workspacesDoc.getOrCreateDocument(FieldName.INITIAL_CONTENT); List<ModelNode> workspacesInitialContent = model.get(ModelKeys.WORKSPACES_INITIAL_CONTENT).asList(); for (ModelNode initialContent : workspacesInitialContent) { Property initialContentProperty = initialContent.asProperty(); initialContentDocument.set(initialContentProperty.getName(), initialContentProperty.getValue().asString()); } } } if (model.hasDefined(ModelKeys.DEFAULT_INITIAL_CONTENT)) { EditableDocument initialContentDocument = workspacesDoc.getOrCreateDocument(FieldName.INITIAL_CONTENT); initialContentDocument.set(FieldName.DEFAULT_INITIAL_CONTENT, model.get(ModelKeys.DEFAULT_INITIAL_CONTENT).asString()); } return workspacesDoc; } private void parseJournaling( RepositoryService repositoryService, OperationContext context, ModelNode model, EditableDocument configDoc ) throws OperationFailedException { if (model.hasDefined(ModelKeys.JOURNALING)) { EditableDocument journaling = configDoc.getOrCreateDocument(FieldName.JOURNALING); if (model.hasDefined(ModelKeys.JOURNAL_ENABLED)) { boolean enabled = attribute(context, model, ModelAttributes.JOURNAL_ENABLED).asBoolean(); journaling.setBoolean(FieldName.JOURNAL_ENABLED, enabled); } // set it temporarily on the repository service because the final location needs to be resolved later if (model.hasDefined(ModelKeys.JOURNAL_RELATIVE_TO)) { String relativeTo = attribute(context, model, ModelAttributes.JOURNAL_RELATIVE_TO).asString(); repositoryService.setJournalRelativeTo(relativeTo); } // set it temporarily on the repository service because the final location needs to be resolved later if (model.hasDefined(ModelKeys.JOURNAL_PATH)) { String path = attribute(context, model, ModelAttributes.JOURNAL_PATH).asString(); repositoryService.setJournalPath(path); } int maxDaysToKeepRecords = attribute(context, model, ModelAttributes.MAX_DAYS_TO_KEEP_RECORDS).asInt(); journaling.setNumber(FieldName.MAX_DAYS_TO_KEEP_RECORDS, maxDaysToKeepRecords); boolean asyncWrites = attribute(context, model, ModelAttributes.ASYNC_WRITES).asBoolean(); journaling.setBoolean(FieldName.ASYNC_WRITES_ENABLED, asyncWrites); String gcThreadPool = attribute(context, model, ModelAttributes.JOURNAL_GC_THREAD_POOL).asString(); journaling.setString(FieldName.THREAD_POOL, gcThreadPool); String gcInitialTime = attribute(context, model, ModelAttributes.JOURNAL_GC_INITIAL_TIME).asString(); journaling.setString(FieldName.INITIAL_TIME, gcInitialTime); } } private void parseCustomNodeTypes(ModelNode model, EditableDocument configDoc, List<String> additionalClasspathEntries) { if (model.hasDefined(ModelKeys.NODE_TYPES)) { EditableArray nodeTypesArray = configDoc.getOrCreateArray(FieldName.NODE_TYPES); for (ModelNode nodeType : model.get(ModelKeys.NODE_TYPES).asList()) { nodeTypesArray.add(nodeType.asString()); } } } private void enableMonitoring( boolean enableMonitoring, EditableDocument configDoc ) { EditableDocument monitoring = configDoc.getOrCreateDocument(FieldName.MONITORING); monitoring.set(FieldName.MONITORING_ENABLED, enableMonitoring); } }