http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java new file mode 100644 index 0000000..2ba84d9 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java @@ -0,0 +1,116 @@ +/* + * 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.rest.transform; + +import static brooklyn.rest.domain.Status.ACCEPTED; +import static brooklyn.rest.domain.Status.RUNNING; +import static brooklyn.rest.domain.Status.STARTING; +import static brooklyn.rest.domain.Status.STOPPED; +import static brooklyn.rest.domain.Status.STOPPING; +import static brooklyn.rest.domain.Status.UNKNOWN; +import static brooklyn.rest.domain.Status.DESTROYED; +import static brooklyn.rest.domain.Status.ERROR; + +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import javax.annotation.Nullable; + +import brooklyn.entity.Application; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.trait.Startable; +import brooklyn.location.Location; +import brooklyn.rest.domain.ApplicationSpec; +import brooklyn.rest.domain.ApplicationSummary; +import brooklyn.rest.domain.Status; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableMap; + +public class ApplicationTransformer { + + public static final Function<? super Application, ApplicationSummary> FROM_APPLICATION = new Function<Application, ApplicationSummary>() { + @Override + public ApplicationSummary apply(Application input) { + return summaryFromApplication(input); + } + }; + + public static Status statusFromApplication(Application application) { + if (application == null) return UNKNOWN; + Lifecycle state = application.getAttribute(Attributes.SERVICE_STATE_ACTUAL); + if (state != null) return statusFromLifecycle(state); + Boolean up = application.getAttribute(Startable.SERVICE_UP); + if (up != null && up.booleanValue()) return RUNNING; + return UNKNOWN; + } + + + public static Status statusFromLifecycle(Lifecycle state) { + if (state == null) return UNKNOWN; + switch (state) { + case CREATED: + return ACCEPTED; + case STARTING: + return STARTING; + case RUNNING: + return RUNNING; + case STOPPING: + return STOPPING; + case STOPPED: + return STOPPED; + case DESTROYED: + return DESTROYED; + case ON_FIRE: + return ERROR; + default: + return UNKNOWN; + } + } + + public static ApplicationSpec specFromApplication(Application application) { + Collection<String> locations = Collections2.transform(application.getLocations(), new Function<Location, String>() { + @Override + @Nullable + public String apply(@Nullable Location input) { + return input.getId(); + } + }); + // okay to have entities and config as null, as this comes from a real instance + return new ApplicationSpec(application.getDisplayName(), application.getEntityType().getName(), + null, locations, null); + } + + public static ApplicationSummary summaryFromApplication(Application application) { + Map<String, URI> links; + if (application.getId() == null) { + links = Collections.emptyMap(); + } else { + links = ImmutableMap.of( + "self", URI.create("/v1/applications/" + application.getId()), + "entities", URI.create("/v1/applications/" + application.getId() + "/entities")); + } + + return new ApplicationSummary(application.getId(), specFromApplication(application), statusFromApplication(application), links); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java new file mode 100644 index 0000000..37d1643 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java @@ -0,0 +1,45 @@ +/* + * 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.rest.transform; + +import com.google.common.base.Function; + +import brooklyn.BrooklynVersion.BrooklynFeature; +import brooklyn.rest.domain.BrooklynFeatureSummary; + +public class BrooklynFeatureTransformer { + + public static final Function<BrooklynFeature, BrooklynFeatureSummary> FROM_FEATURE = new Function<BrooklynFeature, BrooklynFeatureSummary>() { + @Override + public BrooklynFeatureSummary apply(BrooklynFeature feature) { + return featureSummary(feature); + } + }; + + public static BrooklynFeatureSummary featureSummary(BrooklynFeature feature) { + return new BrooklynFeatureSummary( + feature.getName(), + feature.getSymbolicName(), + feature.getVersion(), + feature.getLastModified(), + feature.getAdditionalData()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java new file mode 100644 index 0000000..8620142 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java @@ -0,0 +1,163 @@ +/* + * 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.rest.transform; + +import java.net.URI; +import java.util.Map; +import java.util.Set; + +import org.slf4j.LoggerFactory; + +import brooklyn.basic.BrooklynTypes; +import org.apache.brooklyn.catalog.CatalogItem; +import org.apache.brooklyn.catalog.CatalogItem.CatalogItemType; +import brooklyn.config.ConfigKey; +import brooklyn.entity.Effector; +import brooklyn.entity.Entity; +import brooklyn.entity.EntityType; +import brooklyn.entity.basic.EntityDynamicType; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.event.Sensor; +import brooklyn.location.Location; +import brooklyn.location.LocationSpec; +import brooklyn.policy.Policy; +import brooklyn.policy.PolicySpec; +import brooklyn.rest.domain.CatalogEntitySummary; +import brooklyn.rest.domain.CatalogItemSummary; +import brooklyn.rest.domain.CatalogLocationSummary; +import brooklyn.rest.domain.CatalogPolicySummary; +import brooklyn.rest.domain.EffectorSummary; +import brooklyn.rest.domain.EntityConfigSummary; +import brooklyn.rest.domain.LocationConfigSummary; +import brooklyn.rest.domain.PolicyConfigSummary; +import brooklyn.rest.domain.SensorSummary; +import brooklyn.rest.domain.SummaryComparators; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +public class CatalogTransformer { + + private static final org.slf4j.Logger log = LoggerFactory.getLogger(CatalogTransformer.class); + + public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, CatalogItem<? extends Entity,EntitySpec<?>> item) { + Set<EntityConfigSummary> config = Sets.newTreeSet(SummaryComparators.nameComparator()); + Set<SensorSummary> sensors = Sets.newTreeSet(SummaryComparators.nameComparator()); + Set<EffectorSummary> effectors = Sets.newTreeSet(SummaryComparators.nameComparator()); + + try { + EntitySpec<?> spec = b.getCatalog().createSpec(item); + EntityDynamicType typeMap = BrooklynTypes.getDefinedEntityType(spec.getType()); + EntityType type = typeMap.getSnapshot(); + + for (ConfigKey<?> x: type.getConfigKeys()) + config.add(EntityTransformer.entityConfigSummary(x, typeMap.getConfigKeyField(x.getName()))); + for (Sensor<?> x: type.getSensors()) + sensors.add(SensorTransformer.sensorSummaryForCatalog(x)); + for (Effector<?> x: type.getEffectors()) + effectors.add(EffectorTransformer.effectorSummaryForCatalog(x)); + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + + // templates with multiple entities can't have spec created in the manner above; just ignore + if (item.getCatalogItemType()==CatalogItemType.ENTITY) { + log.warn("Unable to create spec for "+item+": "+e, e); + } + if (log.isTraceEnabled()) { + log.trace("Unable to create spec for "+item+": "+e, e); + } + } + + return new CatalogEntitySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getJavaType(), item.getPlanYaml(), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), + config, sensors, effectors, + item.isDeprecated(), makeLinks(item)); + } + + @SuppressWarnings("unchecked") + public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, CatalogItem<?,?> item) { + try { + switch (item.getCatalogItemType()) { + case TEMPLATE: + case ENTITY: + return catalogEntitySummary(b, (CatalogItem<? extends Entity, EntitySpec<?>>) item); + case POLICY: + return catalogPolicySummary(b, (CatalogItem<? extends Policy, PolicySpec<?>>) item); + case LOCATION: + return catalogLocationSummary(b, (CatalogItem<? extends Location, LocationSpec<?>>) item); + default: + log.warn("Unexpected catalog item type when getting self link (supplying generic item): "+item.getCatalogItemType()+" "+item); + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Invalid item in catalog when converting REST summaries (supplying generic item), at "+item+": "+e, e); + } + return new CatalogItemSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getJavaType(), item.getPlanYaml(), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), item.isDeprecated(), makeLinks(item)); + } + + public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, CatalogItem<? extends Policy,PolicySpec<?>> item) { + Set<PolicyConfigSummary> config = ImmutableSet.of(); + return new CatalogPolicySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getJavaType(), item.getPlanYaml(), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), config, + item.isDeprecated(), makeLinks(item)); + } + + public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, CatalogItem<? extends Location,LocationSpec<?>> item) { + Set<LocationConfigSummary> config = ImmutableSet.of(); + return new CatalogLocationSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getJavaType(), item.getPlanYaml(), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), config, + item.isDeprecated(), makeLinks(item)); + } + + protected static Map<String, URI> makeLinks(CatalogItem<?,?> item) { + return MutableMap.<String, URI>of().addIfNotNull("self", URI.create(getSelfLink(item))); + } + + protected static String getSelfLink(CatalogItem<?,?> item) { + String itemId = item.getId(); + switch (item.getCatalogItemType()) { + case TEMPLATE: + return "/v1/applications/" + itemId + "/" + item.getVersion(); + case ENTITY: + return "/v1/entities/" + itemId + "/" + item.getVersion(); + case POLICY: + return "/v1/policies/" + itemId + "/" + item.getVersion(); + case LOCATION: + return "/v1/locations/" + itemId + "/" + item.getVersion(); + default: + log.warn("Unexpected catalog item type when getting self link (not supplying self link): "+item.getCatalogItemType()+" "+item); + return null; + } + } + private static String tidyIconLink(BrooklynRestResourceUtils b, CatalogItem<?,?> item, String iconUrl) { + if (b.isUrlServerSideAndSafe(iconUrl)) + return "/v1/catalog/icon/"+item.getSymbolicName() + "/" + item.getVersion(); + return iconUrl; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java new file mode 100644 index 0000000..3489942 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java @@ -0,0 +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.rest.transform; + +import java.net.URI; +import java.util.Set; + +import javax.annotation.Nullable; + +import brooklyn.entity.Effector; +import brooklyn.entity.Entity; +import brooklyn.entity.ParameterType; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.rest.domain.EffectorSummary; +import brooklyn.rest.domain.EffectorSummary.ParameterSummary; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.guava.Maybe; +import brooklyn.util.task.Tasks; +import brooklyn.util.task.ValueResolver; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class EffectorTransformer { + + public static EffectorSummary effectorSummary(final EntityLocal entity, Effector<?> effector) { + String applicationUri = "/v1/applications/" + entity.getApplicationId(); + String entityUri = applicationUri + "/entities/" + entity.getId(); + return new EffectorSummary(effector.getName(), effector.getReturnTypeName(), + ImmutableSet.copyOf(Iterables.transform(effector.getParameters(), + new Function<ParameterType<?>, EffectorSummary.ParameterSummary<?>>() { + @Override + public EffectorSummary.ParameterSummary<?> apply(@Nullable ParameterType<?> parameterType) { + return parameterSummary(entity, parameterType); + } + })), effector.getDescription(), ImmutableMap.of( + "self", URI.create(entityUri + "/effectors/" + effector.getName()), + "entity", URI.create(entityUri), + "application", URI.create(applicationUri) + )); + } + + public static EffectorSummary effectorSummaryForCatalog(Effector<?> effector) { + Set<EffectorSummary.ParameterSummary<?>> parameters = ImmutableSet.copyOf(Iterables.transform(effector.getParameters(), + new Function<ParameterType<?>, EffectorSummary.ParameterSummary<?>>() { + @Override + public EffectorSummary.ParameterSummary<?> apply(ParameterType<?> parameterType) { + return parameterSummary(null, parameterType); + } + })); + return new EffectorSummary(effector.getName(), + effector.getReturnTypeName(), parameters, effector.getDescription(), null); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected static EffectorSummary.ParameterSummary<?> parameterSummary(Entity entity, ParameterType<?> parameterType) { + try { + Maybe<?> defaultValue = Tasks.resolving(parameterType.getDefaultValue()).as(parameterType.getParameterClass()) + .context(entity).timeout(ValueResolver.REAL_QUICK_WAIT).getMaybe(); + return new ParameterSummary(parameterType.getName(), parameterType.getParameterClassName(), + parameterType.getDescription(), + WebResourceUtils.getValueForDisplay(defaultValue.orNull(), true, false)); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java new file mode 100644 index 0000000..3ba7cd8 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java @@ -0,0 +1,155 @@ +/* + * 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.rest.transform; + +import static com.google.common.collect.Iterables.transform; + +import java.lang.reflect.Field; +import java.net.URI; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.catalog.CatalogConfig; +import brooklyn.config.ConfigKey; +import brooklyn.config.render.RendererHints; +import brooklyn.entity.Application; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.rest.domain.EntityConfigSummary; +import brooklyn.rest.domain.EntitySummary; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.URLParamEncoder; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +/** + * @author Adam Lowe + */ +public class EntityTransformer { + + public static final Function<? super Entity, EntitySummary> FROM_ENTITY = new Function<Entity, EntitySummary>() { + @Override + public EntitySummary apply(Entity entity) { + return EntityTransformer.entitySummary(entity); + } + }; + + public static EntitySummary entitySummary(Entity entity) { + String applicationUri = "/v1/applications/" + entity.getApplicationId(); + String entityUri = applicationUri + "/entities/" + entity.getId(); + ImmutableMap.Builder<String, URI> lb = ImmutableMap.<String, URI>builder() + .put("self", URI.create(entityUri)); + if (entity.getParent()!=null) + lb.put("parent", URI.create(applicationUri+"/entities/"+entity.getParent().getId())); + String type = entity.getEntityType().getName(); + lb.put("application", URI.create(applicationUri)) + .put("children", URI.create(entityUri + "/children")) + .put("config", URI.create(entityUri + "/config")) + .put("sensors", URI.create(entityUri + "/sensors")) + .put("effectors", URI.create(entityUri + "/effectors")) + .put("policies", URI.create(entityUri + "/policies")) + .put("activities", URI.create(entityUri + "/activities")) + .put("locations", URI.create(entityUri + "/locations")) + .put("tags", URI.create(entityUri + "/tags")) + .put("expunge", URI.create(entityUri + "/expunge")) + .put("rename", URI.create(entityUri + "/name")) + .put("spec", URI.create(entityUri + "/spec")) + ; + + if (entity.getCatalogItemId() != null) { + lb.put("catalog", URI.create("/v1/catalog/entities/" + WebResourceUtils.getPathFromVersionedId(entity.getCatalogItemId()))); + } + + if (entity.getIconUrl()!=null) + lb.put("iconUrl", URI.create(entityUri + "/icon")); + + return new EntitySummary(entity.getId(), entity.getDisplayName(), type, entity.getCatalogItemId(), lb.build()); + } + + public static List<EntitySummary> entitySummaries(Iterable<? extends Entity> entities) { + return Lists.newArrayList(transform( + entities, + new Function<Entity, EntitySummary>() { + @Override + public EntitySummary apply(Entity entity) { + return EntityTransformer.entitySummary(entity); + } + })); + } + + protected static EntityConfigSummary entityConfigSummary(ConfigKey<?> config, String label, Double priority, Map<String, URI> links) { + Map<String, URI> mapOfLinks = links==null ? null : ImmutableMap.copyOf(links); + return new EntityConfigSummary(config, label, priority, mapOfLinks); + } + /** generates a representation for a given config key, + * with label inferred from annoation in the entity class, + * and links pointing to the entity and the applicaiton */ + public static EntityConfigSummary entityConfigSummary(EntityLocal entity, ConfigKey<?> config) { + /* + * following code nearly there to get the @CatalogConfig annotation + * in the class and use that to populate a label + */ + +// EntityDynamicType typeMap = +// ((AbstractEntity)entity).getMutableEntityType(); +// // above line works if we can cast; line below won't work, but there should some way +// // to get back the handle to the spec from an entity local, which then *would* work +// EntityTypes.getDefinedEntityType(entity.getClass()); + +// String label = typeMap.getConfigKeyField(config.getName()); + String label = null; + Double priority = null; + + String applicationUri = "/v1/applications/" + entity.getApplicationId(); + String entityUri = applicationUri + "/entities/" + entity.getId(); + String selfUri = entityUri + "/config/" + URLParamEncoder.encode(config.getName()); + + MutableMap.Builder<String, URI> lb = MutableMap.<String, URI>builder() + .put("self", URI.create(selfUri)) + .put("application", URI.create(applicationUri)) + .put("entity", URI.create(entityUri)) + .put("action:json", URI.create(selfUri)); + + Iterable<RendererHints.NamedAction> hints = Iterables.filter(RendererHints.getHintsFor(config), RendererHints.NamedAction.class); + for (RendererHints.NamedAction na : hints) { + SensorTransformer.addNamedAction(lb, na, entity.getConfig(config), config, entity); + } + + return entityConfigSummary(config, label, priority, lb.build()); + } + + public static String applicationUri(Application entity) { + return "/v1/applications/" + entity.getApplicationId(); + } + + public static String entityUri(Entity entity) { + return applicationUri(entity.getApplication()) + "/entities/" + entity.getId(); + } + + public static EntityConfigSummary entityConfigSummary(ConfigKey<?> config, Field configKeyField) { + CatalogConfig catalogConfig = configKeyField!=null ? configKeyField.getAnnotation(CatalogConfig.class) : null; + String label = catalogConfig==null ? null : catalogConfig.label(); + Double priority = catalogConfig==null ? null : catalogConfig.priority(); + return entityConfigSummary(config, label, priority, null); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java new file mode 100644 index 0000000..61d0f49 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java @@ -0,0 +1,50 @@ +/* + * 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.rest.transform; + +import java.net.URI; +import java.util.Map; + +import brooklyn.management.ha.ManagementPlaneSyncRecord; +import brooklyn.management.ha.ManagementNodeSyncRecord; +import brooklyn.rest.domain.HighAvailabilitySummary; +import brooklyn.rest.domain.HighAvailabilitySummary.HaNodeSummary; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +public class HighAvailabilityTransformer { + + public static HighAvailabilitySummary highAvailabilitySummary(String ownNodeId, ManagementPlaneSyncRecord memento) { + Map<String, HaNodeSummary> nodes = Maps.newLinkedHashMap(); + for (Map.Entry<String, ManagementNodeSyncRecord> entry : memento.getManagementNodes().entrySet()) { + nodes.put(entry.getKey(), haNodeSummary(entry.getValue())); + } + + // TODO What links? + ImmutableMap.Builder<String, URI> lb = ImmutableMap.<String, URI>builder(); + + return new HighAvailabilitySummary(ownNodeId, memento.getMasterNodeId(), nodes, lb.build()); + } + + public static HaNodeSummary haNodeSummary(ManagementNodeSyncRecord memento) { + String status = memento.getStatus() == null ? null : memento.getStatus().toString(); + return new HaNodeSummary(memento.getNodeId(), memento.getUri(), status, memento.getLocalTimestamp(), memento.getRemoteTimestamp()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java new file mode 100644 index 0000000..240845e --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java @@ -0,0 +1,194 @@ +/* + * 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.rest.transform; + +import java.net.URI; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.Sanitizer; +import brooklyn.location.Location; +import brooklyn.location.LocationDefinition; +import brooklyn.location.basic.BasicLocationDefinition; +import brooklyn.location.basic.LocationConfigKeys; +import brooklyn.location.basic.LocationInternal; +import brooklyn.management.ManagementContext; +import brooklyn.rest.domain.LocationSummary; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.guava.Maybe; +import brooklyn.util.text.Strings; + +import com.google.common.collect.ImmutableMap; + +public class LocationTransformer { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(LocationTransformer.LocationDetailLevel.class); + + public static enum LocationDetailLevel { NONE, LOCAL_EXCLUDING_SECRET, FULL_EXCLUDING_SECRET, FULL_INCLUDING_SECRET } + + /** @deprecated since 0.7.0 use method taking management context and detail specifier */ + @Deprecated + public static LocationSummary newInstance(String id, brooklyn.rest.domain.LocationSpec locationSpec) { + return newInstance(null, id, locationSpec, LocationDetailLevel.LOCAL_EXCLUDING_SECRET); + } + @SuppressWarnings("deprecation") + public static LocationSummary newInstance(ManagementContext mgmt, String id, brooklyn.rest.domain.LocationSpec locationSpec, LocationDetailLevel level) { + // TODO: Remove null checks on mgmt when newInstance(String, LocationSpec) is deleted + Map<String, ?> config = locationSpec.getConfig(); + if (mgmt != null && (level==LocationDetailLevel.FULL_EXCLUDING_SECRET || level==LocationDetailLevel.FULL_INCLUDING_SECRET)) { + LocationDefinition ld = new BasicLocationDefinition(id, locationSpec.getName(), locationSpec.getSpec(), locationSpec.getConfig()); + Location ll = mgmt.getLocationRegistry().resolve(ld, false, null).orNull(); + if (ll!=null) config = ((LocationInternal)ll).config().getBag().getAllConfig(); + } else if (level==LocationDetailLevel.LOCAL_EXCLUDING_SECRET) { + // get displayName + if (!config.containsKey(LocationConfigKeys.DISPLAY_NAME.getName()) && mgmt!=null) { + LocationDefinition ld = new BasicLocationDefinition(id, locationSpec.getName(), locationSpec.getSpec(), locationSpec.getConfig()); + Location ll = mgmt.getLocationRegistry().resolve(ld, false, null).orNull(); + if (ll!=null) { + Map<String, Object> configExtra = ((LocationInternal)ll).config().getBag().getAllConfig(); + if (configExtra.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) { + ConfigBag configNew = ConfigBag.newInstance(config); + configNew.configure(LocationConfigKeys.DISPLAY_NAME, (String)configExtra.get(LocationConfigKeys.DISPLAY_NAME.getName())); + config = configNew.getAllConfig(); + } + } + } + } + return new LocationSummary( + id, + locationSpec.getName(), + locationSpec.getSpec(), + null, + copyConfig(config, level), + ImmutableMap.of("self", URI.create("/v1/locations/" + id))); + } + + /** @deprecated since 0.7.0 use method taking management context and detail specifier */ + @Deprecated + public static LocationSummary newInstance(LocationDefinition l) { + return newInstance(null, l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET); + } + + public static LocationSummary newInstance(ManagementContext mgmt, LocationDefinition l, LocationDetailLevel level) { + // TODO: Can remove null checks on mgmt when newInstance(LocationDefinition) is deleted + Map<String, Object> config = l.getConfig(); + if (mgmt != null && (level==LocationDetailLevel.FULL_EXCLUDING_SECRET || level==LocationDetailLevel.FULL_INCLUDING_SECRET)) { + Location ll = mgmt.getLocationRegistry().resolve(l, false, null).orNull(); + if (ll!=null) config = ((LocationInternal)ll).config().getBag().getAllConfig(); + } else if (level==LocationDetailLevel.LOCAL_EXCLUDING_SECRET) { + // get displayName + if (mgmt != null && !config.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) { + Location ll = mgmt.getLocationRegistry().resolve(l, false, null).orNull(); + if (ll!=null) { + Map<String, Object> configExtra = ((LocationInternal)ll).config().getBag().getAllConfig(); + if (configExtra.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) { + ConfigBag configNew = ConfigBag.newInstance(config); + configNew.configure(LocationConfigKeys.DISPLAY_NAME, (String)configExtra.get(LocationConfigKeys.DISPLAY_NAME.getName())); + config = configNew.getAllConfig(); + } + } + } + } + + return new LocationSummary( + l.getId(), + l.getName(), + l.getSpec(), + null, + copyConfig(config, level), + ImmutableMap.of("self", URI.create("/v1/locations/" + l.getId()))); + } + + private static Map<String, ?> copyConfig(Map<String,?> entries, LocationDetailLevel level) { + ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder(); + if (level!=LocationDetailLevel.NONE) { + for (Map.Entry<String,?> entry : entries.entrySet()) { + if (level==LocationDetailLevel.FULL_INCLUDING_SECRET || !Sanitizer.IS_SECRET_PREDICATE.apply(entry.getKey())) { + builder.put(entry.getKey(), WebResourceUtils.getValueForDisplay(entry.getValue(), true, false)); + } + } + } + return builder.build(); + } + + public static LocationSummary newInstance(ManagementContext mgmt, Location l, LocationDetailLevel level) { + String spec = null; + String specId = null; + Location lp = l; + while (lp!=null && (spec==null || specId==null)) { + // walk parent locations + // TODO not sure this is the best strategy, or if it's needed, as the spec config is inherited anyway... + if (spec==null) { + Maybe<Object> originalSpec = ((LocationInternal)lp).config().getRaw(LocationInternal.ORIGINAL_SPEC); + if (originalSpec.isPresent()) + spec = Strings.toString(originalSpec.get()); + } + if (specId==null) { + LocationDefinition ld = null; + // prefer looking it up by name as this loads the canonical definition + if (spec!=null) ld = mgmt.getLocationRegistry().getDefinedLocationByName(spec); + if (ld==null && spec!=null && spec.startsWith("named:")) + ld = mgmt.getLocationRegistry().getDefinedLocationByName(Strings.removeFromStart(spec, "named:")); + if (ld==null) ld = mgmt.getLocationRegistry().getDefinedLocationById(lp.getId()); + if (ld!=null) { + if (spec==null) spec = ld.getSpec(); + specId = ld.getId(); + } + } + lp = lp.getParent(); + } + if (specId==null && spec!=null) { + // fall back to attempting to lookup it + Location ll = mgmt.getLocationRegistry().resolve(spec, false, null).orNull(); + if (ll!=null) specId = ll.getId(); + } + + Map<String, Object> configOrig; + if (level == LocationDetailLevel.LOCAL_EXCLUDING_SECRET) { + configOrig = MutableMap.copyOf(((LocationInternal)l).config().getLocalBag().getAllConfig()); + } else { + configOrig = MutableMap.copyOf(((LocationInternal)l).config().getBag().getAllConfig()); + } + if (level==LocationDetailLevel.LOCAL_EXCLUDING_SECRET) { + // for LOCAL, also get the display name + if (!configOrig.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) { + Map<String, Object> configExtra = ((LocationInternal)l).config().getBag().getAllConfig(); + if (configExtra.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) + configOrig.put(LocationConfigKeys.DISPLAY_NAME.getName(), configExtra.get(LocationConfigKeys.DISPLAY_NAME.getName())); + } + } + Map<String, ?> config = level==LocationDetailLevel.NONE ? null : copyConfig(configOrig, level); + + return new LocationSummary( + l.getId(), + l.getDisplayName(), + spec, + l.getClass().getName(), + config, + MutableMap.of("self", URI.create("/v1/locations/" + l.getId())) + .addIfNotNull("parent", l.getParent()!=null ? URI.create("/v1/locations/"+l.getParent().getId()) : null) + .addIfNotNull("spec", specId!=null ? URI.create("/v1/locations/"+specId) : null) + .asUnmodifiable() ); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java new file mode 100644 index 0000000..6893633 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java @@ -0,0 +1,84 @@ +/* + * 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.rest.transform; + +import java.net.URI; +import java.util.Map; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.policy.Policy; +import brooklyn.policy.basic.Policies; +import brooklyn.rest.domain.ApplicationSummary; +import brooklyn.rest.domain.PolicyConfigSummary; +import brooklyn.rest.domain.PolicySummary; +import org.apache.brooklyn.rest.resources.PolicyConfigResource; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; + +import com.google.common.collect.ImmutableMap; + +/** + * Converts from Brooklyn entities to restful API summary objects + */ +public class PolicyTransformer { + + public static PolicySummary policySummary(Entity entity, Policy policy) { + String applicationUri = "/v1/applications/" + entity.getApplicationId(); + String entityUri = applicationUri + "/entities/" + entity.getId(); + + Map<String, URI> links = ImmutableMap.<String, URI>builder() + .put("self", URI.create(entityUri + "/policies/" + policy.getId())) + .put("config", URI.create(entityUri + "/policies/" + policy.getId() + "/config")) + .put("start", URI.create(entityUri + "/policies/" + policy.getId() + "/start")) + .put("stop", URI.create(entityUri + "/policies/" + policy.getId() + "/stop")) + .put("destroy", URI.create(entityUri + "/policies/" + policy.getId() + "/destroy")) + .put("application", URI.create(applicationUri)) + .put("entity", URI.create(entityUri)) + .build(); + + return new PolicySummary(policy.getId(), policy.getDisplayName(), policy.getCatalogItemId(), ApplicationTransformer.statusFromLifecycle(Policies.getPolicyStatus(policy)), links); + } + + public static PolicyConfigSummary policyConfigSummary(BrooklynRestResourceUtils utils, ApplicationSummary application, EntityLocal entity, Policy policy, ConfigKey<?> config) { + PolicyConfigSummary summary = policyConfigSummary(utils, entity, policy, config); +// TODO +// if (!entity.getApplicationId().equals(application.getInstance().getId())) +// throw new IllegalStateException("Application "+application+" does not match app "+entity.getApplication()+" of "+entity); + return summary; + } + + public static PolicyConfigSummary policyConfigSummary(BrooklynRestResourceUtils utils, EntityLocal entity, Policy policy, ConfigKey<?> config) { + String applicationUri = "/v1/applications/" + entity.getApplicationId(); + String entityUri = applicationUri + "/entities/" + entity.getId(); + String policyUri = entityUri + "/policies/" + policy.getId(); + + Map<String, URI> links = ImmutableMap.<String, URI>builder() + .put("self", URI.create(policyUri + "/config/" + config.getName())) + .put("application", URI.create(applicationUri)) + .put("entity", URI.create(entityUri)) + .put("policy", URI.create(policyUri)) + .build(); + + return new PolicyConfigSummary(config.getName(), config.getTypeName(), config.getDescription(), + PolicyConfigResource.getStringValueForDisplay(utils, policy, config.getDefaultValue()), + config.isReconfigurable(), + links); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java new file mode 100644 index 0000000..dfa6de0 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java @@ -0,0 +1,85 @@ +/* + * 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.rest.transform; + +import java.net.URI; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.render.RendererHints; +import brooklyn.entity.Entity; +import brooklyn.event.AttributeSensor; +import brooklyn.event.Sensor; +import brooklyn.rest.domain.SensorSummary; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.net.URLParamEncoder; +import brooklyn.util.text.Strings; + +import com.google.common.collect.Iterables; + +public class SensorTransformer { + + private static final Logger log = LoggerFactory.getLogger(SensorTransformer.class); + + public static SensorSummary sensorSummaryForCatalog(Sensor<?> sensor) { + return new SensorSummary(sensor.getName(), sensor.getTypeName(), + sensor.getDescription(), null); + } + + public static SensorSummary sensorSummary(Entity entity, Sensor<?> sensor) { + String applicationUri = "/v1/applications/" + entity.getApplicationId(); + String entityUri = applicationUri + "/entities/" + entity.getId(); + String selfUri = entityUri + "/sensors/" + URLParamEncoder.encode(sensor.getName()); + + MutableMap.Builder<String, URI> lb = MutableMap.<String, URI>builder() + .put("self", URI.create(selfUri)) + .put("application", URI.create(applicationUri)) + .put("entity", URI.create(entityUri)) + .put("action:json", URI.create(selfUri)); + + if (sensor instanceof AttributeSensor) { + Iterable<RendererHints.NamedAction> hints = Iterables.filter(RendererHints.getHintsFor((AttributeSensor<?>)sensor), RendererHints.NamedAction.class); + for (RendererHints.NamedAction na : hints) addNamedAction(lb, na , entity, sensor); + } + + return new SensorSummary(sensor.getName(), sensor.getTypeName(), sensor.getDescription(), lb.build()); + } + + private static <T> void addNamedAction(MutableMap.Builder<String, URI> lb, RendererHints.NamedAction na , Entity entity, Sensor<T> sensor) { + addNamedAction(lb, na, entity.getAttribute( ((AttributeSensor<T>) sensor) ), sensor, entity); + } + + @SuppressWarnings("unchecked") + static <T> void addNamedAction(MutableMap.Builder<String, URI> lb, RendererHints.NamedAction na, T value, Object context, Entity contextEntity) { + if (na instanceof RendererHints.NamedActionWithUrl) { + try { + String v = ((RendererHints.NamedActionWithUrl<T>) na).getUrlFromValue(value); + if (Strings.isNonBlank(v)) { + String action = na.getActionName().toLowerCase(); + lb.putIfAbsent("action:"+action, URI.create(v)); + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Unable to make action "+na+" from "+context+" on "+contextEntity+": "+e, e); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java new file mode 100644 index 0000000..50b07e8 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.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.rest.transform; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.Entity; +import brooklyn.entity.basic.BrooklynTaskTags; +import brooklyn.entity.basic.BrooklynTaskTags.WrappedStream; +import brooklyn.management.HasTaskChildren; +import brooklyn.management.Task; +import brooklyn.rest.domain.LinkWithMetadata; +import brooklyn.rest.domain.TaskSummary; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.task.TaskInternal; +import brooklyn.util.text.Strings; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; + +public class TaskTransformer { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(TaskTransformer.class); + + public static final Function<Task<?>, TaskSummary> FROM_TASK = new Function<Task<?>, TaskSummary>() { + @Override + public TaskSummary apply(@Nullable Task<?> input) { + return taskSummary(input); + } + }; + + public static TaskSummary taskSummary(Task<?> task) { + try { + Preconditions.checkNotNull(task); + Entity entity = BrooklynTaskTags.getContextEntity(task); + String entityId; + String entityDisplayName; + URI entityLink; + + String selfLink = asLink(task).getLink(); + + if (entity != null) { + entityId = entity.getId(); + entityDisplayName = entity.getDisplayName(); + entityLink = new URI("/v1/applications/"+entity.getApplicationId()+"/"+"entities"+"/"+entity.getId()); + } else { + entityId = null; + entityDisplayName = null; + entityLink = null; + } + + List<LinkWithMetadata> children = Collections.emptyList(); + if (task instanceof HasTaskChildren) { + children = new ArrayList<LinkWithMetadata>(); + for (Task<?> t: ((HasTaskChildren)task).getChildren()) { + children.add(asLink(t)); + } + } + + Map<String,LinkWithMetadata> streams = new MutableMap<String, LinkWithMetadata>(); + for (WrappedStream stream: BrooklynTaskTags.streams(task)) { + MutableMap<String, Object> metadata = MutableMap.<String,Object>of("name", stream.streamType); + if (stream.streamSize.get()!=null) { + metadata.add("size", stream.streamSize.get()); + metadata.add("sizeText", Strings.makeSizeString(stream.streamSize.get())); + } + String link = selfLink+"/stream/"+stream.streamType; + streams.put(stream.streamType, new LinkWithMetadata(link, metadata)); + } + + Map<String,URI> links = MutableMap.of("self", new URI(selfLink), + "children", new URI(selfLink+"/"+"children")); + if (entityLink!=null) links.put("entity", entityLink); + + Object result; + try { + if (task.isDone()) { + result = WebResourceUtils.getValueForDisplay(task.get(), true, false); + } else { + result = null; + } + } catch (Throwable t) { + result = Exceptions.collapseText(t); + } + + return new TaskSummary(task.getId(), task.getDisplayName(), task.getDescription(), entityId, entityDisplayName, + task.getTags(), ifPositive(task.getSubmitTimeUtc()), ifPositive(task.getStartTimeUtc()), ifPositive(task.getEndTimeUtc()), + task.getStatusSummary(), result, task.isError(), task.isCancelled(), + children, asLink(task.getSubmittedByTask()), + task.isDone() ? null : task instanceof TaskInternal ? asLink(((TaskInternal<?>)task).getBlockingTask()) : null, + task.isDone() ? null : task instanceof TaskInternal ? ((TaskInternal<?>)task).getBlockingDetails() : null, + task.getStatusDetail(true), + streams, + links); + } catch (URISyntaxException e) { + // shouldn't happen + throw Exceptions.propagate(e); + } + } + + private static Long ifPositive(Long time) { + if (time==null || time<=0) return null; + return time; + } + + public static LinkWithMetadata asLink(Task<?> t) { + if (t==null) return null; + MutableMap<String,Object> data = new MutableMap<String,Object>(); + data.put("id", t.getId()); + if (t.getDisplayName()!=null) data.put("taskName", t.getDisplayName()); + Entity entity = BrooklynTaskTags.getContextEntity(t); + if (entity!=null) { + data.put("entityId", entity.getId()); + if (entity.getDisplayName()!=null) data.put("entityDisplayName", entity.getDisplayName()); + } + return new LinkWithMetadata("/v1/activities/"+t.getId(), data); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java new file mode 100644 index 0000000..11df7b5 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java @@ -0,0 +1,564 @@ +/* + * 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.rest.util; + +import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound; +import static com.google.common.collect.Iterables.transform; +import groovy.lang.GroovyClassLoader; + +import java.lang.reflect.Constructor; +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.Future; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.basic.BrooklynTypes; +import org.apache.brooklyn.catalog.BrooklynCatalog; +import org.apache.brooklyn.catalog.CatalogItem; +import brooklyn.catalog.internal.CatalogUtils; +import brooklyn.config.ConfigKey; +import brooklyn.enricher.Enrichers; +import brooklyn.entity.Application; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.AbstractEntity; +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.BasicApplication; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityInternal; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.entity.trait.Startable; +import brooklyn.location.Location; +import brooklyn.location.LocationRegistry; +import brooklyn.management.ManagementContext; +import brooklyn.management.Task; +import brooklyn.management.entitlement.Entitlements; +import brooklyn.management.entitlement.Entitlements.StringAndArgument; +import brooklyn.policy.Policy; +import brooklyn.policy.basic.AbstractPolicy; +import brooklyn.rest.domain.ApplicationSpec; +import brooklyn.rest.domain.EntitySpec; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.flags.TypeCoercions; +import brooklyn.util.javalang.Reflections; +import brooklyn.util.net.Urls; +import brooklyn.util.text.Strings; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.io.Files; + +public class BrooklynRestResourceUtils { + + private static final Logger log = LoggerFactory.getLogger(BrooklynRestResourceUtils.class); + + private final ManagementContext mgmt; + + public BrooklynRestResourceUtils(ManagementContext mgmt) { + Preconditions.checkNotNull(mgmt, "mgmt"); + this.mgmt = mgmt; + } + + public BrooklynCatalog getCatalog() { + return mgmt.getCatalog(); + } + + public ClassLoader getCatalogClassLoader() { + return mgmt.getCatalogClassLoader(); + } + + public LocationRegistry getLocationRegistry() { + return mgmt.getLocationRegistry(); + } + + /** finds the policy indicated by the given ID or name. + * @see {@link #getEntity(String,String)}; it then searches the policies of that + * entity for one whose ID or name matches that given. + * <p> + * + * @throws 404 or 412 (unless input is null in which case output is null) */ + public Policy getPolicy(String application, String entity, String policy) { + return getPolicy(getEntity(application, entity), policy); + } + + /** finds the policy indicated by the given ID or name. + * @see {@link #getPolicy(String,String,String)}. + * <p> + * + * @throws 404 or 412 (unless input is null in which case output is null) */ + public Policy getPolicy(Entity entity, String policy) { + if (policy==null) return null; + + for (Policy p: entity.getPolicies()) { + if (policy.equals(p.getId())) return p; + } + for (Policy p: entity.getPolicies()) { + if (policy.equals(p.getDisplayName())) return p; + } + + throw WebResourceUtils.notFound("Cannot find policy '%s' in entity '%s'", policy, entity); + } + + /** finds the entity indicated by the given ID or name + * <p> + * prefers ID based lookup in which case appId is optional, and if supplied will be enforced. + * optionally the name can be supplied, for cases when paths should work across versions, + * in which case names will be searched recursively (and the application is required). + * + * @throws 404 or 412 (unless input is null in which case output is null) */ + public EntityLocal getEntity(String application, String entity) { + if (entity==null) return null; + Application app = application!=null ? getApplication(application) : null; + EntityLocal e = (EntityLocal) mgmt.getEntityManager().getEntity(entity); + + if (e!=null) { + if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_ENTITY, e)) { + throw WebResourceUtils.notFound("Cannot find entity '%s': no known ID and application not supplied for searching", entity); + } + + if (app==null || app.equals(findTopLevelApplication(e))) return e; + throw WebResourceUtils.preconditionFailed("Application '%s' specified does not match application '%s' to which entity '%s' (%s) is associated", + application, e.getApplication()==null ? null : e.getApplication().getId(), entity, e); + } + if (application==null) + throw WebResourceUtils.notFound("Cannot find entity '%s': no known ID and application not supplied for searching", entity); + + assert app!=null : "null app should not be returned from getApplication"; + e = searchForEntityNamed(app, entity); + if (e!=null) return e; + throw WebResourceUtils.notFound("Cannot find entity '%s' in application '%s' (%s)", entity, application, app); + } + + private Application findTopLevelApplication(Entity e) { + // For nested apps, e.getApplication() can return its direct parent-app rather than the root app + // (particularly if e.getApplication() was called before the parent-app was wired up to its parent, + // because that call causes the application to be cached). + // Therefore we continue to walk the hierarchy until we find an "orphaned" application at the top. + + Application app = e.getApplication(); + while (app != null && !app.equals(app.getApplication())) { + app = app.getApplication(); + } + return app; + } + + /** looks for the given application instance, first by ID then by name + * + * @throws 404 if not found, or not entitled + */ + public Application getApplication(String application) { + Entity e = mgmt.getEntityManager().getEntity(application); + if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_ENTITY, e)) { + throw notFound("Application '%s' not found", application); + } + + if (e != null && e instanceof Application) return (Application) e; + for (Application app : mgmt.getApplications()) { + if (app.getId().equals(application)) return app; + if (application.equalsIgnoreCase(app.getDisplayName())) return app; + } + + throw notFound("Application '%s' not found", application); + } + + /** walks the hierarchy (depth-first) at root (often an Application) looking for + * an entity matching the given ID or name; returns the first such entity, or null if none found + **/ + public EntityLocal searchForEntityNamed(Entity root, String entity) { + if (root.getId().equals(entity) || entity.equals(root.getDisplayName())) return (EntityLocal) root; + for (Entity child: root.getChildren()) { + Entity result = searchForEntityNamed(child, entity); + if (result!=null) return (EntityLocal) result; + } + return null; + } + + @SuppressWarnings({ "unchecked", "deprecation" }) + public Application create(ApplicationSpec spec) { + log.debug("REST creating application instance for {}", spec); + + if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) { + throw WebResourceUtils.unauthorized("User '%s' is not authorized to deploy application %s", + Entitlements.getEntitlementContext().user(), spec); + } + + final String type = spec.getType(); + final String name = spec.getName(); + final Map<String,String> configO = spec.getConfig(); + final Set<EntitySpec> entities = (spec.getEntities() == null) ? ImmutableSet.<EntitySpec>of() : spec.getEntities(); + + final Application instance; + + // Load the class; first try to use the appropriate catalog item; but then allow anything that is on the classpath + final Class<? extends Entity> clazz; + final String catalogItemId; + if (Strings.isEmpty(type)) { + clazz = BasicApplication.class; + catalogItemId = null; + } else { + Class<? extends Entity> tempclazz; + BrooklynCatalog catalog = getCatalog(); + CatalogItem<?, ?> item = catalog.getCatalogItemForType(type); + if (item != null) { + catalogItemId = item.getId(); + tempclazz = (Class<? extends Entity>) catalog.loadClass(item); + } else { + catalogItemId = null; + try { + tempclazz = (Class<? extends Entity>) catalog.getRootClassLoader().loadClass(type); + log.info("Catalog does not contain item for type {}; loaded class directly instead", type); + } catch (ClassNotFoundException e2) { + log.warn("No catalog item for type {}, and could not load class directly; rethrowing", type); + throw new NoSuchElementException("Unable to find catalog item for type "+type); + } + } + clazz = tempclazz; + } + if (Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, null)) { + + try { + if (ApplicationBuilder.class.isAssignableFrom(clazz)) { + Constructor<?> constructor = clazz.getConstructor(); + ApplicationBuilder appBuilder = (ApplicationBuilder) constructor.newInstance(); + if (!Strings.isEmpty(name)) appBuilder.appDisplayName(name); + if (entities.size() > 0) + log.warn("Cannot supply additional entities when using an ApplicationBuilder; ignoring in spec {}", spec); + + log.info("REST placing '{}' under management", spec.getName()); + appBuilder.configure(convertFlagsToKeys(appBuilder.getType(), configO)); + configureRenderingMetadata(spec, appBuilder); + instance = appBuilder.manage(mgmt); + + } else if (Application.class.isAssignableFrom(clazz)) { + brooklyn.entity.proxying.EntitySpec<?> coreSpec = toCoreEntitySpec(clazz, name, configO, catalogItemId); + configureRenderingMetadata(spec, coreSpec); + instance = (Application) mgmt.getEntityManager().createEntity(coreSpec); + for (EntitySpec entitySpec : entities) { + log.info("REST creating instance for entity {}", entitySpec.getType()); + instance.addChild(mgmt.getEntityManager().createEntity(toCoreEntitySpec(entitySpec))); + } + + log.info("REST placing '{}' under management", spec.getName() != null ? spec.getName() : spec); + Entities.startManagement(instance, mgmt); + + } else if (Entity.class.isAssignableFrom(clazz)) { + if (entities.size() > 0) + log.warn("Cannot supply additional entities when using a non-application entity; ignoring in spec {}", spec); + + brooklyn.entity.proxying.EntitySpec<?> coreSpec = toCoreEntitySpec(BasicApplication.class, name, configO, catalogItemId); + configureRenderingMetadata(spec, coreSpec); + + instance = (Application) mgmt.getEntityManager().createEntity(coreSpec); + + Entity soleChild = mgmt.getEntityManager().createEntity(toCoreEntitySpec(clazz, name, configO, catalogItemId)); + instance.addChild(soleChild); + instance.addEnricher(Enrichers.builder() + .propagatingAllBut(Attributes.SERVICE_UP, Attributes.SERVICE_NOT_UP_INDICATORS, + Attributes.SERVICE_STATE_ACTUAL, Attributes.SERVICE_STATE_EXPECTED, + Attributes.SERVICE_PROBLEMS) + .from(soleChild) + .build()); + + log.info("REST placing '{}' under management", spec.getName()); + Entities.startManagement(instance, mgmt); + + } else { + throw new IllegalArgumentException("Class " + clazz + " must extend one of ApplicationBuilder, Application or Entity"); + } + + return instance; + + } catch (Exception e) { + log.error("REST failed to create application: " + e, e); + throw Exceptions.propagate(e); + } + } + throw WebResourceUtils.unauthorized("User '%s' is not authorized to create application from applicationSpec %s", + Entitlements.getEntitlementContext().user(), spec); + } + + public Task<?> start(Application app, ApplicationSpec spec) { + return start(app, getLocations(spec)); + } + + public Task<?> start(Application app, List<? extends Location> locations) { + return Entities.invokeEffector((EntityLocal)app, app, Startable.START, + MutableMap.of("locations", locations)); + } + + public List<Location> getLocations(ApplicationSpec spec) { + // Start all the managed entities by asking the app instance to start in background + Function<String, Location> buildLocationFromId = new Function<String, Location>() { + @Override + public Location apply(String id) { + id = fixLocation(id); + return getLocationRegistry().resolve(id); + } + }; + + ArrayList<Location> locations = Lists.newArrayList(transform(spec.getLocations(), buildLocationFromId)); + return locations; + } + + @SuppressWarnings({ "unchecked", "deprecation" }) + private brooklyn.entity.proxying.EntitySpec<? extends Entity> toCoreEntitySpec(brooklyn.rest.domain.EntitySpec spec) { + String type = spec.getType(); + String name = spec.getName(); + Map<String, String> config = (spec.getConfig() == null) ? Maps.<String,String>newLinkedHashMap() : Maps.newLinkedHashMap(spec.getConfig()); + + BrooklynCatalog catalog = getCatalog(); + CatalogItem<?, ?> item = catalog.getCatalogItemForType(type); + Class<? extends Entity> tempclazz; + final String catalogItemId; + if (item != null) { + tempclazz = (Class<? extends Entity>) catalog.loadClass(item); + catalogItemId = item.getId(); + } else { + catalogItemId = null; + try { + tempclazz = (Class<? extends Entity>) catalog.getRootClassLoader().loadClass(type); + log.info("Catalog does not contain item for type {}; loaded class directly instead", type); + } catch (ClassNotFoundException e2) { + log.warn("No catalog item for type {}, and could not load class directly; rethrowing", type); + throw new NoSuchElementException("Unable to find catalog item for type "+type); + } + } + final Class<? extends Entity> clazz = tempclazz; + brooklyn.entity.proxying.EntitySpec<? extends Entity> result; + if (clazz.isInterface()) { + result = brooklyn.entity.proxying.EntitySpec.create(clazz); + } else { + result = brooklyn.entity.proxying.EntitySpec.create(Entity.class).impl(clazz).additionalInterfaces(Reflections.getAllInterfaces(clazz)); + } + result.catalogItemId(catalogItemId); + if (!Strings.isEmpty(name)) result.displayName(name); + result.configure( convertFlagsToKeys(result.getType(), config) ); + configureRenderingMetadata(spec, result); + return result; + } + + protected void configureRenderingMetadata(ApplicationSpec spec, ApplicationBuilder appBuilder) { + appBuilder.configure(getRenderingConfigurationFor(spec.getType())); + } + + protected void configureRenderingMetadata(ApplicationSpec input, brooklyn.entity.proxying.EntitySpec<?> entity) { + entity.configure(getRenderingConfigurationFor(input.getType())); + } + + protected void configureRenderingMetadata(EntitySpec input, brooklyn.entity.proxying.EntitySpec<?> entity) { + entity.configure(getRenderingConfigurationFor(input.getType())); + } + + protected Map<?, ?> getRenderingConfigurationFor(String catalogId) { + MutableMap<Object, Object> result = MutableMap.of(); + CatalogItem<?,?> item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, catalogId); + if (item==null) return result; + + result.addIfNotNull("iconUrl", item.getIconUrl()); + return result; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private <T extends Entity> brooklyn.entity.proxying.EntitySpec<?> toCoreEntitySpec(Class<T> clazz, String name, Map<String,String> configO, String catalogItemId) { + Map<String, String> config = (configO == null) ? Maps.<String,String>newLinkedHashMap() : Maps.newLinkedHashMap(configO); + + brooklyn.entity.proxying.EntitySpec<? extends Entity> result; + if (clazz.isInterface()) { + result = brooklyn.entity.proxying.EntitySpec.create(clazz); + } else { + // If this is a concrete class, particularly for an Application class, we want the proxy + // to expose all interfaces it implements. + Class interfaceclazz = (Application.class.isAssignableFrom(clazz)) ? Application.class : Entity.class; + Set<Class<?>> additionalInterfaceClazzes = Reflections.getInterfacesIncludingClassAncestors(clazz); + result = brooklyn.entity.proxying.EntitySpec.create(interfaceclazz).impl(clazz).additionalInterfaces(additionalInterfaceClazzes); + } + + result.catalogItemId(catalogItemId); + if (!Strings.isEmpty(name)) result.displayName(name); + result.configure( convertFlagsToKeys(result.getImplementation(), config) ); + return result; + } + + private Map<?,?> convertFlagsToKeys(Class<? extends Entity> javaType, Map<?, ?> config) { + if (config==null || config.isEmpty() || javaType==null) return config; + + Map<String, ConfigKey<?>> configKeys = BrooklynTypes.getDefinedConfigKeys(javaType); + Map<Object,Object> result = new LinkedHashMap<Object,Object>(); + for (Map.Entry<?,?> entry: config.entrySet()) { + log.debug("Setting key {} to {} for REST creation of {}", new Object[] { entry.getKey(), entry.getValue(), javaType}); + Object key = configKeys.get(entry.getKey()); + if (key==null) { + log.warn("Unrecognised config key {} passed to {}; will be treated as flag (and likely ignored)", entry.getKey(), javaType); + key = entry.getKey(); + } + result.put(key, entry.getValue()); + } + return result; + } + + public Task<?> destroy(final Application application) { + return mgmt.getExecutionManager().submit( + MutableMap.of("displayName", "destroying "+application, + "description", "REST call to destroy application "+application.getDisplayName()+" ("+application+")"), + new Runnable() { + @Override + public void run() { + ((EntityInternal)application).destroy(); + mgmt.getEntityManager().unmanage(application); + } + }); + } + + public Task<?> expunge(final Entity entity, final boolean release) { + if (mgmt.getEntitlementManager().isEntitled(Entitlements.getEntitlementContext(), + Entitlements.INVOKE_EFFECTOR, Entitlements.EntityAndItem.of(entity, + StringAndArgument.of("expunge", MutableMap.of("release", release))))) { + return mgmt.getExecutionManager().submit( + MutableMap.of("displayName", "expunging " + entity, "description", "REST call to expunge entity " + + entity.getDisplayName() + " (" + entity + ")"), new Runnable() { + @Override + public void run() { + if (release) + Entities.destroyCatching(entity); + else + mgmt.getEntityManager().unmanage(entity); + } + }); + } + throw WebResourceUtils.unauthorized("User '%s' is not authorized to expunge entity %s", + Entitlements.getEntitlementContext().user(), entity); + } + + + @Deprecated + @SuppressWarnings({ "rawtypes" }) + public Response createCatalogEntryFromGroovyCode(String groovyCode) { + ClassLoader parent = getCatalog().getRootClassLoader(); + @SuppressWarnings("resource") + GroovyClassLoader loader = new GroovyClassLoader(parent); + + Class clazz = loader.parseClass(groovyCode); + + if (AbstractEntity.class.isAssignableFrom(clazz)) { + CatalogItem<?,?> item = getCatalog().addItem(clazz); + log.info("REST created "+item); + return Response.created(URI.create("entities/" + clazz.getName())).build(); + + } else if (AbstractPolicy.class.isAssignableFrom(clazz)) { + CatalogItem<?,?> item = getCatalog().addItem(clazz); + log.info("REST created "+item); + return Response.created(URI.create("policies/" + clazz.getName())).build(); + } + + throw WebResourceUtils.preconditionFailed("Unsupported type superclass "+clazz.getSuperclass()+"; expects Entity or Policy"); + } + + @Deprecated + public static String fixLocation(String locationId) { + if (locationId.startsWith("/v1/locations/")) { + log.warn("REST API using legacy URI syntax for location: "+locationId); + locationId = Strings.removeFromStart(locationId, "/v1/locations/"); + } + return locationId; + } + + public Object getObjectValueForDisplay(Object value) { + if (value==null) return null; + // currently everything converted to string, expanded if it is a "done" future + if (value instanceof Future) { + if (((Future<?>)value).isDone()) { + try { + value = ((Future<?>)value).get(); + } catch (Exception e) { + value = ""+value+" (error evaluating: "+e+")"; + } + } + } + + if (TypeCoercions.isPrimitiveOrBoxer(value.getClass())) return value; + return value.toString(); + } + + // currently everything converted to string, expanded if it is a "done" future + public String getStringValueForDisplay(Object value) { + if (value==null) return null; + return ""+getObjectValueForDisplay(value); + } + + /** true if the URL points to content which must be resolved on the server-side (i.e. classpath) + * and which is safe to do so (currently just images, though in future perhaps also javascript and html plugins) + * <p> + * note we do not let caller access classpath through this mechanism, + * just those which are supplied by the platform administrator e.g. as an icon url */ + public boolean isUrlServerSideAndSafe(String url) { + if (Strings.isEmpty(url)) return false; + String ext = Files.getFileExtension(url); + if (Strings.isEmpty(ext)) return false; + MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(ext); + if (mime==null) return false; + + return !Urls.isUrlWithProtocol(url) || url.startsWith("classpath:"); + } + + + public Iterable<Entity> descendantsOfAnyType(String application, String entity) { + List<Entity> result = Lists.newArrayList(); + EntityLocal e = getEntity(application, entity); + gatherAllDescendants(e, result); + return result; + } + + private static void gatherAllDescendants(Entity e, List<Entity> result) { + if (result.add(e)) { + for (Entity ee: e.getChildren()) + gatherAllDescendants(ee, result); + } + } + + public Iterable<Entity> descendantsOfType(String application, String entity, final String typeRegex) { + Iterable<Entity> result = descendantsOfAnyType(application, entity); + return Iterables.filter(result, new Predicate<Entity>() { + @Override + public boolean apply(Entity entity) { + if (entity==null) return false; + return (entity.getEntityType().getName().matches(typeRegex)); + } + }); + } + + public void reloadBrooklynProperties() { + mgmt.reloadBrooklynProperties(); + } +}