/*
* 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.brooklyn.core.entity.factory;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.mgmt.EntityManager;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.StartableApplication;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
/**
* Experimental mechanism for defining/building applications. In future releases, this
* API will change. Its concepts will most likely be merged with a TOSCA implementation
* and with {@link EntitySpec}.
*
* For building an application. Users can sub-class and override doBuild(), putting the logic for
* creating and wiring together entities in there.
*
* The builder is mutable; a given instance should be used to build only a single application.
* Once {@link #manage()} has been called, the application will be built and no additional configuration
* should be performed through this builder.
*
* Example (simplified) code for sub-classing is:
* <pre>
* {@code
* app = new ApplicationBuilder() {
* //@Override
* public void doBuild() {
* MySqlNode db = addChild(EntitySpec.create(MySqlNode.class)));
* JBoss7Server as = addChild(EntitySpec.create(JBoss7Server.class)
* .configure(HTTP_PORT, "8080+")
* .configure(javaSysProp("brooklyn.example.db.url"), attributeWhenReady(db, MySqlNode.MYSQL_URL));
* }
* }.manage();
* }
* </pre>
*
* @author aled
*
* @deprecated since 0.9.0; use {@link EntitySpec} and {@link EntityManager#createEntity(EntitySpec)}, having
* added the children to the spec etc.
*/
@Deprecated
@Beta
public abstract class ApplicationBuilder {
@SuppressWarnings("unused")
private static final Logger LOG = LoggerFactory.getLogger(ApplicationBuilder.class);
@SuppressWarnings("unchecked")
@Beta
/** @deprecated since 0.7.0 the management context should normally be passed in;
* for TestApplication also see TestApplication.Factory.newManagedInstanceForTests() */
@Deprecated
public static <T extends StartableApplication> T newManagedApp(Class<T> type) {
if (type.isInterface()) {
return (T) newManagedApp(EntitySpec.create(type));
} else {
return (T) newManagedApp(EntitySpec.create(StartableApplication.class, type));
}
}
@SuppressWarnings("unchecked")
private static <T extends StartableApplication> T newManagedApp(EntitySpec<T> spec) {
return (T) new ApplicationBuilder(spec) {
@Override protected void doBuild() {
}
}.manage();
}
@SuppressWarnings("unchecked")
@Beta
public static <T extends StartableApplication> T newManagedApp(Class<T> type, ManagementContext managementContext) {
if (type.isInterface()) {
return (T) newManagedApp(EntitySpec.create(type), managementContext);
} else {
return (T) newManagedApp(EntitySpec.create(StartableApplication.class, type), managementContext);
}
}
/** @deprecated class can be removed; users of this convenience method can now simply do mgmt.getEntityManager().createEntity(spec) */
@SuppressWarnings("unchecked")
@Beta
public static <T extends StartableApplication> T newManagedApp(EntitySpec<T> spec, ManagementContext managementContext) {
return (T) new ApplicationBuilder(spec) {
@Override protected void doBuild() {
}
}.manage(managementContext);
}
protected volatile boolean managed = false;
protected final AtomicBoolean inManage = new AtomicBoolean(false);
private EntitySpec<? extends StartableApplication> appSpec;
private ManagementContext managementContext;
private StartableApplication app;
public ApplicationBuilder() {
this.appSpec = EntitySpec.create(BasicApplication.class);
}
public ApplicationBuilder(EntitySpec<? extends StartableApplication> appSpec) {
this.appSpec = EntitySpec.create(appSpec);
}
public final ApplicationBuilder appDisplayName(String val) {
checkPreManage();
appSpec.displayName(val);
return this;
}
protected final <T extends Entity> T createEntity(EntitySpec<T> spec) {
checkDuringManage();
EntityManager entityManager = managementContext.getEntityManager();
return entityManager.createEntity(spec);
}
/**
* Adds the given entity as a child of the application being built.
* To be called during {@link #doBuild()}.
*/
protected final <T extends Entity> T addChild(T entity) {
checkDuringManage();
return app.addChild(entity);
}
/**
* Returns the type of the application being built.
*/
public final Class<? extends StartableApplication> getType() {
return appSpec.getType();
}
/**
* Configures the application instance.
*/
public final ApplicationBuilder configure(Map<?,?> config) {
checkPreManage();
appSpec.configure(config);
return this;
}
/**
* Adds the given entity as a child of the application being built.
*/
protected final <T extends Entity> T addChild(EntitySpec<T> spec) {
checkDuringManage();
return addChild(createEntity(spec));
}
protected final <T extends Entity> T addChild(Map<?,?> config, Class<T> type) {
checkDuringManage();
EntitySpec<T> spec = EntitySpec.create(type).configure(config);
return addChild(createEntity(spec));
}
protected final ManagementContext getManagementContext() {
return checkNotNull(managementContext, "must only be called after manage()");
}
protected final StartableApplication getApp() {
return checkNotNull(app, "must only be called after manage()");
}
/**
* For overriding, to create and wire together entities.
*/
protected abstract void doBuild();
/**
* Creates a new {@link ManagementContext}, and then builds and manages the application.
*
* @see #manage(ManagementContext)
*/
public final StartableApplication manage() {
return manage(Entities.newManagementContext());
}
/**
* Builds and manages the application, calling the user's {@link #doBuild()} method.
*
* @throws IllegalStateException If already managed, or if called during {@link #doBuild()}, or if
* multiple concurrent calls
*/
public final StartableApplication manage(ManagementContext managementContext) {
if (!inManage.compareAndSet(false, true)) {
throw new IllegalStateException("Concurrent and re-entrant calls to manage() forbidden on "+this);
}
try {
checkNotManaged();
this.managementContext = managementContext;
this.app = managementContext.getEntityManager().createEntity(appSpec);
doBuild();
// not needed with 0.9.0 (TODO - remove when confirmed)
// Entities.startManagement(app, managementContext);
managed = true;
return app;
} finally {
inManage.set(false);
}
}
protected void checkPreManage() {
if (inManage.get()) {
throw new IllegalStateException("Builder being managed; cannot perform operation during call to manage(), or in doBuild()");
}
if (managed) {
throw new IllegalStateException("Builder already managed; cannot perform operation after call to manage()");
}
}
protected void checkNotManaged() {
if (managed) {
throw new IllegalStateException("Builder already managed; cannot perform operation after call to manage()");
}
}
protected void checkDuringManage() {
if (!inManage.get() || app == null) {
throw new IllegalStateException("Operation only permitted during manage, e.g. called from doBuild() of "+this);
}
}
}