You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/12/23 12:06:44 UTC

[21/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723' into reorg2

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
index 0000000,729351a..ca2caf7
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
@@@ -1,0 -1,372 +1,392 @@@
+ /*
+  * 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.entity.dns;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ 
+ import java.net.InetAddress;
+ import java.net.MalformedURLException;
+ import java.net.URI;
+ import java.net.URL;
+ import java.net.UnknownHostException;
+ import java.util.Collection;
+ import java.util.Collections;
+ import java.util.HashSet;
+ import java.util.LinkedHashMap;
+ import java.util.LinkedHashSet;
+ import java.util.Map;
+ import java.util.Set;
+ 
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.entity.Group;
+ import org.apache.brooklyn.api.policy.PolicySpec;
++import org.apache.brooklyn.api.sensor.Sensor;
+ import org.apache.brooklyn.core.entity.AbstractEntity;
+ import org.apache.brooklyn.core.entity.Attributes;
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic;
+ import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+ import org.apache.brooklyn.entity.group.AbstractMembershipTrackingPolicy;
+ import org.apache.brooklyn.entity.group.DynamicGroup;
+ import org.apache.brooklyn.entity.webapp.WebAppService;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.apache.brooklyn.util.collections.MutableSet;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.net.Networking;
+ import org.apache.brooklyn.util.time.Duration;
+ import org.apache.brooklyn.util.time.Time;
+ 
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Maps;
+ 
+ public abstract class AbstractGeoDnsServiceImpl extends AbstractEntity implements AbstractGeoDnsService {
+     private static final Logger log = LoggerFactory.getLogger(AbstractGeoDnsService.class);
+ 
+     @SetFromFlag
+     protected Group targetEntityProvider;
+     protected AbstractMembershipTrackingPolicy tracker;
 -    
++
+     protected Map<Entity, HostGeoInfo> targetHosts = Collections.synchronizedMap(new LinkedHashMap<Entity, HostGeoInfo>());
 -    
++
+     // We complain (at debug) when we encounter a target entity for whom we can't derive hostname/ip information; 
+     // this is the commonest case for the transient condition between the time the entity is created and the time 
+     // it is started (at which point the location is specified). This set contains those entities we've complained 
+     // about already, to avoid repetitive logging.
+     transient protected Set<Entity> entitiesWithoutHostname = new HashSet<Entity>();
+ 
+     // We complain (at info/warn) when we encounter a target entity for whom we can't derive geo information, even 
+     // when hostname/ip is known. This set contains those entities we've complained about already, to avoid repetitive 
+     // logging.
+     transient protected Set<Entity> entitiesWithoutGeoInfo = new HashSet<Entity>();
+ 
+     public AbstractGeoDnsServiceImpl() {
+         super();
+     }
 -    
++
++    @Override
++    public void init() {
++        super.init();
++        Group initialProvider = config().get(ENTITY_PROVIDER);
++        if (initialProvider != null) {
++            setTargetEntityProvider(initialProvider);
++        }
++    }
++
+     @Override
+     public Map<Entity, HostGeoInfo> getTargetHosts() {
+         return targetHosts;
+     }
 -    
++
+     @Override
+     public void onManagementBecomingMaster() {
+         super.onManagementBecomingMaster();
+         startTracker();
+     }
++
+     @Override
+     public void onManagementNoLongerMaster() {
+         endTracker();
+         super.onManagementNoLongerMaster();
+     }
+ 
+     @Override
+     public void destroy() {
+         setServiceState(Lifecycle.DESTROYED);
+         super.destroy();
+     }
 -        
++
+     @Override
+     public void setServiceState(Lifecycle state) {
+         sensors().set(HOSTNAME, getHostname());
+         ServiceStateLogic.setExpectedState(this, state);
+         if (state==Lifecycle.RUNNING)
+             ServiceNotUpLogic.clearNotUpIndicator(this, SERVICE_STATE_ACTUAL);
+         else
+             ServiceNotUpLogic.updateNotUpIndicator(this, SERVICE_STATE_ACTUAL, "Not in RUNNING state");
+     }
 -    
++
+     @Override
+     public void setTargetEntityProvider(final Group entityProvider) {
+         this.targetEntityProvider = checkNotNull(entityProvider, "targetEntityProvider");
+         startTracker();
+     }
 -    
++
+     /** should set up so these hosts are targeted, and setServiceState appropriately */
+     protected abstract void reconfigureService(Collection<HostGeoInfo> targetHosts);
 -    
++
+     protected synchronized void startTracker() {
+         if (targetEntityProvider==null || !getManagementSupport().isDeployed()) {
+             log.debug("Tracker for "+this+" not yet active: "+targetEntityProvider+" / "+getManagementContext());
+             return;
+         }
+         endTracker();
++
++        ImmutableSet.Builder<Sensor<?>> sensorsToTrack = ImmutableSet.<Sensor<?>>builder().add(
++                HOSTNAME, ADDRESS, Attributes.MAIN_URI, WebAppService.ROOT_URL);
++        // Don't subscribe to lifecycle events if entities will be included regardless of their status.
++        if (Boolean.TRUE.equals(config().get(FILTER_FOR_RUNNING))) {
++            sensorsToTrack.add(Attributes.SERVICE_STATE_ACTUAL);
++        }
+         log.debug("Initializing tracker for "+this+", following "+targetEntityProvider);
+         tracker = policies().add(PolicySpec.create(MemberTrackingPolicy.class)
+                 .displayName("GeoDNS targets tracker")
 -                .configure("sensorsToTrack", ImmutableSet.of(HOSTNAME, ADDRESS, Attributes.MAIN_URI, WebAppService.ROOT_URL))
 -                .configure("group", targetEntityProvider));
++                .configure(AbstractMembershipTrackingPolicy.SENSORS_TO_TRACK, sensorsToTrack.build())
++                .configure(AbstractMembershipTrackingPolicy.GROUP, targetEntityProvider));
+         refreshGroupMembership();
+     }
 -    
++
+     protected synchronized void endTracker() {
+         if (tracker == null || targetEntityProvider==null) return;
+         policies().remove(tracker);
+         tracker = null;
+     }
 -    
++
+     public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
+         @Override
+         protected void onEntityEvent(EventType type, Entity entity) {
+             ((AbstractGeoDnsServiceImpl)super.entity).refreshGroupMembership();
+         }
+     }
+ 
+     @Override
+     public abstract String getHostname();
 -    
++
+     long lastUpdate = -1;
 -    
++
+     // TODO: remove group member polling once locations can be determined via subscriptions
+     protected void refreshGroupMembership() {
+         try {
+             if (log.isDebugEnabled()) log.debug("GeoDns {} refreshing targets", this);
+             if (targetEntityProvider == null)
+                 return;
+             if (targetEntityProvider instanceof DynamicGroup)
+                 ((DynamicGroup) targetEntityProvider).rescanEntities();
+             Set<Entity> pool = MutableSet.copyOf(targetEntityProvider instanceof Group ? ((Group)targetEntityProvider).getMembers(): targetEntityProvider.getChildren());
+             if (log.isDebugEnabled()) log.debug("GeoDns {} refreshing targets, pool now {}", this, pool);
 -            
++
+             boolean changed = false;
++            boolean filterForRunning = Boolean.TRUE.equals(config().get(FILTER_FOR_RUNNING));
+             Set<Entity> previousOnes = MutableSet.copyOf(targetHosts.keySet());
+             for (Entity e: pool) {
 -                previousOnes.remove(e);
 -                changed |= addTargetHost(e);
++                if (!filterForRunning || Lifecycle.RUNNING.equals(e.sensors().get(Attributes.SERVICE_STATE_ACTUAL))) {
++                    previousOnes.remove(e);
++                    changed |= addTargetHost(e);
++                }
+             }
+             // anything left in previousOnes is no longer applicable
+             for (Entity e: previousOnes) {
 -                changed = true;
 -                removeTargetHost(e, false);
++                changed |= removeTargetHost(e, false);
+             }
 -            
++
+             // do a periodic full update hourly once we are active (the latter is probably not needed)
 -            if (changed || (lastUpdate>0 && Time.hasElapsedSince(lastUpdate, Duration.ONE_HOUR)))
++            if (changed || (lastUpdate > 0 && Time.hasElapsedSince(lastUpdate, Duration.ONE_HOUR))) {
+                 update();
 -            
++            }
+         } catch (Exception e) {
+             log.error("Problem refreshing group membership: "+e, e);
+         }
+     }
 -    
++
+     /**
+      * Adds this host, if it is absent or if its hostname has changed.
+      * <p>
+      * For whether to use hostname or ip, see config and attributes {@link AbstractGeoDnsService#USE_HOSTNAMES}, 
+      * {@link Attributes#HOSTNAME} and {@link Attributes#ADDRESS} (via {@link #inferHostname(Entity)} and {@link #inferIp(Entity)}.
+      * Note that the "hostname" could in fact be an IP address, if {@link #inferHostname(Entity)} returns an IP!
+      * <p>
+      * TODO in a future release, we may change this to explicitly set the sensor(s) to look at on the entity, and 
+      * be stricter about using them in order.
+      * 
+      * @return true if host is added or changed
+      */
+     protected boolean addTargetHost(Entity entity) {
+         try {
+             HostGeoInfo oldGeo = targetHosts.get(entity);
+             String hostname = inferHostname(entity);
+             String ip = inferIp(entity);
+             String addr = (getConfig(USE_HOSTNAMES) || ip == null) ? hostname : ip;
 -            
++
+             if (addr==null) addr = ip;
+             if (addr == null) {
+                 if (entitiesWithoutHostname.add(entity)) {
+                     log.debug("GeoDns ignoring {} (no hostname/ip/URL info yet available)", entity);
+                 }
+                 return false;
+             }
 -            
++
+             // prefer the geo from the entity (or location parent), but fall back to inferring
+             // e.g. if it supplies a URL
+             HostGeoInfo geo = HostGeoInfo.fromEntity(entity);
+             if (geo==null) geo = inferHostGeoInfo(hostname, ip);
 -            
++
+             if (Networking.isPrivateSubnet(addr) && ip!=null && !Networking.isPrivateSubnet(ip)) {
+                 // fix for #1216
+                 log.debug("GeoDns using IP "+ip+" for "+entity+" as addr "+addr+" resolves to private subnet");
+                 addr = ip;
+             }
+             if (Networking.isPrivateSubnet(addr)) {
+                 if (getConfig(INCLUDE_HOMELESS_ENTITIES)) {
+                     if (entitiesWithoutGeoInfo.add(entity)) {
+                         log.info("GeoDns including {}, even though {} is a private subnet (homeless entities included)", entity, addr);
+                     }
+                 } else {
+                     if (entitiesWithoutGeoInfo.add(entity)) {
+                         log.warn("GeoDns ignoring {} (private subnet detected for {})", entity, addr);
+                     }
+                     return false;
+                 }
+             }
+ 
+             if (geo == null) {
+                 if (getConfig(INCLUDE_HOMELESS_ENTITIES)) {
+                     if (entitiesWithoutGeoInfo.add(entity)) {
+                         log.info("GeoDns including {}, even though no geography info available for {})", entity, addr);
+                     }
+                     geo = HostGeoInfo.create(addr, "unknownLocation("+addr+")", 0, 0);
+                 } else {
+                     if (entitiesWithoutGeoInfo.add(entity)) {
+                         log.warn("GeoDns ignoring {} (no geography info available for {})", entity, addr);
+                     }
+                     return false;
+                 }
+             }
+ 
+             if (!addr.equals(geo.getAddress())) {
+                 // if the location provider did not have an address, create a new one with it
+                 geo = HostGeoInfo.create(addr, geo.displayName, geo.latitude, geo.longitude);
+             }
 -            
++
+             // If we already knew about it, and it hasn't changed, then nothing to do
+             if (oldGeo != null && geo.getAddress().equals(oldGeo.getAddress())) {
+                 return false;
+             }
 -            
++
+             entitiesWithoutHostname.remove(entity);
+             entitiesWithoutGeoInfo.remove(entity);
+             log.info("GeoDns adding "+entity+" at "+geo+(oldGeo != null ? " (previously "+oldGeo+")" : ""));
+             targetHosts.put(entity, geo);
+             return true;
+ 
+         } catch (Exception ee) {
+             log.warn("GeoDns ignoring "+entity+" (error analysing location): "+ee, ee);
+             return false;
+         }
+     }
+ 
+     /** remove if host removed */
+     protected boolean removeTargetHost(Entity e, boolean doUpdate) {
+         if (targetHosts.remove(e) != null) {
+             log.info("GeoDns removing reference to {}", e);
+             if (doUpdate) update();
+             return true;
+         }
+         return false;
+     }
 -    
++
+     protected void update() {
+         lastUpdate = System.currentTimeMillis();
 -        
++
+         Map<Entity, HostGeoInfo> m;
+         synchronized(targetHosts) { m = ImmutableMap.copyOf(targetHosts); }
+         if (log.isDebugEnabled()) log.debug("Full update of "+this+" ("+m.size()+" target hosts)");
 -        
++
+         Map<String,String> entityIdToAddress = Maps.newLinkedHashMap();
+         for (Map.Entry<Entity, HostGeoInfo> entry : m.entrySet()) {
+             entityIdToAddress.put(entry.getKey().getId(), entry.getValue().address);
+         }
 -        
++
+         reconfigureService(new LinkedHashSet<HostGeoInfo>(m.values()));
 -        
++
+         if (log.isDebugEnabled()) log.debug("Targets being set as "+entityIdToAddress);
+         sensors().set(TARGETS, entityIdToAddress);
+     }
 -    
++
+     protected String inferHostname(Entity entity) {
+         String hostname = entity.getAttribute(Attributes.HOSTNAME);
+         URI url = entity.getAttribute(Attributes.MAIN_URI);
+         if (url!=null) {
+             try {
+                 URL u = url.toURL();
 -                
++
+                 String hostname2 = u.getHost(); 
+                 if (hostname==null) {
+                     if (!entitiesWithoutGeoInfo.contains(entity))  //don't log repeatedly
+                         log.warn("GeoDns "+this+" using URL {} to redirect to {} (HOSTNAME attribute is preferred, but not available)", url, entity);
+                     hostname = hostname2;
+                 } else if (!hostname.equals(hostname2)) {
+                     if (!entitiesWithoutGeoInfo.contains(entity))  //don't log repeatedly
+                         log.warn("GeoDns "+this+" URL {} of "+entity+" does not match advertised HOSTNAME {}; using hostname, not URL", url, hostname);
+                 }
 -                
++
+                 if (u.getPort() > 0 && u.getPort() != 80 && u.getPort() != 443) {
+                     if (!entitiesWithoutGeoInfo.contains(entity))  //don't log repeatedly
+                         log.warn("GeoDns "+this+" detected non-standard port in URL {} for {}; forwarding may not work", url, entity);
+                 }
 -                
++
+             } catch (MalformedURLException e) {
+                 log.warn("Invalid URL {} for entity {} in {}", new Object[] {url, entity, this});
+             }
+         }
+         return hostname;
+     }
 -    
++
+     protected String inferIp(Entity entity) {
+         return entity.getAttribute(Attributes.ADDRESS);
+     }
 -    
++
+     protected HostGeoInfo inferHostGeoInfo(String hostname, String ip) throws UnknownHostException {
+         HostGeoInfo geoH = null;
+         if (hostname != null) {
+             try {
+                 // For some entities, the hostname can actually be an IP! Therefore use Networking.getInetAddressWithFixedName
+                 InetAddress addr = Networking.getInetAddressWithFixedName(hostname);
+                 geoH = HostGeoInfo.fromIpAddress(addr);
+             } catch (RuntimeException e) {
+                 // Most likely caused by (a wrapped) UnknownHostException
+                 Exceptions.propagateIfFatal(e);
+                 if (ip == null) {
+                     if (log.isTraceEnabled()) log.trace("inferHostGeoInfo failing ("+Exceptions.getFirstInteresting(e)+"): hostname="+hostname+"; ip="+ip);
+                     throw e;
+                 } else {
+                     if (log.isTraceEnabled()) log.trace("GeoDns failed to infer GeoInfo from hostname {}; will try with IP {} ({})", new Object[] {hostname, ip, e});
+                 }
+             }
+         }
+ 
+         // Try IP address (prior to Mar 2014 we did not do this if USE_HOSTNAME was set but don't think that is desirable due to #1216)
+         if (ip != null) {
+             if (geoH == null) {
+                 InetAddress addr = Networking.getInetAddressWithFixedName(ip);
+                 geoH = HostGeoInfo.fromIpAddress(addr);
+                 if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from ip {} (could not infer from hostname {})", new Object[] {geoH, ip, hostname});
+             } else {
+                 geoH = HostGeoInfo.create(ip, geoH.displayName, geoH.latitude, geoH.longitude);
+                 if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from hostname {}; switching it to ip {}", new Object[] {geoH, hostname, ip});
+             }
+         } else {
+             if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from hostname {}", geoH, hostname);
+         }
 -        
++
+         return geoH;
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
index 0000000,f421df7..58fcca4
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
@@@ -1,0 -1,70 +1,86 @@@
+ /*
+  * 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.entity.dns.geoscaling;
+ 
+ import java.net.URI;
+ 
+ import org.apache.brooklyn.api.entity.ImplementedBy;
+ import org.apache.brooklyn.api.sensor.AttributeSensor;
+ import org.apache.brooklyn.config.ConfigKey;
 -import org.apache.brooklyn.core.config.BasicConfigKey;
+ import org.apache.brooklyn.core.config.ConfigKeys;
+ import org.apache.brooklyn.core.entity.Attributes;
 -import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
++import org.apache.brooklyn.core.sensor.Sensors;
+ import org.apache.brooklyn.entity.dns.AbstractGeoDnsService;
+ import org.apache.brooklyn.entity.webapp.WebAppServiceConstants;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ 
++/**
++ * A geo-DNS service using geoscaling.com.
++ * <p>
++ * AWS users should note that if the Brooklyn server managing this entity is in the same
++ * region as the server being geoscaled then they must set {@link #INCLUDE_HOMELESS_ENTITIES}
++ * to true, as IP lookups of the server will resolve the private address and it will be
++ * ignored by default.
++ */
+ @ImplementedBy(GeoscalingDnsServiceImpl.class)
+ public interface GeoscalingDnsService extends AbstractGeoDnsService {
+     
+     @SetFromFlag("sslTrustAll")
 -    public static final ConfigKey<Boolean> SSL_TRUST_ALL = ConfigKeys.newBooleanConfigKey(
++    ConfigKey<Boolean> SSL_TRUST_ALL = ConfigKeys.newBooleanConfigKey(
+             "ssl.trustAll",
+             "Whether to trust all certificates, or to fail with 'peer not authenticated' if untrusted (default false)",
+             false);
++
+     @SetFromFlag("randomizeSubdomainName")
 -    public static final ConfigKey<Boolean> RANDOMIZE_SUBDOMAIN_NAME = new BasicConfigKey<Boolean>(
 -            Boolean.class, "randomize.subdomain.name");
++    ConfigKey<Boolean> RANDOMIZE_SUBDOMAIN_NAME = ConfigKeys.newBooleanConfigKey(
++            "randomize.subdomain.name");
++
+     @SetFromFlag("username")
 -    public static final ConfigKey<String> GEOSCALING_USERNAME = new BasicConfigKey<String>(
 -            String.class, "geoscaling.username");
++    ConfigKey<String> GEOSCALING_USERNAME = ConfigKeys.newStringConfigKey(
++            "geoscaling.username");
++
+     @SetFromFlag("password")
 -    public static final ConfigKey<String> GEOSCALING_PASSWORD = new BasicConfigKey<String>(
 -            String.class, "geoscaling.password");
++    ConfigKey<String> GEOSCALING_PASSWORD = ConfigKeys.newStringConfigKey(
++            "geoscaling.password");
++
+     @SetFromFlag("primaryDomainName")
 -    public static final ConfigKey<String> GEOSCALING_PRIMARY_DOMAIN_NAME = new BasicConfigKey<String>(
 -            String.class, "geoscaling.primary.domain.name");
++    ConfigKey<String> GEOSCALING_PRIMARY_DOMAIN_NAME = ConfigKeys.newStringConfigKey(
++            "geoscaling.primary.domain.name");
++
+     @SetFromFlag("smartSubdomainName")
 -    public static final ConfigKey<String> GEOSCALING_SMART_SUBDOMAIN_NAME = new BasicConfigKey<String>(
 -            String.class, "geoscaling.smart.subdomain.name");
++    ConfigKey<String> GEOSCALING_SMART_SUBDOMAIN_NAME = ConfigKeys.newStringConfigKey(
++            "geoscaling.smart.subdomain.name");
+     
 -    public static final AttributeSensor<String> GEOSCALING_ACCOUNT = new BasicAttributeSensor<String>(
 -            String.class, "geoscaling.account", "Active user account for the GeoScaling.com service");
 -    public static final AttributeSensor<URI> MAIN_URI = Attributes.MAIN_URI;
 -    public static final AttributeSensor<String> ROOT_URL = WebAppServiceConstants.ROOT_URL;
 -    public static final AttributeSensor<String> MANAGED_DOMAIN = new BasicAttributeSensor<String>(
 -            String.class, "geoscaling.managed.domain", "Fully qualified domain name that will be geo-redirected; " +
++    AttributeSensor<String> GEOSCALING_ACCOUNT = Sensors.newStringSensor(
++            "geoscaling.account", "Active user account for the GeoScaling.com service");
++
++    AttributeSensor<URI> MAIN_URI = Attributes.MAIN_URI;
++
++    AttributeSensor<String> ROOT_URL = WebAppServiceConstants.ROOT_URL;
++
++    AttributeSensor<String> MANAGED_DOMAIN = Sensors.newStringSensor(
++            "geoscaling.managed.domain",
++            "Fully qualified domain name that will be geo-redirected; " +
+                     "this will be the same as "+ROOT_URL.getName()+" but the latter will only be set when the domain has active targets");
+     
 -    public void applyConfig();
++    void applyConfig();
+     
+     /** minimum/default TTL here is 300s = 5m */
 -    public long getTimeToLiveSeconds();
++    long getTimeToLiveSeconds();
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
index 0000000,4273dac..e04b8ec
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
@@@ -1,0 -1,199 +1,201 @@@
+ /*
+  * 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.entity.dns.geoscaling;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ import static org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.PROVIDE_CITY_INFO;
+ 
+ import java.net.URI;
+ import java.util.Collection;
+ import java.util.Set;
+ 
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+ import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+ import org.apache.brooklyn.entity.dns.AbstractGeoDnsServiceImpl;
+ import org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.Domain;
+ import org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.SmartSubdomain;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.apache.brooklyn.util.collections.MutableSet;
+ import org.apache.brooklyn.util.http.HttpTool;
+ import org.apache.brooklyn.util.text.Identifiers;
+ import org.apache.brooklyn.util.text.Strings;
+ 
+ public class GeoscalingDnsServiceImpl extends AbstractGeoDnsServiceImpl implements GeoscalingDnsService {
+ 
+     private static final Logger log = LoggerFactory.getLogger(GeoscalingDnsServiceImpl.class);
+ 
+     // Must remember any desired redirection targets if they're specified before configure() has been called.
+     private Set<HostGeoInfo> rememberedTargetHosts;
+     private GeoscalingWebClient webClient;
+     
+     // These are available only after the configure() method has been invoked.
+     private boolean randomizeSmartSubdomainName;
+     private String username;
+     private String password;
+     private String primaryDomainName;
+     private String smartSubdomainName;
+ 
+     public GeoscalingDnsServiceImpl() {
+     }
+ 
+     @Override
+     public void init() {
+         super.init();
+         
+         // defaulting to randomized subdomains makes deploying multiple applications easier
 -        if (getConfig(RANDOMIZE_SUBDOMAIN_NAME)==null) config().set(RANDOMIZE_SUBDOMAIN_NAME, true); 
 -        
 -        Boolean trustAll = getConfig(SSL_TRUST_ALL);
++        if (config().get(RANDOMIZE_SUBDOMAIN_NAME) == null) {
++            config().set(RANDOMIZE_SUBDOMAIN_NAME, true);
++        }
++
++        Boolean trustAll = config().get(SSL_TRUST_ALL);
+         if (Boolean.TRUE.equals(trustAll)) {
+             webClient = new GeoscalingWebClient(HttpTool.httpClientBuilder().trustAll().build());
+         } else {
+             webClient = new GeoscalingWebClient();
+         }
+     }
+     
+     // Ensure our configure() method gets called; may be able to remove this if/when the framework detects this
+     // and invokes the configure() method automatically?
+     @Override
+     public void onManagementBecomingMaster() {
+         try {
+             applyConfig();
+         } catch (Exception e) {
+             // don't prevent management coming up, but do mark it as on fire
+             log.error("Geoscaling did not come up correctly: "+e, e);
+             ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
+         }
+         super.onManagementBecomingMaster();
+     }
+ 
+     boolean isConfigured = false;
+     
+     public synchronized void applyConfig() {        
 -        randomizeSmartSubdomainName = getConfig(RANDOMIZE_SUBDOMAIN_NAME);
 -        username = getConfig(GEOSCALING_USERNAME);
 -        password = getConfig(GEOSCALING_PASSWORD);
 -        primaryDomainName = getConfig(GEOSCALING_PRIMARY_DOMAIN_NAME);
 -        smartSubdomainName = getConfig(GEOSCALING_SMART_SUBDOMAIN_NAME);
++        randomizeSmartSubdomainName = config().get(RANDOMIZE_SUBDOMAIN_NAME);
++        username = config().get(GEOSCALING_USERNAME);
++        password = config().get(GEOSCALING_PASSWORD);
++        primaryDomainName = config().get(GEOSCALING_PRIMARY_DOMAIN_NAME);
++        smartSubdomainName = config().get(GEOSCALING_SMART_SUBDOMAIN_NAME);
+ 
+         // Ensure all mandatory configuration is provided.
+         checkNotNull(username, "The GeoScaling username is not specified");
+         checkNotNull(password, "The GeoScaling password is not specified");
+         checkNotNull(primaryDomainName, "The GeoScaling primary domain name is not specified");
+         
+         if (randomizeSmartSubdomainName) {
+             // if no smart subdomain specified, but random is, use something random
+             if (smartSubdomainName != null) smartSubdomainName += "-";
+             else smartSubdomainName = "";
+             smartSubdomainName += Identifiers.makeRandomId(8);
+         }
+         checkNotNull(smartSubdomainName, "The GeoScaling smart subdomain name is not specified or randomized");
+         
+         String fullDomain = smartSubdomainName+"."+primaryDomainName;
+         log.info("GeoScaling service will configure redirection for '"+fullDomain+"' domain");
+         sensors().set(GEOSCALING_ACCOUNT, username);
+         sensors().set(MANAGED_DOMAIN, fullDomain);
+         sensors().set(HOSTNAME, getHostname());
+         
+         isConfigured = true;
+         
+         if (rememberedTargetHosts != null) {
+             reconfigureService(rememberedTargetHosts);
+             rememberedTargetHosts = null;
+         }
+     }
+     
+     @Override
+     public String getHostname() {
+         String result = getAttribute(MANAGED_DOMAIN);
+         return (Strings.isBlank(result)) ? null : result;
+     }
+     
+     /** minimum/default TTL here is 300s = 5m */
+     public long getTimeToLiveSeconds() { return 5*60; }
+     
+     @Override
+     public void destroy() {
+         setServiceState(Lifecycle.STOPPING);
+         if (!isConfigured) return;
+         
+         // Don't leave randomized subdomains configured on our GeoScaling account.
+         if (randomizeSmartSubdomainName) {
+             webClient.login(username, password);
+             Domain primaryDomain = webClient.getPrimaryDomain(primaryDomainName);
+             SmartSubdomain smartSubdomain = (primaryDomain != null) ? primaryDomain.getSmartSubdomain(smartSubdomainName) : null;
+             if (smartSubdomain != null) {
+                 log.info("Deleting randomized GeoScaling smart subdomain '"+smartSubdomainName+"."+primaryDomainName+"'");
+                 smartSubdomain.delete();
+             }
+             webClient.logout();
+         }
+         
+         super.destroy();
+         
+         isConfigured = false;
+     }
+     
+     protected void reconfigureService(Collection<HostGeoInfo> targetHosts) {
+         if (!isConfigured) {
+             this.rememberedTargetHosts = MutableSet.copyOf(targetHosts);
+             return;
+         }
+         
+         webClient.login(username, password);
+         Domain primaryDomain = webClient.getPrimaryDomain(primaryDomainName);
+         if (primaryDomain==null) 
+             throw new NullPointerException(this+" got null from web client for primary domain "+primaryDomainName);
+         SmartSubdomain smartSubdomain = primaryDomain.getSmartSubdomain(smartSubdomainName);
+         
+         if (smartSubdomain == null) {
+             log.info("GeoScaling {} smart subdomain '{}.{}' does not exist, creating it now", new Object[] {this, smartSubdomainName, primaryDomainName});
+             // TODO use WithMutexes to ensure this is single-entrant
+             primaryDomain.createSmartSubdomain(smartSubdomainName);
+             smartSubdomain = primaryDomain.getSmartSubdomain(smartSubdomainName);
+         }
+         
+         if (smartSubdomain != null) {
+             log.debug("GeoScaling {} being reconfigured to use {}", this, targetHosts);
+             String script = GeoscalingScriptGenerator.generateScriptString(targetHosts);
+             smartSubdomain.configure(PROVIDE_CITY_INFO, script);
+             if (targetHosts.isEmpty()) {
+                 setServiceState(Lifecycle.CREATED);
+                 sensors().set(ROOT_URL, null);
+                 sensors().set(MAIN_URI, null);
+             } else {
+                 setServiceState(Lifecycle.RUNNING);
+                 String domain = getAttribute(MANAGED_DOMAIN);
+                 if (!Strings.isEmpty(domain)) {
+                     sensors().set(ROOT_URL, "http://"+domain+"/");
+                     sensors().set(MAIN_URI, URI.create("http://"+domain+"/"));
+                 }
+             }
+         } else {
+             log.warn("Failed to retrieve or create GeoScaling smart subdomain '"+smartSubdomainName+"."+primaryDomainName+
+                     "', aborting attempt to configure service");
+             setServiceState(Lifecycle.ON_FIRE);
+         }
+         
+         webClient.logout();
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/JavaWebAppSshDriver.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/JavaWebAppSshDriver.java
index 0000000,7791418..e1e30c3
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/JavaWebAppSshDriver.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/JavaWebAppSshDriver.java
@@@ -1,0 -1,205 +1,205 @@@
+ /*
+  * 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.entity.webapp;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ 
+ import java.io.File;
+ import java.net.URI;
+ import java.util.Set;
+ 
+ import com.google.common.net.HostAndPort;
+ import org.apache.brooklyn.core.entity.Attributes;
+ import org.apache.brooklyn.core.location.access.BrooklynAccessUtils;
+ import org.apache.brooklyn.entity.java.JavaSoftwareProcessSshDriver;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
+ import org.apache.brooklyn.util.core.task.DynamicTasks;
+ import org.apache.brooklyn.util.core.task.Tasks;
+ import org.apache.brooklyn.util.core.task.ssh.SshTasks;
+ import org.apache.brooklyn.util.text.Strings;
+ 
+ import com.google.common.collect.ImmutableList;
+ 
+ public abstract class JavaWebAppSshDriver extends JavaSoftwareProcessSshDriver implements JavaWebAppDriver {
+ 
+     public JavaWebAppSshDriver(JavaWebAppSoftwareProcessImpl entity, SshMachineLocation machine) {
+         super(entity, machine);
+     }
+ 
+     public JavaWebAppSoftwareProcessImpl getEntity() {
+         return (JavaWebAppSoftwareProcessImpl) super.getEntity();
+     }
+ 
+     protected boolean isProtocolEnabled(String protocol) {
+         Set<String> protocols = getEnabledProtocols();
+         for (String contender : protocols) {
+             if (protocol.equalsIgnoreCase(contender)) {
+                 return true;
+             }
+         }
+         return false;
+     }
+ 
+     @Override
+     public Set<String> getEnabledProtocols() {
+         return entity.getAttribute(JavaWebAppSoftwareProcess.ENABLED_PROTOCOLS);
+     }
+     
+     @Override
+     public Integer getHttpPort() {
+         return entity.getAttribute(Attributes.HTTP_PORT);
+     }
+ 
+     @Override
+     public Integer getHttpsPort() {
+         return entity.getAttribute(Attributes.HTTPS_PORT);
+     }
+ 
+     @Override
+     public HttpsSslConfig getHttpsSslConfig() {
+         return entity.getAttribute(WebAppServiceConstants.HTTPS_SSL_CONFIG);
+     }
+ 
+     protected String getSslKeystoreUrl() {
+         HttpsSslConfig ssl = getHttpsSslConfig();
+         return (ssl == null) ? null : ssl.getKeystoreUrl();
+     }
+     
+     protected String getSslKeystorePassword() {
+         HttpsSslConfig ssl = getHttpsSslConfig();
+         return (ssl == null) ? null : ssl.getKeystorePassword();
+     }
+     
+     protected String getSslKeyAlias() {
+         HttpsSslConfig ssl = getHttpsSslConfig();
+         return (ssl == null) ? null : ssl.getKeyAlias();
+     }
+ 
+     protected String inferRootUrl() {
+         if (isProtocolEnabled("https")) {
+             Integer port = getHttpsPort();
+             checkNotNull(port, "HTTPS_PORT sensors not set; is an acceptable port available?");
+             HostAndPort accessibleAddress = BrooklynAccessUtils.getBrooklynAccessibleAddress(getEntity(), port);
+             return String.format("https://%s:%s/", accessibleAddress.getHostText(), accessibleAddress.getPort());
+         } else if (isProtocolEnabled("http")) {
+             Integer port = getHttpPort();
+             checkNotNull(port, "HTTP_PORT sensors not set; is an acceptable port available?");
+             HostAndPort accessibleAddress = BrooklynAccessUtils.getBrooklynAccessibleAddress(getEntity(), port);
+             return String.format("http://%s:%s/", accessibleAddress.getHostText(), accessibleAddress.getPort());
+         } else {
+             throw new IllegalStateException("HTTP and HTTPS protocols not enabled for "+entity+"; enabled protocols are "+getEnabledProtocols());
+         }
+     }
+     
+     @Override
+     public void postLaunch() {
+         String rootUrl = inferRootUrl();
+         entity.sensors().set(Attributes.MAIN_URI, URI.create(rootUrl));
+         entity.sensors().set(WebAppService.ROOT_URL, rootUrl);
+     }
+ 
+     /** 
+      * if files should be placed on the server for deployment,
+      * override this to be the sub-directory of the runDir where they should be stored
+      * (or override getDeployDir() if they should be copied somewhere else,
+      * and set this null);
+      * if files are not copied to the server, but injected (e.g. JMX or uploaded)
+      * then override {@link #deploy(String, String)} as appropriate,
+      * using getContextFromDeploymentTargetName(targetName)
+      * and override this to return null
+      */
+     protected abstract String getDeploySubdir();
+     
+     protected String getDeployDir() {
+         if (getDeploySubdir()==null)
+             throw new IllegalStateException("no deployment directory available for "+this);
+         return getRunDir() + "/" + getDeploySubdir();
+     }
+ 
+     @Override
+     public void deploy(File file) {
+         deploy(file, null);
+     }
+ 
+     @Override
+     public void deploy(File f, String targetName) {
+         if (targetName == null) {
+             targetName = f.getName();
+         }
+         deploy(f.toURI().toASCIIString(), targetName);
+     }
+ 
+     /**
+      * Deploys a URL as a webapp at the appserver.
+      *
+      * Returns a token which can be used as an argument to undeploy,
+      * typically the web context with leading slash where the app can be reached (just "/" for ROOT)
+      *
+      * @see JavaWebAppSoftwareProcess#deploy(String, String) for details of how input filenames are handled
+      */
+     @Override
+     public String deploy(final String url, final String targetName) {
+         final String canonicalTargetName = getFilenameContextMapper().convertDeploymentTargetNameToFilename(targetName);
+         final String dest = getDeployDir() + "/" + canonicalTargetName;
+         //write to a .tmp so autodeploy is not triggered during upload
+         final String tmpDest = dest + "." + Strings.makeRandomId(8) + ".tmp";
+         final String msg = String.format("deploying %s to %s:%s", new Object[]{url, getHostname(), dest});
+         log.info(entity + " " + msg);
+         Tasks.setBlockingDetails(msg);
+         try {
+             final String copyTaskMsg = String.format("copying %s to %s:%s", new Object[]{url, getHostname(), tmpDest});
+             DynamicTasks.queue(copyTaskMsg, new Runnable() {
+                 @Override
+                 public void run() {
+                     int result = copyResource(url, tmpDest);
+                     if (result != 0) {
 -                        throw new IllegalStateException("Invalud result " + result + " while " + copyTaskMsg);
++                        throw new IllegalStateException("Invalid result " + result + " while " + copyTaskMsg);
+                     }
+                 }
+             });
+ 
+             // create a backup
+             DynamicTasks.queue(SshTasks.newSshExecTaskFactory(getMachine(), String.format("mv -f %s %s.bak", dest, dest))
+                     .allowingNonZeroExitCode());
+ 
+             //rename temporary upload file to .war to be picked up for deployment
+             DynamicTasks.queue(SshTasks.newSshExecTaskFactory(getMachine(), String.format("mv -f %s %s", tmpDest, dest))
+                     .requiringExitCodeZero());
+             log.debug("{} deployed {} to {}:{}", new Object[]{entity, url, getHostname(), dest});
+ 
+             DynamicTasks.waitForLast();
+         } finally {
+             Tasks.resetBlockingDetails();
+         }
+         return getFilenameContextMapper().convertDeploymentTargetNameToContext(canonicalTargetName);
+     }
+     
+     @Override
+     public void undeploy(String targetName) {
+         String dest = getDeployDir() + "/" + getFilenameContextMapper().convertDeploymentTargetNameToFilename(targetName);
+         log.info("{} undeploying {}:{}", new Object[]{entity, getHostname(), dest});
+         int result = getMachine().execCommands("removing war on undeploy", ImmutableList.of(String.format("rm -f %s", dest)));
+         log.debug("{} undeployed {}:{}: result {}", new Object[]{entity, getHostname(), dest, result});
+     }
+     
+     @Override
+     public FilenameToWebContextMapper getFilenameContextMapper() {
+         return new FilenameToWebContextMapper();
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java
index 0000000,c977a80..e292550
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java
@@@ -1,0 -1,112 +1,114 @@@
+ /*
+  * 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.entity.webapp.jboss;
+ 
+ import java.util.LinkedHashMap;
+ import java.util.Map;
+ import java.util.concurrent.TimeUnit;
+ 
+ import org.apache.brooklyn.api.entity.Entity;
++import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.entity.java.UsesJmx;
+ import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
+ import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig;
+ import org.apache.brooklyn.feed.jmx.JmxFeed;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.base.Functions;
+ 
+ public class JBoss6ServerImpl extends JavaWebAppSoftwareProcessImpl implements JBoss6Server {
+ 
+     public static final Logger log = LoggerFactory.getLogger(JBoss6ServerImpl.class);
+ 
+     private volatile JmxFeed jmxFeed;
+     
+     public JBoss6ServerImpl() {
+         this(new LinkedHashMap(), null);
+     }
+ 
+     public JBoss6ServerImpl(Entity parent) {
+         this(new LinkedHashMap(), parent);
+     }
+ 
+     public JBoss6ServerImpl(Map flags){
+         this(flags, null);
+     }
+ 
+     public JBoss6ServerImpl(Map flags, Entity parent) {
+         super(flags, parent);
+     }
+ 
+     @Override
+     public void connectSensors() {
+         super.connectSensors();
+ 
+         String requestProcessorMbeanName = "jboss.web:type=GlobalRequestProcessor,name=http-*";
+         String serverMbeanName = "jboss.system:type=Server";
+         boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS);
+ 
+         if (isJmxEnabled()) {
+             jmxFeed = JmxFeed.builder()
+                     .entity(this)
+                     .period(500, TimeUnit.MILLISECONDS)
+                     .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_UP)
+                             // TODO instead of setting SERVICE_UP directly, want to use equivalent of 
+                             // addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS).key("serverMBean")...
+                             // but not supported in feed?
+                             .objectName(serverMbeanName)
+                             .attributeName("Started")
+                             .onException(Functions.constant(false))
+                             .suppressDuplicates(true))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(ERROR_COUNT)
+                             .objectName(requestProcessorMbeanName)
+                             .attributeName("errorCount")
+                             .enabled(retrieveUsageMetrics))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT)
+                             .objectName(requestProcessorMbeanName)
+                             .attributeName("requestCount")
++                            .onFailureOrException(EntityFunctions.attribute(this, REQUEST_COUNT))
+                             .enabled(retrieveUsageMetrics))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME)
+                             .objectName(requestProcessorMbeanName)
+                             .attributeName("processingTime")
+                             .enabled(retrieveUsageMetrics))
+                     .build();
+         } else {
+             // if not using JMX
+             log.warn(this+" running without JMX monitoring; limited visibility of service available");
+             connectServiceUpIsRunning();
+         }
+     }
+ 
+     @Override
+     public void disconnectSensors() {
+         super.disconnectSensors();
+         if (jmxFeed != null) jmxFeed.stop();
+         disconnectServiceUpIsRunning();
+     }
+     
+     @Override
+     public Class<JBoss6Driver> getDriverInterface() {
+         return JBoss6Driver.class;
+     }
+ 
+     protected boolean isJmxEnabled() {
+         return (this instanceof UsesJmx) && Boolean.TRUE.equals(getConfig(UsesJmx.USE_JMX));
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
index 0000000,e2411a7..10b9564
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
@@@ -1,0 -1,212 +1,214 @@@
+ /*
+  * 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.entity.webapp.jboss;
+ 
+ import java.util.Map;
+ 
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.core.config.render.RendererHints;
+ import org.apache.brooklyn.core.entity.Attributes;
++import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.core.location.access.BrooklynAccessUtils;
+ import org.apache.brooklyn.enricher.stock.Enrichers;
+ import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
+ import org.apache.brooklyn.feed.http.HttpFeed;
+ import org.apache.brooklyn.feed.http.HttpPollConfig;
+ import org.apache.brooklyn.feed.http.HttpValueFunctions;
+ import org.apache.brooklyn.util.guava.Functionals;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.base.Functions;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.net.HostAndPort;
+ 
+ public class JBoss7ServerImpl extends JavaWebAppSoftwareProcessImpl implements JBoss7Server {
+ 
+     public static final Logger log = LoggerFactory.getLogger(JBoss7ServerImpl.class);
+ 
+     private volatile HttpFeed httpFeed;
+     
+     public JBoss7ServerImpl(){
+         super();
+     }
+ 
+     public JBoss7ServerImpl(@SuppressWarnings("rawtypes") Map flags){
+         this(flags, null);
+     }
+ 
+     public JBoss7ServerImpl(@SuppressWarnings("rawtypes") Map flags, Entity parent) {
+         super(flags, parent);
+     }
+ 
+     @Override
+     public Class<?> getDriverInterface() {
+         return JBoss7Driver.class;
+     }
+ 
+     @Override
+     public JBoss7Driver getDriver() {
+         return (JBoss7Driver) super.getDriver();
+     }
+     
+     static {
+         RendererHints.register(MANAGEMENT_URL, RendererHints.namedActionWithUrl());
+     }
+ 
+     @Override
+     protected void connectSensors() {
+         super.connectSensors();
+ 
+         HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this,
+                 getAttribute(MANAGEMENT_HTTP_PORT) + getConfig(PORT_INCREMENT));
+         
+         String managementUri = String.format("http://%s:%s/management/subsystem/web/connector/http/read-resource",
+                 hp.getHostText(), hp.getPort());
+         sensors().set(MANAGEMENT_URL, managementUri);
+ 
+         if (isHttpMonitoringEnabled()) {
+             log.debug("JBoss sensors for "+this+" reading from "+managementUri);
+             Map<String, String> includeRuntimeUriVars = ImmutableMap.of("include-runtime","true");
+             boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS);
+ 
+             httpFeed = HttpFeed.builder()
+                     .entity(this)
+                     .period(200)
+                     .baseUri(managementUri)
+                     .credentials(getConfig(MANAGEMENT_USER), getConfig(MANAGEMENT_PASSWORD))
+                     .poll(new HttpPollConfig<Integer>(MANAGEMENT_STATUS)
+                             .onSuccess(HttpValueFunctions.responseCode())
+                             .suppressDuplicates(true))
+                     .poll(new HttpPollConfig<Boolean>(MANAGEMENT_URL_UP)
+                             .onSuccess(HttpValueFunctions.responseCodeEquals(200))
+                             .onFailureOrException(Functions.constant(false))
+                             .suppressDuplicates(true))
+                     .poll(new HttpPollConfig<Integer>(REQUEST_COUNT)
+                             .vars(includeRuntimeUriVars)
+                             .onSuccess(HttpValueFunctions.jsonContents("requestCount", Integer.class))
++                            .onFailureOrException(EntityFunctions.attribute(this, REQUEST_COUNT))
+                             .enabled(retrieveUsageMetrics))
+                     .poll(new HttpPollConfig<Integer>(ERROR_COUNT)
+                             .vars(includeRuntimeUriVars)
+                             .onSuccess(HttpValueFunctions.jsonContents("errorCount", Integer.class))
+                             .enabled(retrieveUsageMetrics))
+                     .poll(new HttpPollConfig<Integer>(TOTAL_PROCESSING_TIME)
+                             .vars(includeRuntimeUriVars)
+                             .onSuccess(HttpValueFunctions.jsonContents("processingTime", Integer.class))
+                             .enabled(retrieveUsageMetrics))
+                     .poll(new HttpPollConfig<Integer>(MAX_PROCESSING_TIME)
+                             .vars(includeRuntimeUriVars)
+                             .onSuccess(HttpValueFunctions.jsonContents("maxTime", Integer.class))
+                             .enabled(retrieveUsageMetrics))
+                     .poll(new HttpPollConfig<Long>(BYTES_RECEIVED)
+                             .vars(includeRuntimeUriVars)
+                             // jboss seems to report 0 even if it has received lots of requests; dunno why.
+                             .onSuccess(HttpValueFunctions.jsonContents("bytesReceived", Long.class))
+                             .enabled(retrieveUsageMetrics))
+                     .poll(new HttpPollConfig<Long>(BYTES_SENT)
+                             .vars(includeRuntimeUriVars)
+                             .onSuccess(HttpValueFunctions.jsonContents("bytesSent", Long.class))
+                             .enabled(retrieveUsageMetrics))
+                     .build();
+             
+             enrichers().add(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+                     .from(MANAGEMENT_URL_UP)
+                     .computing(Functionals.ifNotEquals(true).value("Management URL not reachable") )
+                     .build());
+         }
+         
+         connectServiceUpIsRunning();
+     }
+     
+     /**
+      * @deprecated since 0.9.0; now a no-op; marked final to force anyone sub-classing + overriding it to update their code.
+      */
+     @Deprecated
+     protected final void connectServiceUp() {
+     }
+     
+     /**
+      * @deprecated since 0.9.0; now a no-op; marked final to force anyone sub-classing + overriding it to update their code.
+      */
+     @Deprecated
+     protected final void disconnectServiceUp() {
+     }
+     
+     @Override
+     protected void disconnectSensors() {
+         super.disconnectSensors();
+         
+         if (httpFeed != null) httpFeed.stop();
+         disconnectServiceUpIsRunning();
+     }
+     
+     protected boolean isHttpMonitoringEnabled() {
+         return Boolean.TRUE.equals(getConfig(USE_HTTP_MONITORING));
+     }
+     
+     public int getManagementHttpsPort() {
+         return getAttribute(MANAGEMENT_HTTPS_PORT);
+     }
+     
+     public int getManagementHttpPort() {
+         return getAttribute(MANAGEMENT_HTTP_PORT);
+     }
+     
+     public int getManagementNativePort() {
+         return getAttribute(MANAGEMENT_NATIVE_PORT);
+     }
+     
+     public int getPortOffset() {
+         return getConfig(PORT_INCREMENT);
+     }
+     
+     public boolean isWelcomeRootEnabled() {
+         return false;
+     }
+ 
+     public String getBindAddress() {
+         return getConfig(BIND_ADDRESS);
+     }
+     
+     public String getManagementBindAddress() {
+         return getConfig(BIND_ADDRESS);
+     }
+     
+     public String getUnsecureBindAddress() {
+         return getConfig(BIND_ADDRESS);
+     }
+     
+     // If empty-string, disables Management security (!) by excluding the security-realm attribute
+     public String getHttpManagementInterfaceSecurityRealm() {
+         return "";
+     }
+ 
+     public int getDeploymentTimeoutSecs() {
+         return getConfig(DEPLOYMENT_TIMEOUT);
+     }
+ 
+     /** Path of the keystore file on the AS7 server */
+     public String getHttpsSslKeystoreFile() {
+         return getDriver().getSslKeystoreFile();
+     }
+     
+     @Override
+     public String getShortName() {
+         return "JBossAS7";
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
index 0000000,c772b51..24a6c6f
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
@@@ -1,0 -1,140 +1,142 @@@
+ /*
+  * 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.entity.webapp.jetty;
+ 
+ import java.util.concurrent.TimeUnit;
+ 
++import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.enricher.stock.Enrichers;
+ import org.apache.brooklyn.entity.java.JavaAppUtils;
+ import org.apache.brooklyn.entity.java.UsesJmx;
+ import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
+ import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig;
+ import org.apache.brooklyn.feed.jmx.JmxFeed;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.base.Functions;
+ import com.google.common.base.Predicates;
+ 
+ /**
+  * An {@link org.apache.brooklyn.api.entity.Entity} that represents a single Jetty instance.
+  */
+ public class Jetty6ServerImpl extends JavaWebAppSoftwareProcessImpl implements Jetty6Server {
+ 
+     private static final Logger log = LoggerFactory.getLogger(Jetty6ServerImpl.class);
+ 
+     private volatile JmxFeed jmxFeedJetty, jmxFeedMx;
+ 
+     @Override
+     public void connectSensors() {
+         super.connectSensors();
+         
+         if (getDriver().isJmxEnabled()) {
+             String serverMbeanName = "org.mortbay.jetty:type=server,id=0";
+             String statsMbeanName = "org.mortbay.jetty.handler:type=atomicstatisticshandler,id=0";
+ 
+             jmxFeedJetty = JmxFeed.builder()
+                     .entity(this)
+                     .period(500, TimeUnit.MILLISECONDS)
+                     .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_UP)
+                             .objectName(serverMbeanName)
+                             .attributeName("running")
+                             .onSuccess(Functions.forPredicate(Predicates.<Object>equalTo(true)))
+                             .setOnFailureOrException(false))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT)
+                             .objectName(statsMbeanName)
 -                            .attributeName("requests"))
++                            .attributeName("requests")
++                            .onFailureOrException(EntityFunctions.attribute(this, REQUEST_COUNT)))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(RESPONSES_4XX_COUNT)
+                             .objectName(statsMbeanName)
+                             .attributeName("responses4xx"))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(RESPONSES_5XX_COUNT)
+                             .objectName(statsMbeanName)
+                             .attributeName("responses5xx"))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME)
+                             .objectName(statsMbeanName)
+                             .attributeName("requestTimeTotal"))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(MAX_PROCESSING_TIME)
+                             .objectName(statsMbeanName)
+                             .attributeName("requestTimeMax"))
+                     // NB: requestsActive may be useful
+                     .build();
+             
+             enrichers().add(Enrichers.builder()
+                     .combining(RESPONSES_4XX_COUNT, RESPONSES_5XX_COUNT)
+                     .publishing(ERROR_COUNT)
+                     .computingSum()
+                     .build());
+ 
+             jmxFeedMx = JavaAppUtils.connectMXBeanSensors(this);
+         } else {
+             // if not using JMX
+             log.warn("Jetty running without JMX monitoring; limited visibility of service available");
+             // TODO we could do simple things, like check that web server is accepting connections
+         }
+     }
+ 
+     @Override
+     protected void disconnectSensors() {
+         if (jmxFeedJetty != null) jmxFeedJetty.stop();
+         if (jmxFeedMx != null) jmxFeedMx.stop();
+         super.disconnectSensors();
+     }
+ 
+     public Integer getJmxPort() {
+         if (((Jetty6Driver) getDriver()).isJmxEnabled()) {
+             return getAttribute(UsesJmx.JMX_PORT);
+         } else {
+             return Integer.valueOf(-1);
+         }
+     }
+ 
+     @Override
+     public Class getDriverInterface() {
+         return Jetty6Driver.class;
+     }
+     
+     @Override
+     public String getShortName() {
+         return "Jetty";
+     }
+     
+     @Override
+     public void deploy(String url, String targetName) {
+         super.deploy(url, targetName);
+         restartIfRunning();
+     }
+ 
+     @Override
+     public void undeploy(String targetName) {
+         super.undeploy(targetName);
+         restartIfRunning();
+     }
+     
+     protected void restartIfRunning() {
+         // TODO for now we simply restart jetty to achieve "hot deployment"; should use the config mechanisms
+         Lifecycle serviceState = getAttribute(SERVICE_STATE_ACTUAL);
+         if (serviceState == Lifecycle.RUNNING)
+             restart();
+         // may need a restart also if deploy effector is done in parallel to starting
+         // but note this routine is used by initialDeployWars so just being in starting state is not enough!
+     }
+ 
+ }
+ 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
index 0000000,22cab1f..6302278
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
@@@ -1,0 -1,119 +1,125 @@@
+ /*
+  * 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.entity.webapp.tomcat;
+ 
+ import static java.lang.String.format;
+ 
+ import java.util.concurrent.TimeUnit;
+ 
++import javax.annotation.Nullable;
++
++import org.apache.brooklyn.api.sensor.AttributeSensor;
++import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.entity.java.JavaAppUtils;
+ import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
+ import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig;
+ import org.apache.brooklyn.feed.jmx.JmxFeed;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
++import com.google.common.base.Function;
+ import com.google.common.base.Functions;
+ import com.google.common.base.Predicates;
+ 
+ /**
+  * An {@link org.apache.brooklyn.api.entity.Entity} that represents a single Tomcat instance.
+  */
+ public class TomcatServerImpl extends JavaWebAppSoftwareProcessImpl implements TomcatServer {
+ 
+     private static final Logger LOG = LoggerFactory.getLogger(TomcatServerImpl.class);
+ 
+     public TomcatServerImpl() {
+         super();
+     }
+ 
+     private volatile JmxFeed jmxWebFeed;
+     private volatile JmxFeed jmxAppFeed;
+ 
+     @Override
+     public void connectSensors() {
+         super.connectSensors();
+ 
+         if (getDriver().isJmxEnabled()) {
+             String requestProcessorMbeanName = "Catalina:type=GlobalRequestProcessor,name=\"http-*\"";
+ 
+             Integer port = isHttpsEnabled() ? getAttribute(HTTPS_PORT) : getAttribute(HTTP_PORT);
+             String connectorMbeanName = format("Catalina:type=Connector,port=%s", port);
+             boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS);
+ 
+             jmxWebFeed = JmxFeed.builder()
+                     .entity(this)
+                     .period(3000, TimeUnit.MILLISECONDS)
+                     .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_PROCESS_IS_RUNNING)
+                             // TODO Want to use something different from SERVICE_PROCESS_IS_RUNNING,
+                             // to indicate this is jmx MBean's reported state (or failure to connect)
+                             .objectName(connectorMbeanName)
+                             .attributeName("stateName")
+                             .onSuccess(Functions.forPredicate(Predicates.<Object>equalTo("STARTED")))
+                             .setOnFailureOrException(false)
+                             .suppressDuplicates(true))
+                     .pollAttribute(new JmxAttributePollConfig<String>(CONNECTOR_STATUS)
+                             .objectName(connectorMbeanName)
+                             .attributeName("stateName")
+                             .suppressDuplicates(true))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(ERROR_COUNT)
+                             .objectName(requestProcessorMbeanName)
+                             .attributeName("errorCount")
+                             .enabled(retrieveUsageMetrics))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT)
+                             .objectName(requestProcessorMbeanName)
+                             .attributeName("requestCount")
 -                            .enabled(retrieveUsageMetrics))
++                            .enabled(retrieveUsageMetrics)
++                            .onFailureOrException(EntityFunctions.attribute(this, REQUEST_COUNT)))
+                     .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME)
+                             .objectName(requestProcessorMbeanName)
+                             .attributeName("processingTime")
+                             .enabled(retrieveUsageMetrics))
+                     .build();
+ 
+             jmxAppFeed = JavaAppUtils.connectMXBeanSensors(this);
+         } else {
+             // if not using JMX
+             LOG.warn("Tomcat running without JMX monitoring; limited visibility of service available");
+             connectServiceUpIsRunning();
+         }
+     }
+ 
+     @Override
+     public void disconnectSensors() {
+         super.disconnectSensors();
+         if (getDriver() != null && getDriver().isJmxEnabled()) {
+            if (jmxWebFeed != null) jmxWebFeed.stop();
+            if (jmxAppFeed != null) jmxAppFeed.stop();
+         } else {
+             disconnectServiceUpIsRunning();
+         }
+     }
+ 
+     @SuppressWarnings("rawtypes")
+     @Override
+     public Class getDriverInterface() {
+         return TomcatDriver.class;
+     }
+     
+     @Override
+     public String getShortName() {
+         return "Tomcat";
+     }
+ }
+