blob: e000997420cb4d5fd250c9b534c3951f3c301a19 [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.brooklyn.core.entity;
import java.util.Collection;
import java.util.Map;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceProblemsLogic;
import org.apache.brooklyn.core.entity.trait.StartableMethods;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Users can extend this to define the entities in their application, and the relationships between
* those entities. Users should override the {@link #init()} method, and in there should create
* their entities.
*/
public abstract class AbstractApplication extends AbstractEntity implements StartableApplication {
private static final Logger log = LoggerFactory.getLogger(AbstractApplication.class);
/**
* The default name to use for this app, if not explicitly overridden by the top-level app.
* Necessary to avoid the app being wrapped in another layer of "BasicApplication" on deployment.
* Previously, the catalog item gave an explicit name (rathe rthan this defaultDisplayName), which
* meant that if the user chose a different name then AMP would automatically wrap this app so
* that both names would be presented.
*/
public static final ConfigKey<String> DEFAULT_DISPLAY_NAME = ConfigKeys.newStringConfigKey("defaultDisplayName");
private volatile Application application;
public AbstractApplication() {
}
public void init() {
super.init();
if (Strings.isNonBlank(getConfig(DEFAULT_DISPLAY_NAME))) {
setDefaultDisplayName(getConfig(DEFAULT_DISPLAY_NAME));
}
initApp();
}
protected void initApp() {}
/**
*
* @deprecated since 0.6; use EntitySpec so no-arg constructor
*/
@Deprecated
public AbstractApplication(Map properties) {
super(properties);
}
/**
* Constructor for when application is nested inside another application
*
* @deprecated Nesting applications is not currently supported
*/
@Deprecated
public AbstractApplication(Map properties, Entity parent) {
super(properties, parent);
}
@Override
public Application getApplication() {
if (application!=null) {
if (application.getId().equals(getId()))
return (Application) getProxyIfAvailable();
return application;
}
if (getParent()==null) return (Application)getProxyIfAvailable();
return getParent().getApplication();
}
@Override
protected synchronized void setApplication(Application app) {
if (app.getId().equals(getId())) {
application = getProxy()!=null ? (Application)getProxy() : app;
} else {
application = app;
// Alex, Mar 2013: added some checks;
// i *think* these conditions should not happen,
// and so should throw but don't want to break things (yet)
if (getParent()==null) {
log.warn("Setting application of "+this+" to "+app+", but "+this+" is not parented");
} else if (getParent().getApplicationId().equals(app.getParent())) {
log.warn("Setting application of "+this+" to "+app+", but parent "+getParent()+" has different app "+getParent().getApplication());
}
}
super.setApplication(app);
}
@Override
public AbstractApplication setParent(Entity parent) {
super.setParent(parent);
return this;
}
/** as {@link AbstractEntity#initEnrichers()} but also adding default service not-up and problem indicators from children */
@Override
protected void initEnrichers() {
super.initEnrichers();
// default app logic; easily overridable by adding a different enricher with the same tag
ServiceStateLogic.newEnricherFromChildren().checkChildrenAndMembers().addTo(this);
ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application created but not yet started, at "+Time.makeDateString());
}
/**
* Default start will start all Startable children (child.start(Collection<? extends Location>)),
* calling preStart(locations) first and postStart(locations) afterwards.
*/
@Override
public void start(Collection<? extends Location> locations) {
this.addLocations(locations);
Collection<? extends Location> locationsToUse = getLocations();
ServiceProblemsLogic.clearProblemsIndicator(this, START);
ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application starting");
setExpectedStateAndRecordLifecycleEvent(Lifecycle.STARTING);
try {
preStart(locationsToUse);
// if there are other items which should block service_up, they should be done in preStart
ServiceStateLogic.ServiceNotUpLogic.clearNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL);
doStart(locationsToUse);
postStart(locationsToUse);
} catch (Exception e) {
// TODO should probably remember these problems then clear? if so, do it here ... or on all effectors?
// ServiceProblemsLogic.updateProblemsIndicator(this, START, e);
recordApplicationEvent(Lifecycle.ON_FIRE);
// no need to log here; the effector invocation should do that
throw Exceptions.propagate(e);
} finally {
ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
}
setExpectedStateAndRecordLifecycleEvent(Lifecycle.RUNNING);
logApplicationLifecycle("Started");
}
protected void logApplicationLifecycle(String message) {
log.info(message+" application " + this);
}
protected void doStart(Collection<? extends Location> locations) {
StartableMethods.start(this, locations);
}
/**
* Default is no-op. Subclasses can override.
* */
public void preStart(Collection<? extends Location> locations) {
//no-op
}
/**
* Default is no-op. Subclasses can override.
* */
public void postStart(Collection<? extends Location> locations) {
//no-op
}
/**
* Default stop will stop all Startable children
*/
@Override
public void stop() {
logApplicationLifecycle("Stopping");
ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application stopping");
sensors().set(SERVICE_UP, false);
setExpectedStateAndRecordLifecycleEvent(Lifecycle.STOPPING);
try {
doStop();
} catch (Exception e) {
setExpectedStateAndRecordLifecycleEvent(Lifecycle.ON_FIRE);
log.warn("Error stopping application " + this + " (rethrowing): "+e);
throw Exceptions.propagate(e);
}
ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application stopped");
setExpectedStateAndRecordLifecycleEvent(Lifecycle.STOPPED);
if (getParent()==null) {
synchronized (this) {
//TODO review mgmt destroy lifecycle
// we don't necessarily want to forget all about the app on stop,
//since operator may be interested in things recently stopped;
//but that could be handled by the impl at management
//(keeping recently unmanaged things)
// however unmanaging must be done last, _after_ we stop children and set attributes
getEntityManager().unmanage(this);
}
}
logApplicationLifecycle("Stopped");
}
protected void doStop() {
StartableMethods.stop(this);
}
/** default impl invokes restart on all children simultaneously */
@Override
public void restart() {
StartableMethods.restart(this);
}
@Override
public void onManagementStopped() {
super.onManagementStopped();
if (getManagementContext().isRunning()) {
recordApplicationEvent(Lifecycle.DESTROYED);
}
}
protected void setExpectedStateAndRecordLifecycleEvent(Lifecycle state) {
ServiceStateLogic.setExpectedState(this, state);
recordApplicationEvent(state);
}
protected void recordApplicationEvent(Lifecycle state) {
try {
((ManagementContextInternal)getManagementContext()).getUsageManager().recordApplicationEvent(this, state);
} catch (RuntimeInterruptedException e) {
throw e;
} catch (RuntimeException e) {
if (getManagementContext().isRunning()) {
log.warn("Problem recording application event '"+state+"' for "+this, e);
}
}
}
}