http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java new file mode 100644 index 0000000..986855e --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java @@ -0,0 +1,451 @@ +/* + * 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.resources; + +import static com.google.common.base.Preconditions.checkNotNull; +import static javax.ws.rs.core.Response.created; +import static javax.ws.rs.core.Response.status; +import static javax.ws.rs.core.Response.Status.ACCEPTED; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.UriInfo; + +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.entity.Group; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.api.objs.BrooklynObject; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.config.ConstraintViolationException; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.core.mgmt.EntityManagementUtils; +import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult; +import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.entity.group.AbstractGroup; +import org.apache.brooklyn.rest.api.ApplicationApi; +import org.apache.brooklyn.rest.domain.ApplicationSpec; +import org.apache.brooklyn.rest.domain.ApplicationSummary; +import org.apache.brooklyn.rest.domain.EntityDetail; +import org.apache.brooklyn.rest.domain.EntitySummary; +import org.apache.brooklyn.rest.domain.TaskSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.ApplicationTransformer; +import org.apache.brooklyn.rest.transform.EntityTransformer; +import org.apache.brooklyn.rest.transform.TaskTransformer; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.UserFacingException; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Throwables; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +@HaHotStateRequired +public class ApplicationResource extends AbstractBrooklynRestResource implements ApplicationApi { + + private static final Logger log = LoggerFactory.getLogger(ApplicationResource.class); + + @Context + private UriInfo uriInfo; + + private EntityDetail fromEntity(Entity entity) { + Boolean serviceUp = entity.getAttribute(Attributes.SERVICE_UP); + + Lifecycle serviceState = entity.getAttribute(Attributes.SERVICE_STATE_ACTUAL); + + String iconUrl = entity.getIconUrl(); + if (iconUrl!=null) { + if (brooklyn().isUrlServerSideAndSafe(iconUrl)) + // route to server if it is a server-side url + iconUrl = EntityTransformer.entityUri(entity, ui.getBaseUriBuilder())+"/icon"; + } + + List<EntitySummary> children = Lists.newArrayList(); + if (!entity.getChildren().isEmpty()) { + for (Entity child : entity.getChildren()) { + children.add(fromEntity(child)); + } + } + + String parentId = null; + if (entity.getParent()!= null) { + parentId = entity.getParent().getId(); + } + + List<String> groupIds = Lists.newArrayList(); + if (!entity.groups().isEmpty()) { + groupIds.addAll(entitiesIdAsArray(entity.groups())); + } + + List<Map<String, String>> members = Lists.newArrayList(); + if (entity instanceof Group) { + // use attribute instead of method in case it is read-only + Collection<Entity> memberEntities = entity.getAttribute(AbstractGroup.GROUP_MEMBERS); + if (memberEntities != null && !memberEntities.isEmpty()) + members.addAll(entitiesIdAndNameAsList(memberEntities)); + } + + return new EntityDetail( + entity.getApplicationId(), + entity.getId(), + parentId, + entity.getDisplayName(), + entity.getEntityType().getName(), + serviceUp, + serviceState, + iconUrl, + entity.getCatalogItemId(), + children, + groupIds, + members); + } + + private List<Map<String, String>> entitiesIdAndNameAsList(Collection<? extends Entity> entities) { + List<Map<String, String>> members = Lists.newArrayList(); + for (Entity entity : entities) { + if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + members.add(ImmutableMap.of("id", entity.getId(), "name", entity.getDisplayName())); + } + } + return members; + } + + private List<String> entitiesIdAsArray(Iterable<? extends Entity> entities) { + List<String> ids = Lists.newArrayList(); + for (Entity entity : entities) { + if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + ids.add(entity.getId()); + } + } + return ids; + } + + @Override + public List<EntityDetail> fetch(String entityIds) { + + List<EntityDetail> entitySummaries = Lists.newArrayList(); + for (Entity application : mgmt().getApplications()) { + entitySummaries.add(fromEntity(application)); + } + + if (entityIds != null) { + for (String entityId: entityIds.split(",")) { + Entity entity = mgmt().getEntityManager().getEntity(entityId.trim()); + while (entity != null && entity.getParent() != null) { + if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + entitySummaries.add(fromEntity(entity)); + } + entity = entity.getParent(); + } + } + } + return entitySummaries; + } + + @Override + public List<ApplicationSummary> list(String typeRegex) { + if (Strings.isBlank(typeRegex)) { + typeRegex = ".*"; + } + return FluentIterable + .from(mgmt().getApplications()) + .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY)) + .filter(EntityPredicates.hasInterfaceMatching(typeRegex)) + .transform(ApplicationTransformer.fromApplication(ui.getBaseUriBuilder())) + .toList(); + } + + @Override + public ApplicationSummary get(String application) { + return ApplicationTransformer.summaryFromApplication(brooklyn().getApplication(application), ui.getBaseUriBuilder()); + } + + @Override + public Response create(ApplicationSpec applicationSpec) { + return createFromAppSpec(applicationSpec); + } + + /** @deprecated since 0.7.0 see #create */ @Deprecated + protected Response createFromAppSpec(ApplicationSpec applicationSpec) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, applicationSpec)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s", + Entitlements.getEntitlementContext().user(), applicationSpec); + } + + checkApplicationTypesAreValid(applicationSpec); + checkLocationsAreValid(applicationSpec); + // TODO duplicate prevention + List<Location> locations = brooklyn().getLocations(applicationSpec); + Application app = brooklyn().create(applicationSpec); + Task<?> t = brooklyn().start(app, locations); + TaskSummary ts = TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t); + URI ref = serviceAbsoluteUriBuilder(uriInfo.getBaseUriBuilder(), ApplicationApi.class, "get") + .build(app.getApplicationId()); + return created(ref).entity(ts).build(); + } + + @Override + public Response createFromYaml(String yaml) { + // First of all, see if it's a URL + URI uri; + try { + uri = new URI(yaml); + } catch (URISyntaxException e) { + // It's not a URI then... + uri = null; + } + if (uri != null) { + log.debug("Create app called with URI; retrieving contents: {}", uri); + yaml = ResourceUtils.create(mgmt()).getResourceAsString(uri.toString()); + } + + log.debug("Creating app from yaml:\n{}", yaml); + EntitySpec<? extends Application> spec = createEntitySpecForApplication(yaml); + + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s", + Entitlements.getEntitlementContext().user(), yaml); + } + + return launch(yaml, spec); + } + + private Response launch(String yaml, EntitySpec<? extends Application> spec) { + try { + Application app = EntityManagementUtils.createUnstarted(mgmt(), spec); + CreationResult<Application,Void> result = EntityManagementUtils.start(app); + + boolean isEntitled = Entitlements.isEntitled( + mgmt().getEntitlementManager(), + Entitlements.INVOKE_EFFECTOR, + EntityAndItem.of(app, StringAndArgument.of(Startable.START.getName(), null))); + + if (!isEntitled) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s", + Entitlements.getEntitlementContext().user(), spec.getType()); + } + + log.info("Launched from YAML: " + yaml + " -> " + app + " (" + result.task() + ")"); + + URI ref = serviceAbsoluteUriBuilder(ui.getBaseUriBuilder(), ApplicationApi.class, "get").build(app.getApplicationId()); + ResponseBuilder response = created(ref); + if (result.task() != null) + response.entity(TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(result.task())); + return response.build(); + } catch (ConstraintViolationException e) { + throw new UserFacingException(e); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + @Override + public Response createPoly(byte[] inputToAutodetectType) { + log.debug("Creating app from autodetecting input"); + + boolean looksLikeLegacy = false; + Exception legacyFormatException = null; + // attempt legacy format + try { + ApplicationSpec appSpec = mapper().readValue(inputToAutodetectType, ApplicationSpec.class); + if (appSpec.getType() != null || appSpec.getEntities() != null) { + looksLikeLegacy = true; + } + return createFromAppSpec(appSpec); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + legacyFormatException = e; + log.debug("Input is not legacy ApplicationSpec JSON (will try others): "+e, e); + } + + //TODO infer encoding from request + String potentialYaml = new String(inputToAutodetectType); + EntitySpec<? extends Application> spec = createEntitySpecForApplication(potentialYaml); + + // TODO not json - try ZIP, etc + + if (spec != null) { + return launch(potentialYaml, spec); + } else if (looksLikeLegacy) { + throw Throwables.propagate(legacyFormatException); + } else { + return Response.serverError().entity("Unsupported format; not able to autodetect.").build(); + } + } + + @Override + public Response createFromForm(String contents) { + log.debug("Creating app from form"); + return createPoly(contents.getBytes()); + } + + @Override + public Response delete(String application) { + Application app = brooklyn().getApplication(application); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, Entitlements.EntityAndItem.of(app, + StringAndArgument.of(Entitlements.LifecycleEffectors.DELETE, null)))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to delete application %s", + Entitlements.getEntitlementContext().user(), app); + } + Task<?> t = brooklyn().destroy(app); + TaskSummary ts = TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t); + return status(ACCEPTED).entity(ts).build(); + } + + private EntitySpec<? extends Application> createEntitySpecForApplication(String potentialYaml) { + try { + return EntityManagementUtils.createEntitySpecForApplication(mgmt(), potentialYaml); + } catch (Exception e) { + // An IllegalArgumentException for creating the entity spec gets wrapped in a ISE, and possibly a Compound. + // But we want to return a 400 rather than 500, so ensure we throw IAE. + IllegalArgumentException iae = (IllegalArgumentException) Exceptions.getFirstThrowableOfType(e, IllegalArgumentException.class); + if (iae != null) { + throw new IllegalArgumentException("Cannot create spec for app: "+iae.getMessage(), e); + } else { + throw Exceptions.propagate(e); + } + } + } + + private void checkApplicationTypesAreValid(ApplicationSpec applicationSpec) { + String appType = applicationSpec.getType(); + if (appType != null) { + checkEntityTypeIsValid(appType); + + if (applicationSpec.getEntities() != null) { + throw WebResourceUtils.preconditionFailed("Application given explicit type '%s' must not define entities", appType); + } + return; + } + + for (org.apache.brooklyn.rest.domain.EntitySpec entitySpec : applicationSpec.getEntities()) { + String entityType = entitySpec.getType(); + checkEntityTypeIsValid(checkNotNull(entityType, "entityType")); + } + } + + private void checkSpecTypeIsValid(String type, Class<? extends BrooklynObject> subType) { + Maybe<RegisteredType> typeV = RegisteredTypes.tryValidate(mgmt().getTypeRegistry().get(type), RegisteredTypeLoadingContexts.spec(subType)); + if (!typeV.isNull()) { + // found, throw if any problem + typeV.get(); + return; + } + + // not found, try classloading + try { + brooklyn().getCatalogClassLoader().loadClass(type); + } catch (ClassNotFoundException e) { + log.debug("Class not found for type '" + type + "'; reporting 404", e); + throw WebResourceUtils.notFound("Undefined type '%s'", type); + } + log.info(JavaClassNames.simpleClassName(subType)+" type '{}' not defined in catalog but is on classpath; continuing", type); + } + + private void checkEntityTypeIsValid(String type) { + checkSpecTypeIsValid(type, Entity.class); + } + + @SuppressWarnings("deprecation") + private void checkLocationsAreValid(ApplicationSpec applicationSpec) { + for (String locationId : applicationSpec.getLocations()) { + locationId = BrooklynRestResourceUtils.fixLocation(locationId); + if (!brooklyn().getLocationRegistry().canMaybeResolve(locationId) && brooklyn().getLocationRegistry().getDefinedLocationById(locationId)==null) { + throw WebResourceUtils.notFound("Undefined location '%s'", locationId); + } + } + } + + @Override + public List<EntitySummary> getDescendants(String application, String typeRegex) { + return EntityTransformer.entitySummaries(brooklyn().descendantsOfType(application, application, typeRegex), ui.getBaseUriBuilder()); + } + + @Override + public Map<String, Object> getDescendantsSensor(String application, String sensor, String typeRegex) { + Iterable<Entity> descs = brooklyn().descendantsOfType(application, application, typeRegex); + return getSensorMap(sensor, descs); + } + + public static Map<String, Object> getSensorMap(String sensor, Iterable<Entity> descs) { + if (Iterables.isEmpty(descs)) + return Collections.emptyMap(); + Map<String, Object> result = MutableMap.of(); + Iterator<Entity> di = descs.iterator(); + Sensor<?> s = null; + while (di.hasNext()) { + Entity potentialSource = di.next(); + s = potentialSource.getEntityType().getSensor(sensor); + if (s!=null) break; + } + if (s==null) + s = Sensors.newSensor(Object.class, sensor); + if (!(s instanceof AttributeSensor<?>)) { + log.warn("Cannot retrieve non-attribute sensor "+s+" for entities; returning empty map"); + return result; + } + for (Entity e: descs) { + Object v = null; + try { + v = e.getAttribute((AttributeSensor<?>)s); + } catch (Exception exc) { + Exceptions.propagateIfFatal(exc); + log.warn("Error retrieving sensor "+s+" for "+e+" (ignoring): "+exc); + } + if (v!=null) + result.put(e.getId(), v); + } + return result; + } + +}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java new file mode 100644 index 0000000..d7b206a --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java @@ -0,0 +1,509 @@ +/* + * 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.resources; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.policy.PolicySpec; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.catalog.CatalogPredicates; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; +import org.apache.brooklyn.core.catalog.internal.CatalogDto; +import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument; +import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.rest.api.CatalogApi; +import org.apache.brooklyn.rest.domain.ApiError; +import org.apache.brooklyn.rest.domain.CatalogEntitySummary; +import org.apache.brooklyn.rest.domain.CatalogItemSummary; +import org.apache.brooklyn.rest.domain.CatalogLocationSummary; +import org.apache.brooklyn.rest.domain.CatalogPolicySummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.CatalogTransformer; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.StringPredicates; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import javax.ws.rs.core.UriInfo; +import org.apache.brooklyn.util.guava.Maybe; + +@HaHotStateRequired +public class CatalogResource extends AbstractBrooklynRestResource implements CatalogApi { + + private static final Logger log = LoggerFactory.getLogger(CatalogResource.class); + + @SuppressWarnings("rawtypes") + private Function<CatalogItem, CatalogItemSummary> toCatalogItemSummary(final UriInfo ui) { + return new Function<CatalogItem, CatalogItemSummary>() { + @Override + public CatalogItemSummary apply(@Nullable CatalogItem input) { + return CatalogTransformer.catalogItemSummary(brooklyn(), input, ui.getBaseUriBuilder()); + } + }; + }; + + static Set<String> missingIcons = MutableSet.of(); + + @Override + public Response create(String yaml) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog item", + Entitlements.getEntitlementContext().user()); + } + + Iterable<? extends CatalogItem<?, ?>> items; + try { + items = brooklyn().getCatalog().addItems(yaml); + } catch (IllegalArgumentException e) { + return Response.status(Status.BAD_REQUEST) + .type(MediaType.APPLICATION_JSON) + .entity(ApiError.of(e)) + .build(); + } + + log.info("REST created catalog items: "+items); + + Map<String,Object> result = MutableMap.of(); + + for (CatalogItem<?,?> item: items) { + result.put(item.getId(), CatalogTransformer.catalogItemSummary(brooklyn(), item, ui.getBaseUriBuilder())); + } + return Response.status(Status.CREATED).entity(result).build(); + } + + @SuppressWarnings("deprecation") + @Override + public Response resetXml(String xml, boolean ignoreErrors) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, null) || + !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, null)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + + ((BasicBrooklynCatalog)mgmt().getCatalog()).reset(CatalogDto.newDtoFromXmlContents(xml, "REST reset"), !ignoreErrors); + return Response.ok().build(); + } + + @Override + @Deprecated + public void deleteEntity_0_7_0(String entityId) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(entityId, "delete"))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + try { + Maybe<RegisteredType> item = RegisteredTypes.tryValidate(mgmt().getTypeRegistry().get(entityId), RegisteredTypeLoadingContexts.spec(Entity.class)); + if (item.isNull()) { + throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId); + } + if (item.isAbsent()) { + throw WebResourceUtils.notFound("Item with id '%s' is not an entity", entityId); + } + + brooklyn().getCatalog().deleteCatalogItem(item.get().getSymbolicName(), item.get().getVersion()); + + } catch (NoSuchElementException e) { + // shouldn't come here + throw WebResourceUtils.notFound("Entity with id '%s' could not be deleted", entityId); + + } + } + + @Override + public void deleteApplication(String symbolicName, String version) throws Exception { + deleteEntity(symbolicName, version); + } + + @Override + public void deleteEntity(String symbolicName, String version) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(symbolicName+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + + RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, version); + if (item == null) { + throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version); + } else if (!RegisteredTypePredicates.IS_ENTITY.apply(item) && !RegisteredTypePredicates.IS_APPLICATION.apply(item)) { + throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not an entity", symbolicName, version); + } else { + brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); + } + } + + @Override + public void deletePolicy(String policyId, String version) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(policyId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + + RegisteredType item = mgmt().getTypeRegistry().get(policyId, version); + if (item == null) { + throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version); + } else if (!RegisteredTypePredicates.IS_POLICY.apply(item)) { + throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a policy", policyId, version); + } else { + brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); + } + } + + @Override + public void deleteLocation(String locationId, String version) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(locationId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + + RegisteredType item = mgmt().getTypeRegistry().get(locationId, version); + if (item == null) { + throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version); + } else if (!RegisteredTypePredicates.IS_LOCATION.apply(item)) { + throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a location", locationId, version); + } else { + brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); + } + } + + @Override + public List<CatalogEntitySummary> listEntities(String regex, String fragment, boolean allVersions) { + Predicate<CatalogItem<Entity, EntitySpec<?>>> filter = + Predicates.and( + CatalogPredicates.IS_ENTITY, + CatalogPredicates.<Entity, EntitySpec<?>>disabled(false)); + List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); + return castList(result, CatalogEntitySummary.class); + } + + @Override + public List<CatalogItemSummary> listApplications(String regex, String fragment, boolean allVersions) { + @SuppressWarnings("unchecked") + Predicate<CatalogItem<Application, EntitySpec<? extends Application>>> filter = + Predicates.and( + CatalogPredicates.IS_TEMPLATE, + CatalogPredicates.<Application,EntitySpec<? extends Application>>deprecated(false), + CatalogPredicates.<Application,EntitySpec<? extends Application>>disabled(false)); + return getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); + } + + @Override + @Deprecated + public CatalogEntitySummary getEntity_0_7_0(String entityId) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, entityId)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + CatalogItem<Entity,EntitySpec<?>> result = + CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Entity.class, entityId); + + if (result==null) { + throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId); + } + + return CatalogTransformer.catalogEntitySummary(brooklyn(), result, ui.getBaseUriBuilder()); + } + + @Override + public CatalogEntitySummary getEntity(String symbolicName, String version) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, symbolicName+(Strings.isBlank(version)?"":":"+version))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + //TODO These casts are not pretty, we could just provide separate get methods for the different types? + //Or we could provide asEntity/asPolicy cast methods on the CataloItem doing a safety check internally + @SuppressWarnings("unchecked") + CatalogItem<Entity, EntitySpec<?>> result = + (CatalogItem<Entity, EntitySpec<?>>) brooklyn().getCatalog().getCatalogItem(symbolicName, version); + + if (result==null) { + throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version); + } + + return CatalogTransformer.catalogEntitySummary(brooklyn(), result, ui.getBaseUriBuilder()); + } + + @Override + @Deprecated + public CatalogEntitySummary getApplication_0_7_0(String applicationId) throws Exception { + return getEntity_0_7_0(applicationId); + } + + @Override + public CatalogEntitySummary getApplication(String symbolicName, String version) { + return getEntity(symbolicName, version); + } + + @Override + public List<CatalogPolicySummary> listPolicies(String regex, String fragment, boolean allVersions) { + Predicate<CatalogItem<Policy, PolicySpec<?>>> filter = + Predicates.and( + CatalogPredicates.IS_POLICY, + CatalogPredicates.<Policy, PolicySpec<?>>disabled(false)); + List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); + return castList(result, CatalogPolicySummary.class); + } + + @Override + @Deprecated + public CatalogPolicySummary getPolicy_0_7_0(String policyId) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + CatalogItem<? extends Policy, PolicySpec<?>> result = + CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Policy.class, policyId); + + if (result==null) { + throw WebResourceUtils.notFound("Policy with id '%s' not found", policyId); + } + + return CatalogTransformer.catalogPolicySummary(brooklyn(), result, ui.getBaseUriBuilder()); + } + + @Override + public CatalogPolicySummary getPolicy(String policyId, String version) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId+(Strings.isBlank(version)?"":":"+version))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + @SuppressWarnings("unchecked") + CatalogItem<? extends Policy, PolicySpec<?>> result = + (CatalogItem<? extends Policy, PolicySpec<?>>)brooklyn().getCatalog().getCatalogItem(policyId, version); + + if (result==null) { + throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version); + } + + return CatalogTransformer.catalogPolicySummary(brooklyn(), result, ui.getBaseUriBuilder()); + } + + @Override + public List<CatalogLocationSummary> listLocations(String regex, String fragment, boolean allVersions) { + Predicate<CatalogItem<Location, LocationSpec<?>>> filter = + Predicates.and( + CatalogPredicates.IS_LOCATION, + CatalogPredicates.<Location, LocationSpec<?>>disabled(false)); + List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); + return castList(result, CatalogLocationSummary.class); + } + + @Override + @Deprecated + public CatalogLocationSummary getLocation_0_7_0(String locationId) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + CatalogItem<? extends Location, LocationSpec<?>> result = + CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Location.class, locationId); + + if (result==null) { + throw WebResourceUtils.notFound("Location with id '%s' not found", locationId); + } + + return CatalogTransformer.catalogLocationSummary(brooklyn(), result, ui.getBaseUriBuilder()); + } + + @Override + public CatalogLocationSummary getLocation(String locationId, String version) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId+(Strings.isBlank(version)?"":":"+version))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + @SuppressWarnings("unchecked") + CatalogItem<? extends Location, LocationSpec<?>> result = + (CatalogItem<? extends Location, LocationSpec<?>>)brooklyn().getCatalog().getCatalogItem(locationId, version); + + if (result==null) { + throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version); + } + + return CatalogTransformer.catalogLocationSummary(brooklyn(), result, ui.getBaseUriBuilder()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private <T,SpecT> List<CatalogItemSummary> getCatalogItemSummariesMatchingRegexFragment(Predicate<CatalogItem<T,SpecT>> type, String regex, String fragment, boolean allVersions) { + List filters = new ArrayList(); + filters.add(type); + if (Strings.isNonEmpty(regex)) + filters.add(CatalogPredicates.xml(StringPredicates.containsRegex(regex))); + if (Strings.isNonEmpty(fragment)) + filters.add(CatalogPredicates.xml(StringPredicates.containsLiteralIgnoreCase(fragment))); + if (!allVersions) + filters.add(CatalogPredicates.isBestVersion(mgmt())); + + filters.add(CatalogPredicates.entitledToSee(mgmt())); + + ImmutableList<CatalogItem<Object, Object>> sortedItems = + FluentIterable.from(brooklyn().getCatalog().getCatalogItems()) + .filter(Predicates.and(filters)) + .toSortedList(CatalogItemComparator.getInstance()); + return Lists.transform(sortedItems, toCatalogItemSummary(ui)); + } + + @Override + @Deprecated + public Response getIcon_0_7_0(String itemId) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, itemId)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + return getCatalogItemIcon( mgmt().getTypeRegistry().get(itemId) ); + } + + @Override + public Response getIcon(String itemId, String version) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, itemId+(Strings.isBlank(version)?"":":"+version))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + return getCatalogItemIcon(mgmt().getTypeRegistry().get(itemId, version)); + } + + @Override + public void setDeprecatedLegacy(String itemId, boolean deprecated) { + log.warn("Use of deprecated \"/catalog/entities/{itemId}/deprecated/{deprecated}\" for "+itemId + +"; use \"/catalog/entities/{itemId}/deprecated\" with request body"); + setDeprecated(itemId, deprecated); + } + + @SuppressWarnings("deprecation") + @Override + public void setDeprecated(String itemId, boolean deprecated) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(itemId, "deprecated"))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + CatalogUtils.setDeprecated(mgmt(), itemId, deprecated); + } + + @SuppressWarnings("deprecation") + @Override + public void setDisabled(String itemId, boolean disabled) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(itemId, "disabled"))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + CatalogUtils.setDisabled(mgmt(), itemId, disabled); + } + + private Response getCatalogItemIcon(RegisteredType result) { + String url = result.getIconUrl(); + if (url==null) { + log.debug("No icon available for "+result+"; returning "+Status.NO_CONTENT); + return Response.status(Status.NO_CONTENT).build(); + } + + if (brooklyn().isUrlServerSideAndSafe(url)) { + // classpath URL's we will serve IF they end with a recognised image format; + // paths (ie non-protocol) and + // NB, for security, file URL's are NOT served + log.debug("Loading and returning "+url+" as icon for "+result); + + MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url)); + try { + Object content = ResourceUtils.create(CatalogUtils.newClassLoadingContext(mgmt(), result)).getResourceFromUrl(url); + return Response.ok(content, mime).build(); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + synchronized (missingIcons) { + if (missingIcons.add(url)) { + // note: this can be quite common when running from an IDE, as resources may not be copied; + // a mvn build should sort it out (the IDE will then find the resources, until you clean or maybe refresh...) + log.warn("Missing icon data for "+result.getId()+", expected at: "+url+" (subsequent messages will log debug only)"); + log.debug("Trace for missing icon data at "+url+": "+e, e); + } else { + log.debug("Missing icon data for "+result.getId()+", expected at: "+url+" (already logged WARN and error details)"); + } + } + throw WebResourceUtils.notFound("Icon unavailable for %s", result.getId()); + } + } + + log.debug("Returning redirect to "+url+" as icon for "+result); + + // for anything else we do a redirect (e.g. http / https; perhaps ftp) + return Response.temporaryRedirect(URI.create(url)).build(); + } + + // TODO Move to an appropriate utility class? + @SuppressWarnings("unchecked") + private static <T> List<T> castList(List<? super T> list, Class<T> elementType) { + List<T> result = Lists.newArrayList(); + Iterator<? super T> li = list.iterator(); + while (li.hasNext()) { + try { + result.add((T) li.next()); + } catch (Throwable throwable) { + if (throwable instanceof NoClassDefFoundError) { + // happens if class cannot be loaded for any reason during transformation - don't treat as fatal + } else { + Exceptions.propagateIfFatal(throwable); + } + + // item cannot be transformed; we will have logged a warning earlier + log.debug("Ignoring invalid catalog item: "+throwable); + } + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java new file mode 100644 index 0000000..22661bd --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.rest.resources; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nullable; +import javax.ws.rs.core.Response; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument; +import org.apache.brooklyn.core.mgmt.internal.EffectorUtils; +import org.apache.brooklyn.rest.api.EffectorApi; +import org.apache.brooklyn.rest.domain.EffectorSummary; +import org.apache.brooklyn.rest.domain.SummaryComparators; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.EffectorTransformer; +import org.apache.brooklyn.rest.transform.TaskTransformer; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; + +@HaHotStateRequired +public class EffectorResource extends AbstractBrooklynRestResource implements EffectorApi { + + private static final Logger log = LoggerFactory.getLogger(EffectorResource.class); + + @Override + public List<EffectorSummary> list(final String application, final String entityToken) { + final Entity entity = brooklyn().getEntity(application, entityToken); + return FluentIterable + .from(entity.getEntityType().getEffectors()) + .filter(new Predicate<Effector<?>>() { + @Override + public boolean apply(@Nullable Effector<?> input) { + return Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, + Entitlements.EntityAndItem.of(entity, StringAndArgument.of(input.getName(), null))); + } + }) + .transform(new Function<Effector<?>, EffectorSummary>() { + @Override + public EffectorSummary apply(Effector<?> effector) { + return EffectorTransformer.effectorSummary(entity, effector, ui.getBaseUriBuilder()); + } + }) + .toSortedList(SummaryComparators.nameComparator()); + } + + @Override + public Response invoke(String application, String entityToken, String effectorName, + String timeout, Map<String, Object> parameters) { + final Entity entity = brooklyn().getEntity(application, entityToken); + + // TODO check effectors? + Maybe<Effector<?>> effector = EffectorUtils.findEffectorDeclared(entity, effectorName); + if (effector.isAbsentOrNull()) { + throw WebResourceUtils.notFound("Entity '%s' has no effector with name '%s'", entityToken, effectorName); + } else if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, + Entitlements.EntityAndItem.of(entity, StringAndArgument.of(effector.get().getName(), null)))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to invoke effector %s on entity %s", + Entitlements.getEntitlementContext().user(), effector.get().getName(), entity); + } + log.info("REST invocation of " + entity + "." + effector.get() + " " + parameters); + Task<?> t = entity.invoke(effector.get(), parameters); + + try { + Object result; + if (timeout == null || timeout.isEmpty() || "never".equalsIgnoreCase(timeout)) { + result = t.get(); + } else { + long timeoutMillis = "always".equalsIgnoreCase(timeout) ? 0 : Time.parseElapsedTime(timeout); + try { + if (timeoutMillis == 0) throw new TimeoutException(); + result = t.get(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + result = TaskTransformer.taskSummary(t, ui.getBaseUriBuilder()); + } + } + return Response.status(Response.Status.ACCEPTED).entity(result).build(); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java new file mode 100644 index 0000000..4c3c9d2 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java @@ -0,0 +1,206 @@ +/* + * 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.resources; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.BasicConfigKey; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem; +import org.apache.brooklyn.rest.api.EntityConfigApi; +import org.apache.brooklyn.rest.domain.EntityConfigSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.EntityTransformer; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.core.task.ValueResolver; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Predicates; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +@HaHotStateRequired +public class EntityConfigResource extends AbstractBrooklynRestResource implements EntityConfigApi { + + private static final Logger LOG = LoggerFactory.getLogger(EntityConfigResource.class); + + @Override + public List<EntityConfigSummary> list(final String application, final String entityToken) { + final Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + // TODO merge with keys which have values: + // ((EntityInternal) entity).config().getBag().getAllConfigAsConfigKeyMap(); + List<EntityConfigSummary> result = Lists.newArrayList(); + + for (ConfigKey<?> key : entity.getEntityType().getConfigKeys()) { + // Exclude config that user is not allowed to see + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, key.getName()))) { + LOG.trace("User {} not authorized to see config {} of entity {}; excluding from ConfigKey list results", + new Object[] {Entitlements.getEntitlementContext().user(), key.getName(), entity}); + continue; + } + result.add(EntityTransformer.entityConfigSummary(entity, key, ui.getBaseUriBuilder())); + } + + return result; + } + + // TODO support parameters ?show=value,summary&name=xxx &format={string,json,xml} + // (and in sensors class) + @Override + public Map<String, Object> batchConfigRead(String application, String entityToken, Boolean raw) { + // TODO: add test + Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + // wrap in a task for better runtime view + return Entities.submit(entity, Tasks.<Map<String,Object>>builder().displayName("REST API batch config read").body(new BatchConfigRead(mgmt(), this, entity, raw)).build()).getUnchecked(); + } + + private static class BatchConfigRead implements Callable<Map<String,Object>> { + private final ManagementContext mgmt; + private final EntityConfigResource resource; + private final Entity entity; + private final Boolean raw; + + public BatchConfigRead(ManagementContext mgmt, EntityConfigResource resource, Entity entity, Boolean raw) { + this.mgmt = mgmt; + this.resource = resource; + this.entity = entity; + this.raw = raw; + } + + @Override + public Map<String, Object> call() throws Exception { + Map<ConfigKey<?>, ?> source = ((EntityInternal) entity).config().getBag().getAllConfigAsConfigKeyMap(); + Map<String, Object> result = Maps.newLinkedHashMap(); + for (Map.Entry<ConfigKey<?>, ?> ek : source.entrySet()) { + ConfigKey<?> key = ek.getKey(); + Object value = ek.getValue(); + + // Exclude config that user is not allowed to see + if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, ek.getKey().getName()))) { + LOG.trace("User {} not authorized to see sensor {} of entity {}; excluding from current-state results", + new Object[] {Entitlements.getEntitlementContext().user(), ek.getKey().getName(), entity}); + continue; + } + result.put(key.getName(), + resource.resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(key).resolve()); + } + return result; + } + } + + @Override + public Object get(String application, String entityToken, String configKeyName, Boolean raw) { + return get(true, application, entityToken, configKeyName, raw); + } + + @Override + public String getPlain(String application, String entityToken, String configKeyName, Boolean raw) { + return Strings.toString(get(false, application, entityToken, configKeyName, raw)); + } + + public Object get(boolean preferJson, String application, String entityToken, String configKeyName, Boolean raw) { + Entity entity = brooklyn().getEntity(application, entityToken); + ConfigKey<?> ck = findConfig(entity, configKeyName); + + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, ck.getName()))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s' config '%s'", + Entitlements.getEntitlementContext().user(), entity, ck.getName()); + } + + Object value = ((EntityInternal)entity).config().getRaw(ck).orNull(); + return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).raw(raw).context(entity).timeout(ValueResolver.PRETTY_QUICK_WAIT).renderAs(ck).resolve(); + } + + private ConfigKey<?> findConfig(Entity entity, String configKeyName) { + ConfigKey<?> ck = entity.getEntityType().getConfigKey(configKeyName); + if (ck == null) + ck = new BasicConfigKey<Object>(Object.class, configKeyName); + return ck; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void setFromMap(String application, String entityToken, Boolean recurse, Map newValues) { + final Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + if (LOG.isDebugEnabled()) + LOG.debug("REST user " + Entitlements.getEntitlementContext() + " setting configs " + newValues); + for (Object entry : newValues.entrySet()) { + String configName = Strings.toString(((Map.Entry) entry).getKey()); + Object newValue = ((Map.Entry) entry).getValue(); + + ConfigKey ck = findConfig(entity, configName); + ((EntityInternal) entity).config().set(ck, TypeCoercions.coerce(newValue, ck.getTypeToken())); + if (Boolean.TRUE.equals(recurse)) { + for (Entity e2 : Entities.descendants(entity, Predicates.alwaysTrue(), false)) { + ((EntityInternal) e2).config().set(ck, newValue); + } + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void set(String application, String entityToken, String configName, Boolean recurse, Object newValue) { + final Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + ConfigKey ck = findConfig(entity, configName); + LOG.debug("REST setting config " + configName + " on " + entity + " to " + newValue); + ((EntityInternal) entity).config().set(ck, TypeCoercions.coerce(newValue, ck.getTypeToken())); + if (Boolean.TRUE.equals(recurse)) { + for (Entity e2 : Entities.descendants(entity, Predicates.alwaysTrue(), false)) { + ((EntityInternal) e2).config().set(ck, newValue); + } + } + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java new file mode 100644 index 0000000..4d263da --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java @@ -0,0 +1,223 @@ +/* + * 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.resources; + +import static javax.ws.rs.core.Response.created; +import static javax.ws.rs.core.Response.status; +import static javax.ws.rs.core.Response.Status.ACCEPTED; + +import java.net.URI; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.core.mgmt.BrooklynTags; +import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag; +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.core.mgmt.EntityManagementUtils; +import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult; +import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.rest.api.EntityApi; +import org.apache.brooklyn.rest.domain.EntitySummary; +import org.apache.brooklyn.rest.domain.LocationSummary; +import org.apache.brooklyn.rest.domain.TaskSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.EntityTransformer; +import org.apache.brooklyn.rest.transform.LocationTransformer; +import org.apache.brooklyn.rest.transform.LocationTransformer.LocationDetailLevel; +import org.apache.brooklyn.rest.transform.TaskTransformer; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Collections2; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder; + +@HaHotStateRequired +public class EntityResource extends AbstractBrooklynRestResource implements EntityApi { + + private static final Logger log = LoggerFactory.getLogger(EntityResource.class); + + @Context + private UriInfo uriInfo; + + @Override + public List<EntitySummary> list(final String application) { + return FluentIterable + .from(brooklyn().getApplication(application).getChildren()) + .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY)) + .transform(EntityTransformer.fromEntity(ui.getBaseUriBuilder())) + .toList(); + } + + @Override + public EntitySummary get(String application, String entityName) { + Entity entity = brooklyn().getEntity(application, entityName); + if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + return EntityTransformer.entitySummary(entity, ui.getBaseUriBuilder()); + } + throw WebResourceUtils.forbidden("User '%s' is not authorized to get entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + @Override + public List<EntitySummary> getChildren(final String application, final String entity) { + return FluentIterable + .from(brooklyn().getEntity(application, entity).getChildren()) + .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY)) + .transform(EntityTransformer.fromEntity(ui.getBaseUriBuilder())) + .toList(); + } + + @Override + public List<EntitySummary> getChildrenOld(String application, String entity) { + log.warn("Using deprecated call to /entities when /children should be used"); + return getChildren(application, entity); + } + + @Override + public Response addChildren(String applicationToken, String entityToken, Boolean start, String timeoutS, String yaml) { + final Entity parent = brooklyn().getEntity(applicationToken, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, parent)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'", + Entitlements.getEntitlementContext().user(), entityToken); + } + CreationResult<List<Entity>, List<String>> added = EntityManagementUtils.addChildren(parent, yaml, start) + .blockUntilComplete(timeoutS==null ? Duration.millis(20) : Duration.of(timeoutS)); + ResponseBuilder response; + + if (added.get().size()==1) { + Entity child = Iterables.getOnlyElement(added.get()); + URI ref = serviceAbsoluteUriBuilder(uriInfo.getBaseUriBuilder(), EntityApi.class, "get") + .build(child.getApplicationId(), child.getId()); + response = created(ref); + } else { + response = Response.status(Status.CREATED); + } + return response.entity(TaskTransformer.taskSummary(added.task(), ui.getBaseUriBuilder())).build(); + } + + @Override + public List<TaskSummary> listTasks(String applicationId, String entityId) { + Entity entity = brooklyn().getEntity(applicationId, entityId); + Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(mgmt().getExecutionManager(), entity); + return new LinkedList<TaskSummary>(Collections2.transform(tasks, + TaskTransformer.fromTask(ui.getBaseUriBuilder()))); + } + + @Override + public TaskSummary getTask(final String application, final String entityToken, String taskId) { + // TODO deprecate in favour of ActivityApi.get ? + Task<?> t = mgmt().getExecutionManager().getTask(taskId); + if (t == null) + throw WebResourceUtils.notFound("Cannot find task '%s'", taskId); + return TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t); + } + + @SuppressWarnings("unchecked") + @Override + public List<Object> listTags(String applicationId, String entityId) { + Entity entity = brooklyn().getEntity(applicationId, entityId); + return (List<Object>) resolving(MutableList.copyOf(entity.tags().getTags())).preferJson(true).resolve(); + } + + @Override + public Response getIcon(String applicationId, String entityId) { + Entity entity = brooklyn().getEntity(applicationId, entityId); + String url = entity.getIconUrl(); + if (url == null) + return Response.status(Status.NO_CONTENT).build(); + + if (brooklyn().isUrlServerSideAndSafe(url)) { + // classpath URL's we will serve IF they end with a recognised image format; + // paths (ie non-protocol) and + // NB, for security, file URL's are NOT served + MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url)); + Object content = ResourceUtils.create(brooklyn().getCatalogClassLoader()).getResourceFromUrl(url); + return Response.ok(content, mime).build(); + } + + // for anything else we do a redirect (e.g. http / https; perhaps ftp) + return Response.temporaryRedirect(URI.create(url)).build(); + } + + @Override + public Response rename(String application, String entity, String newName) { + Entity entityLocal = brooklyn().getEntity(application, entity); + entityLocal.setDisplayName(newName); + return status(Response.Status.OK).build(); + } + + @Override + public Response expunge(String application, String entity, boolean release) { + Entity entityLocal = brooklyn().getEntity(application, entity); + Task<?> task = brooklyn().expunge(entityLocal, release); + TaskSummary summary = TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(task); + return status(ACCEPTED).entity(summary).build(); + } + + @Override + public List<EntitySummary> getDescendants(String application, String entity, String typeRegex) { + return EntityTransformer.entitySummaries(brooklyn().descendantsOfType(application, entity, typeRegex), ui.getBaseUriBuilder()); + } + + @Override + public Map<String, Object> getDescendantsSensor(String application, String entity, String sensor, String typeRegex) { + Iterable<Entity> descs = brooklyn().descendantsOfType(application, entity, typeRegex); + return ApplicationResource.getSensorMap(sensor, descs); + } + + @Override + public List<LocationSummary> getLocations(String application, String entity) { + List<LocationSummary> result = Lists.newArrayList(); + Entity e = brooklyn().getEntity(application, entity); + for (Location l : e.getLocations()) { + result.add(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.NONE, ui.getBaseUriBuilder())); + } + return result; + } + + @Override + public String getSpec(String applicationToken, String entityToken) { + Entity entity = brooklyn().getEntity(applicationToken, entityToken); + NamedStringTag spec = BrooklynTags.findFirst(BrooklynTags.YAML_SPEC_KIND, entity.tags().getTags()); + if (spec == null) + return null; + return (String) WebResourceUtils.getValueForDisplay(spec.getContents(), true, true); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java new file mode 100644 index 0000000..8f4251c --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java @@ -0,0 +1,186 @@ +/* + * 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.resources; + +import java.net.URI; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.Response; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationDefinition; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.location.LocationConfigKeys; +import org.apache.brooklyn.rest.api.LocationApi; +import org.apache.brooklyn.rest.domain.LocationSpec; +import org.apache.brooklyn.rest.domain.LocationSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.LocationTransformer; +import org.apache.brooklyn.rest.transform.LocationTransformer.LocationDetailLevel; +import org.apache.brooklyn.rest.util.EntityLocationUtils; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.NaturalOrderComparator; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder; + +@SuppressWarnings("deprecation") +@HaHotStateRequired +public class LocationResource extends AbstractBrooklynRestResource implements LocationApi { + + private static final Logger log = LoggerFactory.getLogger(LocationResource.class); + + private final Set<String> specsWarnedOnException = Sets.newConcurrentHashSet(); + + @Override + public List<LocationSummary> list() { + Function<LocationDefinition, LocationSummary> transformer = new Function<LocationDefinition, LocationSummary>() { + @Override + public LocationSummary apply(LocationDefinition l) { + try { + return LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ui.getBaseUriBuilder()); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + String spec = l.getSpec(); + if (spec == null || specsWarnedOnException.add(spec)) { + log.warn("Unable to find details of location {} in REST call to list (ignoring location): {}", l, e); + if (log.isDebugEnabled()) log.debug("Error details for location " + l, e); + } else { + if (log.isTraceEnabled()) + log.trace("Unable again to find details of location {} in REST call to list (ignoring location): {}", l, e); + } + return null; + } + } + }; + return FluentIterable.from(brooklyn().getLocationRegistry().getDefinedLocations().values()) + .transform(transformer) + .filter(LocationSummary.class) + .toSortedList(nameOrSpecComparator()); + } + + private static NaturalOrderComparator COMPARATOR = new NaturalOrderComparator(); + private static Comparator<LocationSummary> nameOrSpecComparator() { + return new Comparator<LocationSummary>() { + @Override + public int compare(LocationSummary o1, LocationSummary o2) { + return COMPARATOR.compare(getNameOrSpec(o1).toLowerCase(), getNameOrSpec(o2).toLowerCase()); + } + }; + } + private static String getNameOrSpec(LocationSummary o) { + if (Strings.isNonBlank(o.getName())) return o.getName(); + if (Strings.isNonBlank(o.getSpec())) return o.getSpec(); + return o.getId(); + } + + // this is here to support the web GUI's circles + @Override + public Map<String,Map<String,Object>> getLocatedLocations() { + Map<String,Map<String,Object>> result = new LinkedHashMap<String,Map<String,Object>>(); + Map<Location, Integer> counts = new EntityLocationUtils(mgmt()).countLeafEntitiesByLocatedLocations(); + for (Map.Entry<Location,Integer> count: counts.entrySet()) { + Location l = count.getKey(); + Map<String,Object> m = MutableMap.<String,Object>of( + "id", l.getId(), + "name", l.getDisplayName(), + "leafEntityCount", count.getValue(), + "latitude", l.getConfig(LocationConfigKeys.LATITUDE), + "longitude", l.getConfig(LocationConfigKeys.LONGITUDE) + ); + result.put(l.getId(), m); + } + return result; + } + + /** @deprecated since 0.7.0; REST call now handled by below (optional query parameter added) */ + public LocationSummary get(String locationId) { + return get(locationId, false); + } + + @Override + public LocationSummary get(String locationId, String fullConfig) { + return get(locationId, Boolean.valueOf(fullConfig)); + } + + public LocationSummary get(String locationId, boolean fullConfig) { + LocationDetailLevel configLevel = fullConfig ? LocationDetailLevel.FULL_EXCLUDING_SECRET : LocationDetailLevel.LOCAL_EXCLUDING_SECRET; + Location l1 = mgmt().getLocationManager().getLocation(locationId); + if (l1!=null) { + return LocationTransformer.newInstance(mgmt(), l1, configLevel, ui.getBaseUriBuilder()); + } + + LocationDefinition l2 = brooklyn().getLocationRegistry().getDefinedLocationById(locationId); + if (l2==null) throw WebResourceUtils.notFound("No location matching %s", locationId); + return LocationTransformer.newInstance(mgmt(), l2, configLevel, ui.getBaseUriBuilder()); + } + + @Override + public Response create(LocationSpec locationSpec) { + String name = locationSpec.getName(); + ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add( + "brooklyn.catalog:", + " symbolicName: "+name, + "", + "brooklyn.locations:", + "- type: "+locationSpec.getSpec()); + if (locationSpec.getConfig().size() > 0) { + yaml.add(" brooklyn.config:"); + for (Map.Entry<String, ?> entry : locationSpec.getConfig().entrySet()) { + yaml.add(" " + entry.getKey() + ": " + entry.getValue()); + } + } + + String locationBlueprint = Joiner.on("\n").join(yaml.build()); + brooklyn().getCatalog().addItems(locationBlueprint); + LocationDefinition l = brooklyn().getLocationRegistry().getDefinedLocationByName(name); + URI ref = serviceAbsoluteUriBuilder(ui.getBaseUriBuilder(), LocationApi.class, "get").build(name); + return Response.created(ref) + .entity(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ui.getBaseUriBuilder())) + .build(); + } + + @Override + @Deprecated + public void delete(String locationId) { + // TODO make all locations be part of the catalog, then flip the JS GUI to use catalog api + if (deleteAllVersions(locationId)>0) return; + throw WebResourceUtils.notFound("No catalog item location matching %s; only catalog item locations can be deleted", locationId); + } + + private int deleteAllVersions(String locationId) { + RegisteredType item = mgmt().getTypeRegistry().get(locationId); + if (item==null) return 0; + brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); + return 1 + deleteAllVersions(locationId); + } +}