/*
* 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.List;
import org.infinispan.manager.CacheContainer;
import org.infinispan.schematic.Schematic;
import org.infinispan.schematic.document.EditableArray;
import org.infinispan.schematic.document.EditableDocument;
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.ServiceVerificationHandler;
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.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.modeshape.common.util.StringUtil;
import org.modeshape.jboss.metric.ModelMetrics;
import org.modeshape.jboss.metric.MonitorService;
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;
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();
}
@Override
protected void performRuntime( final OperationContext context,
final ModelNode operation,
final ModelNode model,
final ServiceVerificationHandler verificationHandler,
final List<ServiceController<?>> newControllers ) throws OperationFailedException {
final ServiceTarget target = context.getServiceTarget();
final AddressContext addressContext = AddressContext.forOperation(operation);
final String repositoryName = addressContext.repositoryName();
final String cacheName = attribute(context, model, ModelAttributes.CACHE_NAME, repositoryName);
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 optTolerance = intAttribute(context,
model,
ModelAttributes.DOCUMENT_OPTIMIZATION_CHILD_COUNT_TOLERANCE,
null);
// Figure out which cache container to use (by default we'll use Infinispan subsystem's default cache container) ...
String namedContainer = attribute(context, model, ModelAttributes.CACHE_CONTAINER, "modeshape");
// 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;
}
// Always set whether monitoring is enabled ...
enableMonitoring(enableMonitoring, configDoc);
// Initial node-types if configured
parseCustomNodeTypes(model, configDoc);
// Workspace information is on the repository model node (unlike the XML) ...
EditableDocument workspacesDoc = parseWorkspaces(context, model, configDoc);
// Set the storage information (that was set on the repository ModelNode) ...
setRepositoryStorageConfiguration(cacheName, configDoc);
// security
parseSecurity(context, model, configDoc);
// Now create the repository service that manages the lifecycle of the JcrRepository instance ...
RepositoryConfiguration repositoryConfig = new RepositoryConfiguration(configDoc, repositoryName);
RepositoryService repositoryService = new RepositoryService(repositoryConfig);
// Journaling
parseJournaling(repositoryService, context, model, configDoc);
ServiceName repositoryServiceName = ModeShapeServiceNames.repositoryServiceName(repositoryName);
// 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());
}
}
// Add dependency to the Infinispan cache container used for content ...
repositoryServiceBuilder.addDependency(ServiceName.JBOSS.append("infinispan", namedContainer),
CacheContainer.class,
repositoryService.getCacheManagerInjector());
// Add dependency, if necessary, to the workspaces cache container
String workspacesCacheContainer = attribute(context, model, ModelAttributes.WORKSPACES_CACHE_CONTAINER, null);
if (workspacesCacheContainer != null && !workspacesCacheContainer.toLowerCase().equalsIgnoreCase(namedContainer)) {
// there is a different ISPN container configured for the ws caches
repositoryServiceBuilder.addDependency(ServiceName.JBOSS.append("infinispan", workspacesCacheContainer),
CacheContainer.class,
repositoryService.getWorkspacesCacheContainerInjector());
// the name is a constant which will be resolved later by the RepositoryService
workspacesDoc.set(FieldName.WORKSPACE_CACHE_CONFIGURATION, RepositoryService.WORKSPACES_CONTAINER_NAME);
}
repositoryServiceBuilder.addDependency(Services.JBOSS_SERVICE_MODULE_LOADER,
ModuleLoader.class,
repositoryService.getModuleLoaderInjector());
// Add dependency to the binaries storage service, which captures the properties for the binaries storage
repositoryServiceBuilder.addDependency(ModeShapeServiceNames.binaryStorageDefaultServiceName(repositoryName),
BinaryStorage.class,
repositoryService.getBinaryStorageInjector());
// 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);
ServiceController<String> dataDirServiceController = RelativePathService.addService(dataDirServiceName,
"modeshape/" + repositoryName,
ModeShapeExtension.JBOSS_DATA_DIR_VARIABLE,
target);
newControllers.add(dataDirServiceController);
repositoryServiceBuilder.addDependency(dataDirServiceName, String.class, repositoryService.getDataDirectoryPathInjector());
// Add the default binary storage service which will provide the binary configuration
BinaryStorageService defaultBinaryService = new BinaryStorageService(repositoryName);
ServiceBuilder<BinaryStorage> binaryStorageBuilder = target.addService(ModeShapeServiceNames.binaryStorageDefaultServiceName(repositoryName),
defaultBinaryService);
binaryStorageBuilder.addDependency(dataDirServiceName, String.class, defaultBinaryService.getDataDirectoryPathInjector());
binaryStorageBuilder.setInitialMode(ServiceController.Mode.ACTIVE);
// 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 ...
newControllers.add(repositoryServiceBuilder.install());
newControllers.add(referenceBuilder.install());
newControllers.add(binderBuilder.install());
newControllers.add(binaryStorageBuilder.install());
newControllers.add(monitorBuilder.install());
}
private void parseSecurity( OperationContext context,
ModelNode model,
EditableDocument configDoc ) throws OperationFailedException {
// JAAS ...
EditableDocument security = configDoc.getOrCreateDocument(FieldName.SECURITY);
EditableDocument jaas = security.getOrCreateDocument(FieldName.JAAS);
String securityDomain = attribute(context, model, ModelAttributes.SECURITY_DOMAIN).asString();
jaas.set(FieldName.JAAS_POLICY_NAME, securityDomain);
// 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);
}
}
// Servlet authenticator ...
EditableArray providers = security.getOrCreateArray(FieldName.PROVIDERS);
EditableDocument servlet = Schematic.newDocument();
servlet.set(FieldName.CLASSNAME, "servlet");
providers.add(servlet);
}
private void setRepositoryStorageConfiguration( String cacheName,
EditableDocument configDoc ) {
EditableDocument storage = configDoc.getOrCreateDocument(FieldName.STORAGE);
storage.set(FieldName.CACHE_NAME, cacheName);
// The proper container will be injected into the RepositoryService, so use the fixed container name ...
storage.set(FieldName.CACHE_CONFIGURATION, RepositoryService.CONTENT_CONTAINER_NAME);
}
private EditableDocument parseWorkspaces( OperationContext context,
ModelNode model,
EditableDocument configDoc ) 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.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);
// 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 ) {
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);
}
}