http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/classloading/ClassLoaderFromBrooklynClassLoadingContext.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/classloading/ClassLoaderFromBrooklynClassLoadingContext.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/classloading/ClassLoaderFromBrooklynClassLoadingContext.java
new file mode 100644
index 0000000..33aa4ca
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/classloading/ClassLoaderFromBrooklynClassLoadingContext.java
@@ -0,0 +1,66 @@
+/*
+ * 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.management.classloading;
+
+import java.net.URL;
+
+import 
org.apache.brooklyn.api.management.classloading.BrooklynClassLoadingContext;
+
+public class ClassLoaderFromBrooklynClassLoadingContext extends ClassLoader {
+
+    /** Constructs a {@link ClassLoader} which delegates to the given {@link 
BrooklynClassLoadingContext} */
+    public static ClassLoader of(BrooklynClassLoadingContext clc) {
+        return new ClassLoaderFromBrooklynClassLoadingContext(clc);
+    }
+    
+    protected final BrooklynClassLoadingContext clc;
+
+    protected 
ClassLoaderFromBrooklynClassLoadingContext(BrooklynClassLoadingContext clc) {
+        this.clc = clc;
+    }
+    
+    @Override
+    public Class<?> findClass(String className) throws ClassNotFoundException {
+        Class<?> result = clc.loadClass(className);
+        if (result!=null) return result;
+        
+        // last resort. see comment in XStream CompositeClassLoader
+        ClassLoader contextClassLoader = 
Thread.currentThread().getContextClassLoader();
+        if (contextClassLoader != null) {
+            result = contextClassLoader.loadClass(className);
+            if (result!=null) return result;
+        }
+        return null;
+    }
+    
+    @Override
+    protected URL findResource(String name) {
+        URL result = clc.getResource(name);
+        if (result!=null) return result;
+        
+        // last resort. see comment in XStream CompositeClassLoader
+        ClassLoader contextClassLoader = 
Thread.currentThread().getContextClassLoader();
+        if (contextClassLoader != null) {
+            result = contextClassLoader.getResource(name);
+            if (result!=null) return result;
+        }
+        return null;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/classloading/JavaBrooklynClassLoadingContext.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/classloading/JavaBrooklynClassLoadingContext.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/classloading/JavaBrooklynClassLoadingContext.java
new file mode 100644
index 0000000..99fa15b
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/classloading/JavaBrooklynClassLoadingContext.java
@@ -0,0 +1,123 @@
+/*
+ * 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.management.classloading;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+
+import com.google.common.base.Objects;
+
+public class JavaBrooklynClassLoadingContext extends 
AbstractBrooklynClassLoadingContext {
+
+    // on deserialization this loader is replaced with the catalog's root 
loader;
+    // may cause problems for non-osgi catalog items, but that's a reasonable 
trade-off,
+    // should this be serialized (e.g. in SpecialFlagsTransformer) in such a 
case!
+    private final transient ClassLoader loader;
+
+    /**
+     * @deprecated since 0.7.0 only for legacy catalog items which provide a 
non-osgi loader; see {@link #newDefault(ManagementContext)}
+     */ @Deprecated
+    public static JavaBrooklynClassLoadingContext create(ClassLoader loader) {
+        return new JavaBrooklynClassLoadingContext(null, checkNotNull(loader, 
"loader"));
+    }
+    
+    /**
+     * At least one of mgmt or loader must not be null.
+     * @deprecated since 0.7.0 only for legacy catalog items which provide a 
non-osgi loader; see {@link #newDefault(ManagementContext)}
+     */ @Deprecated
+    public static JavaBrooklynClassLoadingContext create(ManagementContext 
mgmt, ClassLoader loader) {
+        checkState(mgmt != null || loader != null, "mgmt and loader must not 
both be null");
+        return new JavaBrooklynClassLoadingContext(mgmt, loader);
+    }
+    
+    public static JavaBrooklynClassLoadingContext create(ManagementContext 
mgmt) {
+        return new JavaBrooklynClassLoadingContext(checkNotNull(mgmt, "mgmt"), 
null);
+    }
+
+    @Deprecated /** @deprecated since 0.7.0 use {@link 
#create(ManagementContext)} */
+    public static JavaBrooklynClassLoadingContext newDefault(ManagementContext 
mgmt) {
+        return new JavaBrooklynClassLoadingContext(checkNotNull(mgmt, "mgmt"), 
null);
+    }
+
+    @Deprecated /** @deprecated since 0.7.0 will become private; use one of 
the static methods to instantiate */
+    public JavaBrooklynClassLoadingContext(ManagementContext mgmt, ClassLoader 
loader) {
+        super(mgmt);
+        this.loader = loader;
+    }
+    
+    private ClassLoader getClassLoader() {
+        if (loader != null) return loader;
+        if (mgmt!=null) return mgmt.getCatalogClassLoader();
+        return JavaBrooklynClassLoadingContext.class.getClassLoader();
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public Maybe<Class<?>> tryLoadClass(String className) {
+        try {
+            return (Maybe) Maybe.of(getClassLoader().loadClass(className));
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            return Maybe.absent("Invalid class: "+className, e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "java:"+getClassLoader();
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(super.hashCode(), getClassLoader());
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (!super.equals(obj)) return false;
+        if (!(obj instanceof JavaBrooklynClassLoadingContext)) return false;
+        if (!Objects.equal(getClassLoader(), 
((JavaBrooklynClassLoadingContext)obj).getClassLoader())) return false;
+        return true;
+    }
+
+    @Override
+    public URL getResource(String name) {
+        return getClassLoader().getResource(name);
+    }
+
+    @Override
+    public Iterable<URL> getResources(String name) {
+        Enumeration<URL> resources;
+        try {
+            resources = getClassLoader().getResources(name);
+        } catch (IOException e) {
+            throw Exceptions.propagate(e);
+        }
+        return Collections.list(resources);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/classloading/OsgiBrooklynClassLoadingContext.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/classloading/OsgiBrooklynClassLoadingContext.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/classloading/OsgiBrooklynClassLoadingContext.java
new file mode 100644
index 0000000..af5920b
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/classloading/OsgiBrooklynClassLoadingContext.java
@@ -0,0 +1,147 @@
+/*
+ * 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.management.classloading;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.management.entitlement.EntitlementClass;
+import org.apache.brooklyn.core.management.entitlement.Entitlements;
+import org.apache.brooklyn.core.management.ha.OsgiManager;
+import org.apache.brooklyn.core.management.internal.ManagementContextInternal;
+
+import brooklyn.catalog.internal.CatalogUtils;
+import brooklyn.util.guava.Maybe;
+
+import com.google.common.base.Objects;
+
+public class OsgiBrooklynClassLoadingContext extends 
AbstractBrooklynClassLoadingContext {
+
+    private final String catalogItemId;
+    private boolean hasBundles = false;
+    private transient Collection<CatalogBundle> _bundles;
+
+    public OsgiBrooklynClassLoadingContext(ManagementContext mgmt, String 
catalogItemId, Collection<CatalogBundle> bundles) {
+        super(mgmt);
+        this._bundles = bundles;
+        this.hasBundles = bundles!=null && !bundles.isEmpty();
+        this.catalogItemId = catalogItemId;
+    }
+
+    public Collection<CatalogBundle> getBundles() {
+        if (_bundles!=null || !hasBundles) return _bundles;
+        CatalogItem<?, ?> cat = 
CatalogUtils.getCatalogItemOptionalVersion(mgmt, catalogItemId);
+        if (cat==null) {
+            throw new IllegalStateException("Catalog item not found for 
"+catalogItemId+"; cannot create loading context");
+        }
+        _bundles = cat.getLibraries();
+        return _bundles;
+    }
+    
+    @Override
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public Maybe<Class<?>> tryLoadClass(String className) {
+        Maybe<Class<Object>> clazz = null;
+        Maybe<OsgiManager> osgi = null;
+        if (mgmt!=null) {
+            osgi = ((ManagementContextInternal)mgmt).getOsgiManager();
+            if (osgi.isPresent() && getBundles()!=null && 
!getBundles().isEmpty()) {
+                if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), 
Entitlements.SEE_CATALOG_ITEM, catalogItemId))
+                    return Maybe.absent("Not entitled to use this catalog 
entry");
+                
+                clazz = osgi.get().tryResolveClass(className, getBundles());
+                if (clazz.isPresent())
+                    return (Maybe)clazz;
+            }
+        }
+        
+        if (clazz!=null) { 
+            // if OSGi bundles were defined and failed, then use its error 
message
+            return (Maybe)clazz;
+        }
+        // else determine best message
+        if (mgmt==null) return Maybe.absent("No mgmt context available for 
loading "+className);
+        if (osgi!=null && osgi.isAbsent()) return Maybe.absent("OSGi not 
available on mgmt for loading "+className);
+        if (!hasBundles)
+            return Maybe.absent("No bundles available for loading "+className);
+        return Maybe.absent("Inconsistent state 
("+mgmt+"/"+osgi+"/"+getBundles()+" loading "+className);
+    }
+
+    @Override
+    public String toString() {
+        return "OSGi:"+catalogItemId+"["+getBundles()+"]";
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(super.hashCode(), getBundles(), catalogItemId);
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (!super.equals(obj)) return false;
+        if (!(obj instanceof OsgiBrooklynClassLoadingContext)) return false;
+
+        OsgiBrooklynClassLoadingContext other = 
(OsgiBrooklynClassLoadingContext)obj;
+        if (!catalogItemId.equals(other.catalogItemId)) return false;
+        if (!Objects.equal(getBundles(), other.getBundles())) return false;
+        return true;
+    }
+
+    @Override
+    public URL getResource(String name) {
+        if (mgmt != null && isEntitledToSeeCatalogItem()) {
+            Maybe<OsgiManager> osgi = ((ManagementContextInternal) 
mgmt).getOsgiManager();
+            if (osgi.isPresent() && hasBundles) {
+                return osgi.get().getResource(name, getBundles());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Iterable<URL> getResources(String name) {
+        if (mgmt != null && isEntitledToSeeCatalogItem()) {
+            Maybe<OsgiManager> osgi = ((ManagementContextInternal) 
mgmt).getOsgiManager();
+            if (osgi.isPresent() && hasBundles) {
+                return osgi.get().getResources(name, getBundles());
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    public String getCatalogItemId() {
+        return catalogItemId;
+    }
+
+    /**
+     * @return true if the current entitlement context may {@link 
Entitlements#SEE_CATALOG_ITEM see}
+     * {@link #getCatalogItemId}.
+     */
+    private boolean isEntitledToSeeCatalogItem() {
+        return Entitlements.isEntitled(mgmt.getEntitlementManager(),
+                Entitlements.SEE_CATALOG_ITEM,
+                catalogItemId);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/BasicEntitlementClassDefinition.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/BasicEntitlementClassDefinition.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/BasicEntitlementClassDefinition.java
new file mode 100644
index 0000000..07544a6
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/BasicEntitlementClassDefinition.java
@@ -0,0 +1,56 @@
+/*
+ * 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.management.entitlement;
+
+import org.apache.brooklyn.api.management.entitlement.EntitlementClass;
+
+import com.google.common.base.Objects;
+import com.google.common.reflect.TypeToken;
+
+
+public class BasicEntitlementClassDefinition<T> implements EntitlementClass<T> 
{
+
+    private final String identifier;
+    private final TypeToken<T> argumentType;
+
+    public BasicEntitlementClassDefinition(String identifier, TypeToken<T> 
argumentType) {
+        this.identifier = identifier;
+        this.argumentType = argumentType;
+    }
+    
+    public BasicEntitlementClassDefinition(String identifier, Class<T> 
argumentType) {
+        this.identifier = identifier;
+        this.argumentType = TypeToken.of(argumentType);
+    }
+    
+    @Override
+    public String entitlementClassIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public TypeToken<T> entitlementClassArgumentType() {
+        return argumentType;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this).add("identitifier", 
identifier).add("argumentType", argumentType).toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementManagerAdapter.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementManagerAdapter.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementManagerAdapter.java
new file mode 100644
index 0000000..a331a48
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementManagerAdapter.java
@@ -0,0 +1,133 @@
+/*
+ * 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.management.entitlement;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.management.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.management.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.management.entitlement.EntitlementManager;
+import 
org.apache.brooklyn.core.management.entitlement.Entitlements.EntitlementClassesHandler;
+import 
org.apache.brooklyn.core.management.entitlement.Entitlements.EntityAndItem;
+import 
org.apache.brooklyn.core.management.entitlement.Entitlements.StringAndArgument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * provides an easy entry point to supplying entitlements, by providing the 
dispatch and defining the additional methods
+ * which have to be supplied.
+ * <p>
+ * note that this class may change as versions change, deliberately breaking 
backwards compatibility
+ * to ensure all permissions are used.
+ * <p>
+ * @since 0.7.0 */
+@Beta
+public abstract class EntitlementManagerAdapter implements EntitlementManager {
+
+    private static final Logger log = 
LoggerFactory.getLogger(EntitlementManagerAdapter.class);
+    
+    protected class Handler implements EntitlementClassesHandler<Boolean> {
+        final EntitlementContext context;
+        protected Handler(EntitlementContext context) {
+            this.context = context;
+        }
+        
+        @Override
+        public Boolean handleSeeCatalogItem(String catalogItemId) {
+            return isEntitledToSeeCatalogItem(context, catalogItemId);
+        }
+        @Override
+        public Boolean handleAddCatalogItem(Object catalogItemBeingAdded) {
+            return isEntitledToAddCatalogItem(context, catalogItemBeingAdded);
+        }
+        @Override
+        public Boolean handleModifyCatalogItem(StringAndArgument 
catalogItemIdAndModification) {
+            return isEntitledToModifyCatalogItem(context, 
catalogItemIdAndModification==null ? null : 
catalogItemIdAndModification.getString(),
+                catalogItemIdAndModification==null ? null : 
catalogItemIdAndModification.getArgument());
+        }
+        
+        @Override
+        public Boolean handleSeeEntity(Entity entity) {
+            return isEntitledToSeeEntity(context, entity);
+        }
+        @Override
+        public Boolean handleSeeSensor(EntityAndItem<String> sensorInfo) {
+            return isEntitledToSeeSensor(context, sensorInfo.getEntity(), 
sensorInfo.getItem());
+        }
+        @Override
+        public Boolean handleInvokeEffector(EntityAndItem<StringAndArgument> 
effectorInfo) {
+            StringAndArgument item = effectorInfo.getItem();
+            return isEntitledToInvokeEffector(context, 
effectorInfo.getEntity(), item==null ? null : item.getString(), item==null ? 
null : item.getArgument());
+        }
+        @Override
+        public Boolean handleModifyEntity(Entity entity) {
+            return isEntitledToModifyEntity(context, entity);
+        }
+
+        @Override
+        public Boolean handleDeployApplication(Object app) {
+            return isEntitledToDeployApplication(context, app);
+        }
+
+        @Override
+        public Boolean handleSeeAllServerInfo() {
+            return isEntitledToSeeAllServerInfo(context);
+        }
+
+        @Override
+        public Boolean handleSeeServerStatus() {
+            return isEntitledToSeeServerStatus(context);
+        }
+
+        @Override
+        public Boolean handleRoot() {
+            return isEntitledToRoot(context);
+        }
+    }
+    
+    @Override
+    public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+        if (log.isTraceEnabled()) {
+            log.trace("Checking entitlement of "+context+" to 
"+entitlementClass+" "+entitlementClassArgument);
+        }
+        
+        if (isEntitledToRoot( context )) return true;
+        
+        Handler handler = new Handler(context);
+        return Entitlements.EntitlementClassesEnum.of(entitlementClass).handle(
+            handler, entitlementClassArgument);
+    }
+
+    protected abstract boolean isEntitledToSeeCatalogItem(EntitlementContext 
context, String catalogItemId);
+    /** passes item to be added, either yaml, or possibly null if any addition 
allowed (eg when resetting) */
+    protected abstract boolean isEntitledToAddCatalogItem(EntitlementContext 
context, Object catalogItemBeingAdded);
+    /** passes item being modified, as ID and description of modification, 
both possibly null if any modification is allowed (eg when resetting) */
+    protected abstract boolean 
isEntitledToModifyCatalogItem(EntitlementContext context, String catalogItemId, 
Object catalogItemModification);
+    protected abstract boolean isEntitledToSeeSensor(EntitlementContext 
context, Entity entity, String sensorName);
+    protected abstract boolean isEntitledToSeeEntity(EntitlementContext 
context, Entity entity);
+    /** arguments might be null, a map, or a list, depending how/where invoked 
*/
+    protected abstract boolean isEntitledToInvokeEffector(EntitlementContext 
context, Entity entity, String effectorName, Object arguments);
+    protected abstract boolean isEntitledToModifyEntity(EntitlementContext 
context, Entity entity);
+    protected abstract boolean 
isEntitledToDeployApplication(EntitlementContext context, Object app);
+    protected abstract boolean isEntitledToSeeAllServerInfo(EntitlementContext 
context);
+    protected abstract boolean isEntitledToSeeServerStatus(EntitlementContext 
context);
+    protected abstract boolean isEntitledToRoot(EntitlementContext context);
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementPredicates.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementPredicates.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementPredicates.java
new file mode 100644
index 0000000..085fb9e
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementPredicates.java
@@ -0,0 +1,40 @@
+/*
+ * 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.management.entitlement;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.management.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.management.entitlement.EntitlementManager;
+
+import com.google.common.base.Predicate;
+
+public class EntitlementPredicates {
+
+    public static <T> Predicate<T> isEntitled(final EntitlementManager 
entitlementManager, final EntitlementClass<T> entitlementClass) {
+
+        return new Predicate<T>() {
+            @Override
+            public boolean apply(@Nullable T t) {
+                return Entitlements.isEntitled(entitlementManager, 
entitlementClass, t);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/Entitlements.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/Entitlements.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/Entitlements.java
new file mode 100644
index 0000000..6bf9329
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/Entitlements.java
@@ -0,0 +1,418 @@
+/*
+ * 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.management.entitlement;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.management.Task;
+import org.apache.brooklyn.api.management.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.management.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.management.entitlement.EntitlementManager;
+import org.apache.brooklyn.core.management.internal.ManagementContextInternal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.javalang.Reflections;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.text.Strings;
+
+/** @since 0.7.0 */
+@Beta
+public class Entitlements {
+
+    private static final Logger log = 
LoggerFactory.getLogger(Entitlements.class);
+    
+    // ------------------- individual permissions
+    
+    public static EntitlementClass<String> SEE_CATALOG_ITEM = new 
BasicEntitlementClassDefinition<String>("catalog.see", String.class); 
+    public static EntitlementClass<Object> ADD_CATALOG_ITEM = new 
BasicEntitlementClassDefinition<Object>("catalog.add", Object.class); 
+    public static EntitlementClass<StringAndArgument> MODIFY_CATALOG_ITEM = 
new BasicEntitlementClassDefinition<StringAndArgument>("catalog.modify", 
StringAndArgument.class); 
+    
+    public static EntitlementClass<Entity> SEE_ENTITY = new 
BasicEntitlementClassDefinition<Entity>("entity.see", Entity.class);
+    public static EntitlementClass<EntityAndItem<String>> SEE_SENSOR = new 
BasicEntitlementClassDefinition<EntityAndItem<String>>("sensor.see", 
EntityAndItem. typeToken(String.class));
+    // string is effector name; argument may be a map or a list, depending how 
the args were supplied
+    public static EntitlementClass<EntityAndItem<StringAndArgument>> 
INVOKE_EFFECTOR = new 
BasicEntitlementClassDefinition<EntityAndItem<StringAndArgument>>("effector.invoke",
 EntityAndItem.typeToken(StringAndArgument.class));
+    public static EntitlementClass<Entity> MODIFY_ENTITY = new 
BasicEntitlementClassDefinition<Entity>("entity.modify", Entity.class);
+    
+    /** the permission to deploy an application, where parameter is some 
representation of the app to be deployed (spec instance or yaml plan) */
+    public static EntitlementClass<Object> DEPLOY_APPLICATION = new 
BasicEntitlementClassDefinition<Object>("app.deploy", Object.class);
+
+    /** catch-all for catalog, locations, scripting, usage, etc - exporting 
persistence, shutting down, etc;
+     * this is significantly more powerful than {@link #SERVER_STATUS}.
+     * NB: this may be refactored and deprecated in future */
+    public static EntitlementClass<Void> SEE_ALL_SERVER_INFO = new 
BasicEntitlementClassDefinition<Void>("server.info.all.see", Void.class);
+
+    /** permission to see general server status info: basically HA status; not 
nearly as much as {@link #SEE_ALL_SERVER_INFO} */
+    public static EntitlementClass<Void> SERVER_STATUS = new 
BasicEntitlementClassDefinition<Void>("server.status", Void.class);
+    
+    /** permission to run untrusted code or embedded scripts at the server; 
+     * secondary check required for any operation which could potentially 
grant root-level access */ 
+    public static EntitlementClass<Void> ROOT = new 
BasicEntitlementClassDefinition<Void>("root", Void.class);
+
+    @SuppressWarnings("unchecked")
+    public static enum EntitlementClassesEnum {
+        ENTITLEMENT_SEE_CATALOG_ITEM(SEE_CATALOG_ITEM) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleSeeCatalogItem((String)argument); } },
+        ENTITLEMENT_ADD_CATALOG_ITEM(ADD_CATALOG_ITEM) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleAddCatalogItem(argument); } },
+        ENTITLEMENT_MODIFY_CATALOG_ITEM(MODIFY_CATALOG_ITEM) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleModifyCatalogItem((StringAndArgument)argument); } },
+        
+        ENTITLEMENT_SEE_ENTITY(SEE_ENTITY) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleSeeEntity((Entity)argument); } },
+        ENTITLEMENT_SEE_SENSOR(SEE_SENSOR) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleSeeSensor((EntityAndItem<String>)argument); } },
+        ENTITLEMENT_INVOKE_EFFECTOR(INVOKE_EFFECTOR) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleInvokeEffector((EntityAndItem<StringAndArgument>)argument); } },
+        ENTITLEMENT_MODIFY_ENTITY(MODIFY_ENTITY) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleModifyEntity((Entity)argument); } },
+        
+        ENTITLEMENT_DEPLOY_APPLICATION(DEPLOY_APPLICATION) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleDeployApplication(argument); } },
+        
+        ENTITLEMENT_SEE_ALL_SERVER_INFO(SEE_ALL_SERVER_INFO) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleSeeAllServerInfo(); } },
+        ENTITLEMENT_SERVER_STATUS(SERVER_STATUS) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleSeeServerStatus(); } },
+        ENTITLEMENT_ROOT(ROOT) { public <T> T 
handle(EntitlementClassesHandler<T> handler, Object argument) { return 
handler.handleRoot(); } },
+        ;
+        
+        private EntitlementClass<?> entitlementClass;
+
+        private EntitlementClassesEnum(EntitlementClass<?> specificClass) {
+            this.entitlementClass = specificClass;
+        }
+        public EntitlementClass<?> getEntitlementClass() {
+            return entitlementClass;
+        }
+
+        public abstract <T> T handle(EntitlementClassesHandler<T> handler, 
Object argument);
+        
+        public static EntitlementClassesEnum of(EntitlementClass<?> 
entitlementClass) {
+            for (EntitlementClassesEnum x: values()) {
+                if (entitlementClass.equals(x.getEntitlementClass())) return x;
+            }
+            return null;
+        }
+    }
+    
+    public interface EntitlementClassesHandler<T> {
+        public T handleSeeCatalogItem(String catalogItemId);
+        public T handleSeeServerStatus();
+        public T handleAddCatalogItem(Object catalogItemBeingAdded);
+        public T handleModifyCatalogItem(StringAndArgument 
catalogItemIdAndModification);
+        public T handleSeeEntity(Entity entity);
+        public T handleSeeSensor(EntityAndItem<String> sensorInfo);
+        public T handleInvokeEffector(EntityAndItem<StringAndArgument> 
effectorInfo);
+        public T handleModifyEntity(Entity entity);
+        public T handleDeployApplication(Object app);
+        public T handleSeeAllServerInfo();
+        public T handleRoot();
+    }
+    
+    protected static class Pair<T1,T2> {
+        protected final T1 p1;
+        protected final T2 p2;
+        protected Pair(T1 p1, T2 p2) { this.p1 = p1; this.p2 = p2; }
+    }
+    public static class EntityAndItem<T> extends Pair<Entity,T> {
+        public static <TT> TypeToken<EntityAndItem<TT>> typeToken(Class<TT> 
type) {
+            return new TypeToken<Entitlements.EntityAndItem<TT>>() {
+                private static final long serialVersionUID = 
-738154831809025407L;
+            };
+        }
+        public EntityAndItem(Entity entity, T item) { super (entity, item); }
+        public Entity getEntity() { return p1; }
+        public T getItem() { return p2; }
+        public static <T> EntityAndItem<T> of(Entity entity, T item) {
+            return new EntityAndItem<T>(entity, item);
+        }
+    }
+    
+    public static class StringAndArgument extends Pair<String,Object> {
+        public StringAndArgument(String string, Object argument) { 
super(string, argument); }
+        public String getString() { return p1; }
+        public Object getArgument() { return p2; }
+        public static StringAndArgument of(String string, Object argument) {
+            return new StringAndArgument(string, argument);
+        }
+    }
+
+    /** 
+     * These lifecycle operations are currently treated as effectors. This may 
change in the future.
+     * @since 0.7.0 */
+    @Beta
+    public static class LifecycleEffectors {
+        public static final String DELETE = "delete";
+    }
+    
+    // ------------- permission sets -------------
+    
+    /** always ALLOW access to everything */
+    public static EntitlementManager root() {
+        return new EntitlementManager() {
+            @Override
+            public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> permission, T typeArgument) {
+                return true;
+            }
+            @Override
+            public String toString() {
+                return "Entitlements.root";
+            }
+        };
+    }
+
+    /** always DENY access to anything which requires entitlements */
+    public static EntitlementManager minimal() {
+        return new EntitlementManager() {
+            @Override
+            public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> permission, T typeArgument) {
+                return false;
+            }
+            @Override
+            public String toString() {
+                return "Entitlements.minimal";
+            }
+        };
+    }
+
+    public static class FineGrainedEntitlements {
+    
+        private static final Joiner COMMA_JOINER = Joiner.on(',');
+
+        public static EntitlementManager anyOf(final EntitlementManager... 
checkers) {
+            return anyOf(Arrays.asList(checkers));
+        }
+        
+        public static EntitlementManager anyOf(final Iterable<? extends 
EntitlementManager> checkers) {
+            return new EntitlementManager() {
+                @Override
+                public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> permission, T typeArgument) {
+                    for (EntitlementManager checker: checkers)
+                        if (checker.isEntitled(context, permission, 
typeArgument))
+                            return true;
+                    return false;
+                }
+                @Override
+                public String toString() {
+                    return "Entitlements.anyOf(" + COMMA_JOINER.join(checkers) 
+ ")";
+                }
+            };
+        }
+        
+        public static EntitlementManager allOf(final EntitlementManager... 
checkers) {
+            return allOf(Arrays.asList(checkers));
+        }
+        
+        public static EntitlementManager allOf(final Iterable<? extends 
EntitlementManager> checkers) {
+            return new EntitlementManager() {
+                @Override
+                public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> permission, T typeArgument) {
+                    for (EntitlementManager checker: checkers)
+                        if (checker.isEntitled(context, permission, 
typeArgument))
+                            return true;
+                    return false;
+                }
+                @Override
+                public String toString() {
+                    return "Entitlements.allOf(" + COMMA_JOINER.join(checkers) 
+ ")";
+                }
+            };
+        }
+
+        public static <U> EntitlementManager allowing(EntitlementClass<U> 
permission, Predicate<U> test) {
+            return new SinglePermissionEntitlementChecker<U>(permission, test);
+        }
+
+        public static <U> EntitlementManager allowing(EntitlementClass<U> 
permission) {
+            return new SinglePermissionEntitlementChecker<U>(permission, 
Predicates.<U>alwaysTrue());
+        }
+
+        public static class SinglePermissionEntitlementChecker<U> implements 
EntitlementManager {
+            final EntitlementClass<U> permission;
+            final Predicate<U> test;
+            
+            protected SinglePermissionEntitlementChecker(EntitlementClass<U> 
permission, Predicate<U> test) {
+                this.permission = permission;
+                this.test = test;
+            }
+            
+            @SuppressWarnings("unchecked")
+            @Override
+            public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> permission, T typeArgument) {
+                if (!Objects.equal(this.permission, permission)) return false;
+                return test.apply((U)typeArgument);
+            }
+            @Override
+            public String toString() {
+                return "Entitlements.allowing(" + permission + " -> " + test + 
")";
+            }
+        }
+        public static EntitlementManager seeNonSecretSensors() {
+            return allowing(SEE_SENSOR, new Predicate<EntityAndItem<String>>() 
{
+                @Override
+                public boolean apply(EntityAndItem<String> input) {
+                    if (input == null) return false;
+                    return !Entities.isSecret(input.getItem());
+                }
+                @Override
+                public String toString() {
+                    return "Predicates.nonSecret";
+                }
+            });
+        }
+        
+    }
+    
+    /** allow read-only */
+    public static EntitlementManager readOnly() {
+        return FineGrainedEntitlements.anyOf(
+            FineGrainedEntitlements.allowing(SEE_ENTITY),
+            FineGrainedEntitlements.seeNonSecretSensors()
+        );
+    }
+
+    /** allow healthcheck */
+    public static EntitlementManager serverStatusOnly() {
+        return FineGrainedEntitlements.allowing(SERVER_STATUS);
+    }
+
+    // ------------- lookup conveniences -------------
+
+    private static class PerThreadEntitlementContextHolder {
+        public static final ThreadLocal<EntitlementContext> 
perThreadEntitlementsContextHolder = new ThreadLocal<EntitlementContext>();
+    }
+
+    /** 
+     * Finds the currently applicable {@link EntitlementContext} by examining 
the current thread
+     * then by investigating the current task, its submitter, etc. */
+    // NOTE: entitlements are propagated to tasks whenever they are created, 
as tags
+    // (see BrooklynTaskTags.tagForEntitlement and 
BasicExecutionContext.submitInternal).
+    // It might be cheaper to only do this lookup, not to propagate as tags, 
and to ensure
+    // all entitlement operations are wrapped in a task at source; but 
currently we do not
+    // do that so we need at least to set entitlement on the outermost task.
+    // Setting it on tasks submitted by a task is not strictly necessary (i.e. 
in BasicExecutionContext)
+    // but seems cheap enough, and means checking entitlements is fast, if we 
choose to do that more often.
+    public static EntitlementContext getEntitlementContext() {
+        EntitlementContext context;
+        context = 
PerThreadEntitlementContextHolder.perThreadEntitlementsContextHolder.get();
+        if (context!=null) return context;
+        
+        Task<?> task = Tasks.current();
+        while (task!=null) {
+            context = BrooklynTaskTags.getEntitlement(task);
+            if (context!=null) return context;
+            task = task.getSubmittedByTask();
+        }
+        
+        // no entitlements set -- assume entitlements not used, or system 
internal
+        return null;
+    }
+
+    public static void setEntitlementContext(EntitlementContext context) {
+        EntitlementContext oldContext = 
PerThreadEntitlementContextHolder.perThreadEntitlementsContextHolder.get();
+        if (oldContext!=null && context!=null) {
+            log.warn("Changing entitlement context from "+oldContext+" to 
"+context+"; context should have been reset or extended, not replaced");
+            log.debug("Trace for entitlement context duplicate overwrite", new 
Throwable("Trace for entitlement context overwrite"));
+        }
+        
PerThreadEntitlementContextHolder.perThreadEntitlementsContextHolder.set(context);
+    }
+    
+    public static void clearEntitlementContext() {
+        
PerThreadEntitlementContextHolder.perThreadEntitlementsContextHolder.set(null);
+    }
+    
+    public static <T> boolean isEntitled(EntitlementManager checker, 
EntitlementClass<T> permission, T typeArgument) {
+        return checker.isEntitled(getEntitlementContext(), permission, 
typeArgument);
+    }
+
+    /** throws {@link NotEntitledException} if entitlement not available for 
current {@link #getEntitlementContext()} */
+    public static <T> void checkEntitled(EntitlementManager checker, 
EntitlementClass<T> permission, T typeArgument) {
+        if (!isEntitled(checker, permission, typeArgument)) {
+            throw new NotEntitledException(getEntitlementContext(), 
permission, typeArgument);
+        }
+    }
+    /** throws {@link NotEntitledException} if entitlement not available for 
current {@link #getEntitlementContext()} 
+     * @since 0.7.0
+     * @deprecated since 0.7.0, use {@link #checkEntitled(EntitlementManager, 
EntitlementClass, Object)};
+     * kept briefly because there is some downstream usage*/
+    public static <T> void requireEntitled(EntitlementManager checker, 
EntitlementClass<T> permission, T typeArgument) {
+        checkEntitled(checker, permission, typeArgument);
+    }
+    
+    // ----------------- initialization ----------------
+
+    public final static String ENTITLEMENTS_CONFIG_PREFIX = 
"brooklyn.entitlements";
+    
+    public static ConfigKey<String> GLOBAL_ENTITLEMENT_MANAGER = 
ConfigKeys.newStringConfigKey(ENTITLEMENTS_CONFIG_PREFIX+".global", 
+        "Class for entitlements in effect globally; "
+        + "short names 'minimal', 'readonly', or 'root' are permitted here, 
with the default 'root' giving full access to all declared users; "
+        + "or supply the name of an "+EntitlementManager.class+" class to 
instantiate, taking a 1-arg BrooklynProperties constructor or a 0-arg 
constructor",
+        "root");
+    
+    public static EntitlementManager newManager(ManagementContext mgmt, 
BrooklynProperties brooklynProperties) {
+        return newGlobalManager(mgmt, brooklynProperties);
+    }
+    private static EntitlementManager newGlobalManager(ManagementContext mgmt, 
BrooklynProperties brooklynProperties) {
+        return load(mgmt, brooklynProperties, 
brooklynProperties.getConfig(GLOBAL_ENTITLEMENT_MANAGER));
+    }
+    
+    public static EntitlementManager load(@Nullable ManagementContext mgmt, 
BrooklynProperties brooklynProperties, String type) {
+        if ("root".equalsIgnoreCase(type)) return root();
+        if ("readonly".equalsIgnoreCase(type) || 
"read_only".equalsIgnoreCase(type)) return readOnly();
+        if ("minimal".equalsIgnoreCase(type)) return minimal();
+        if (Strings.isNonBlank(type)) {
+            try {
+                ClassLoader cl = mgmt==null ? null : 
((ManagementContextInternal)mgmt).getCatalogClassLoader();
+                if (cl==null) cl = Entitlements.class.getClassLoader();
+                Class<?> clazz = cl.loadClass(type);
+                return (EntitlementManager) instantiate(clazz, 
ImmutableList.of(
+                        new Object[] {mgmt, brooklynProperties},
+                        new Object[] {mgmt},
+                        new Object[] {brooklynProperties},
+                        new Object[0]));
+            } catch (Exception e) { 
+                throw Exceptions.propagate(e); 
+            }
+        }
+        throw new IllegalStateException("Invalid entitlement manager 
specified: '"+type+"'");
+    }
+    
+    private static Object instantiate(Class<?> clazz, List<Object[]> 
constructorArgOptions) {
+        try {
+            for (Object[] constructorArgOption : constructorArgOptions) {
+                Optional<?> result = 
Reflections.invokeConstructorWithArgs(clazz, constructorArgOption);
+                if (result.isPresent()) return result.get();
+            }
+        } catch (Exception e) { 
+            throw Exceptions.propagate(e); 
+        }
+        throw new IllegalStateException("No matching constructor to 
instantiate "+clazz);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/NotEntitledException.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/NotEntitledException.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/NotEntitledException.java
new file mode 100644
index 0000000..aab7c0d
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/NotEntitledException.java
@@ -0,0 +1,44 @@
+/*
+ * 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.management.entitlement;
+
+import org.apache.brooklyn.api.management.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.management.entitlement.EntitlementContext;
+
+
+public class NotEntitledException extends RuntimeException {
+
+    private static final long serialVersionUID = -4001882260980589181L;
+    
+    EntitlementContext entitlementContext;
+    EntitlementClass<?> permission;
+    Object typeArgument;
+    
+    public <T> NotEntitledException(EntitlementContext entitlementContext, 
EntitlementClass<T> permission, T typeArgument) {
+        this.entitlementContext = entitlementContext;
+        this.permission = permission;
+        this.typeArgument = typeArgument;
+    }
+    
+    @Override
+    public String toString() {
+        return 
super.toString()+"["+entitlementContext+":"+permission+(typeArgument!=null ? 
"("+typeArgument+")" : "")+"]";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManager.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManager.java
new file mode 100644
index 0000000..c5914e8
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManager.java
@@ -0,0 +1,100 @@
+/*
+ * 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.management.entitlement;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.management.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.management.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.management.entitlement.EntitlementManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigPredicates;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Preconditions;
+
+public class PerUserEntitlementManager implements EntitlementManager {
+
+    private static final Logger log = 
LoggerFactory.getLogger(PerUserEntitlementManager.class);
+    
+    public final static String PER_USER_ENTITLEMENTS_CONFIG_PREFIX = 
Entitlements.ENTITLEMENTS_CONFIG_PREFIX+".perUser";
+    
+    public final static ConfigKey<String> DEFAULT_MANAGER = 
ConfigKeys.newStringConfigKey(PER_USER_ENTITLEMENTS_CONFIG_PREFIX+
+        ".default", "Default entitlements manager for users without further 
specification", "minimal");
+    
+    protected final EntitlementManager defaultManager;
+    protected final Map<String,EntitlementManager> perUserManagers = 
MutableMap.of();
+
+    private final static ThreadLocal<Boolean> ACTIVE = new 
ThreadLocal<Boolean>();
+    
+    private static EntitlementManager load(BrooklynProperties properties, 
String type) {
+        if (Boolean.TRUE.equals(ACTIVE.get())) {
+            // prevent infinite loop
+            throw new IllegalStateException("Cannot set 
"+PerUserEntitlementManager.class.getName()+" within config for itself");
+        }
+        try {
+            ACTIVE.set(true);
+            return Entitlements.load(null, properties, type);
+        } finally {
+            ACTIVE.remove();
+        }
+    }
+    
+    public PerUserEntitlementManager(BrooklynProperties properties) {
+        this(load(properties, properties.getConfig(DEFAULT_MANAGER)));
+        
+        BrooklynProperties users = 
properties.submap(ConfigPredicates.startingWith(PER_USER_ENTITLEMENTS_CONFIG_PREFIX+"."));
+        for (Map.Entry<ConfigKey<?>,?> key: users.getAllConfig().entrySet()) {
+            if (key.getKey().getName().equals(DEFAULT_MANAGER.getName())) 
continue;
+            String user = Strings.removeFromStart(key.getKey().getName(), 
PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".");
+            addUser(user, load(properties, Strings.toString(key.getValue())));
+        }
+        
+        log.info(getClass().getSimpleName()+" created with 
"+perUserManagers.size()+" user"+Strings.s(perUserManagers)+" and "
+            + "default "+defaultManager+" (users: "+perUserManagers+")");
+    }
+    
+    public PerUserEntitlementManager(EntitlementManager defaultManager) {
+        this.defaultManager = Preconditions.checkNotNull(defaultManager);
+    }
+
+    public void addUser(String user, EntitlementManager managerForThisUser) {
+        perUserManagers.put(Preconditions.checkNotNull(user, "user"), 
Preconditions.checkNotNull(managerForThisUser, "managerForThisUser"));
+    }
+
+    @Override
+    public <T> boolean isEntitled(EntitlementContext context, 
EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+        EntitlementManager entitlementInEffect = null;
+        if (context==null || context.user()==null) {
+            // no user means it is running as an internal process, always has 
root
+            entitlementInEffect = Entitlements.root(); 
+        } else {
+            if (context!=null) entitlementInEffect = 
perUserManagers.get(context.user());
+            if (entitlementInEffect==null) entitlementInEffect = 
defaultManager;
+        }
+        return entitlementInEffect.isEntitled(context, entitlementClass, 
entitlementClassArgument);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManagerWithDefault.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManagerWithDefault.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManagerWithDefault.java
new file mode 100644
index 0000000..b4c7e38
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManagerWithDefault.java
@@ -0,0 +1,31 @@
+/*
+ * 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.management.entitlement;
+
+import org.apache.brooklyn.api.management.entitlement.EntitlementManager;
+
+@Deprecated
+/** @deprecated since 0.7.0 use {@link PerUserEntitlementManager} */
+public class PerUserEntitlementManagerWithDefault extends 
PerUserEntitlementManager {
+
+    public PerUserEntitlementManagerWithDefault(EntitlementManager 
defaultManager) {
+        super(defaultManager);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/WebEntitlementContext.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/WebEntitlementContext.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/WebEntitlementContext.java
new file mode 100644
index 0000000..2efed42
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/WebEntitlementContext.java
@@ -0,0 +1,57 @@
+/*
+ * 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.management.entitlement;
+
+import org.apache.brooklyn.api.management.entitlement.EntitlementContext;
+
+import brooklyn.util.javalang.JavaClassNames;
+
+/**
+ * Indicates an authenticated web request as the entitlements context;
+ * note user may still be null if no authentication was requested
+ */
+public class WebEntitlementContext implements EntitlementContext {
+
+    final String user;
+    final String sourceIp;
+    final String requestUri;
+    
+    /**
+     * A mostly-unique identifier for the inbound request, to distinguish
+     * between duplicate requests and for cross-referencing with URIs
+     */
+    final String requestUniqueIdentifier;
+    
+    public WebEntitlementContext(String user, String sourceIp, String 
requestUri, String requestUniqueIdentifier) {
+        this.user = user;
+        this.sourceIp = sourceIp;
+        this.requestUri = requestUri;
+        this.requestUniqueIdentifier = requestUniqueIdentifier;
+    }
+    
+    @Override public String user() { return user; }
+    public String sourceIp() { return sourceIp; }
+    public String requestUri() { return requestUri; }
+    public String requestUniqueIdentifier() { return requestUniqueIdentifier; }
+
+    @Override
+    public String toString() {
+        return 
JavaClassNames.simpleClassName(getClass())+"["+user+"@"+sourceIp+":"+requestUniqueIdentifier+"]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/ha/BasicMasterChooser.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/ha/BasicMasterChooser.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/ha/BasicMasterChooser.java
new file mode 100644
index 0000000..034a6bd
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/ha/BasicMasterChooser.java
@@ -0,0 +1,204 @@
+/*
+ * 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.management.ha;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.trait.Identifiable;
+import org.apache.brooklyn.api.management.ha.ManagementNodeState;
+import org.apache.brooklyn.api.management.ha.ManagementNodeSyncRecord;
+import org.apache.brooklyn.api.management.ha.ManagementPlaneSyncRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.text.NaturalOrderComparator;
+import brooklyn.util.time.Duration;
+
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Ordering;
+
+/**
+ * @since 0.7.0
+ * 
+ * @author aled
+ */
+@Beta
+public abstract class BasicMasterChooser implements MasterChooser {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(BasicMasterChooser.class);
+
+    protected static class ScoredRecord<T extends Comparable<T>> implements 
Identifiable, Comparable<ScoredRecord<T>> {
+        String id;
+        ManagementNodeSyncRecord record;
+        T score;
+        
+        @Override
+        public String getId() {
+            return id;
+        }
+
+        @Override
+        public int compareTo(ScoredRecord<T> o) {
+            return score.compareTo(o.score);
+        }
+    }
+    
+    public ManagementNodeSyncRecord choose(ManagementPlaneSyncRecord memento, 
Duration heartbeatTimeout, String ownNodeId) {
+        if (LOG.isDebugEnabled()) LOG.debug("Choosing new master from 
"+memento.getManagementNodes());
+        ManagementNodeSyncRecord me = 
memento.getManagementNodes().get(ownNodeId);
+        if (me==null) {
+            LOG.warn("Management node details not known when choosing new 
master: "+memento+" / "+ownNodeId);
+            return null;
+        }
+        Long nowIsh = me.getRemoteTimestamp();
+        if (nowIsh==null) {
+            LOG.warn("Management node for self missing timestamp when choosing 
new master: "+memento);
+            return null;
+        }
+        
+        List<ScoredRecord<?>> contenders = filterHealthy(memento, 
heartbeatTimeout, nowIsh);
+        
+        if (!contenders.isEmpty()) {
+            return pick(contenders);
+        } else {
+            LOG.info("No valid management node found for choosing new master: 
contender="+memento.getManagementNodes());
+            return null;
+        }        
+    }
+
+    /** pick the best contender; argument guaranteed to be non-null and 
non-empty,
+     * filtered for health reasons */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected ManagementNodeSyncRecord pick(List<ScoredRecord<?>> contenders) {
+        ScoredRecord min = null;
+        for (ScoredRecord x: contenders) {
+            if (min==null || x.score.compareTo(min.score)<0) min = x;
+        }
+        return min.record;
+    }
+
+    public static class AlphabeticChooserScore implements 
Comparable<AlphabeticChooserScore> {
+        long priority;
+        int versionBias;
+        String brooklynVersion;
+        int statePriority;
+        String nodeId;
+        
+        @Override
+        public int compareTo(AlphabeticChooserScore o) {
+            if (o==null) return -1;
+            return ComparisonChain.start()
+                // invert the order where we prefer higher values
+                .compare(o.priority, this.priority)
+                .compare(o.versionBias, this.versionBias)
+                .compare(o.brooklynVersion, this.brooklynVersion, 
+                    
Ordering.from(NaturalOrderComparator.INSTANCE).nullsFirst())
+                .compare(o.statePriority, this.statePriority)
+                .compare(this.nodeId, o.nodeId, 
Ordering.usingToString().nullsLast())
+                .result();
+        }
+    }
+    
+    /** comparator which prefers, in order:
+     * <li> higher explicit priority
+     * <li> non-snapshot Brooklyn version, then any Brooklyn version, and 
lastly null version 
+     *      (using {@link NaturalOrderComparator} so e.g. v10 > v3.20 > v3.9 )
+     * <li> higher version (null last)
+     * <li> node which reports it's master, hot standby, then standby 
+     * <li> finally (common case): lower (alphabetically) node id
+     */
+    public static class AlphabeticMasterChooser extends BasicMasterChooser {
+        final boolean preferHotStandby;
+        public AlphabeticMasterChooser(boolean preferHotStandby) { 
this.preferHotStandby = preferHotStandby; }
+        public AlphabeticMasterChooser() { this.preferHotStandby = true; }
+        @Override
+        protected AlphabeticChooserScore score(ManagementNodeSyncRecord 
contender) {
+            AlphabeticChooserScore score = new AlphabeticChooserScore();
+            score.priority = contender.getPriority()!=null ? 
contender.getPriority() : 0;
+            score.brooklynVersion = contender.getBrooklynVersion();
+            score.versionBias = contender.getBrooklynVersion()==null ? -2 :
+                
contender.getBrooklynVersion().toLowerCase().indexOf("snapshot")>=0 ? -1 :
+                0;
+            if (preferHotStandby) {
+                // other master should be preferred before we get invoked, but 
including for good measure
+                score.statePriority = 
contender.getStatus()==ManagementNodeState.MASTER ? 3 :
+                    contender.getStatus()==ManagementNodeState.HOT_STANDBY ? 2 
:
+                        contender.getStatus()==ManagementNodeState.STANDBY ? 1 
: -1;
+            } else {
+                score.statePriority = 0;
+            }
+            score.nodeId = contender.getNodeId();
+            return score;
+        }
+    }
+    
+    /**
+     * Filters the {@link ManagementPlaneSyncRecord#getManagementNodes()} to 
only those in an appropriate state, 
+     * and with heartbeats that have not timed out.
+     */
+    protected List<ScoredRecord<?>> filterHealthy(ManagementPlaneSyncRecord 
memento, Duration heartbeatTimeout, long nowIsh) {
+        long oldestAcceptableTimestamp = nowIsh - 
heartbeatTimeout.toMilliseconds();
+        List<ScoredRecord<?>> contenders = MutableList.of();
+        for (ManagementNodeSyncRecord contender : 
memento.getManagementNodes().values()) {
+            boolean statusOk = (contender.getStatus() == 
ManagementNodeState.STANDBY || contender.getStatus() == 
ManagementNodeState.HOT_STANDBY || contender.getStatus() == 
ManagementNodeState.MASTER);
+            Long remoteTimestamp = contender.getRemoteTimestamp();
+            boolean heartbeatOk;
+            if (remoteTimestamp==null) {
+                throw new IllegalStateException("Missing remote timestamp when 
performing master election: "+this+" / "+contender);
+                // if the above exception happens in some contexts we could 
either fallback to local, or fail:
+//                remoteTimestamp = contender.getLocalTimestamp();
+                // or
+//                heartbeatOk=false;
+            } else {
+                heartbeatOk = remoteTimestamp >= oldestAcceptableTimestamp;
+            }
+            if (statusOk && heartbeatOk) {
+                contenders.add(newScoredRecord(contender));
+            }
+            if (LOG.isTraceEnabled()) LOG.trace("Filtering choices of new 
master: contender="+contender+"; statusOk="+statusOk+"; 
heartbeatOk="+heartbeatOk);
+        }
+        return contenders;
+    }
+
+    @VisibleForTesting
+    //Java 6 compiler workaround, using parameterized types fails
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected List<ScoredRecord<?>> sort(List<ScoredRecord<?>> input) {
+        ArrayList copy = new ArrayList<ScoredRecord<?>>(input);
+        Collections.sort(copy);
+        return copy;
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected ScoredRecord<?> newScoredRecord(ManagementNodeSyncRecord 
contender) {
+        ScoredRecord r = new ScoredRecord();
+        r.id = contender.getNodeId();
+        r.record = contender;
+        r.score = score(contender);
+        return r;
+    }
+
+    protected abstract Comparable<?> score(ManagementNodeSyncRecord contender);
+    
+}

Reply via email to