| /* |
| * 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 org.apache.brooklyn.core.entity.EntityInternal; |
| import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecSummary; |
| import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder; |
| |
| import java.net.URI; |
| import java.util.*; |
| |
| 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 com.google.common.collect.*; |
| 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.core.typereg.RegisteredTypes; |
| import org.apache.brooklyn.rest.api.EntityApi; |
| import org.apache.brooklyn.rest.domain.*; |
| 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.EntityRelationUtils; |
| 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.core.task.ScheduledTask; |
| import org.apache.brooklyn.util.time.Duration; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.annotations.Beta; |
| import com.google.common.base.Objects; |
| import com.google.common.io.Files; |
| |
| @HaHotStateRequired |
| public class EntityResource extends AbstractBrooklynRestResource implements EntityApi { |
| |
| @SuppressWarnings("unused") |
| 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<RelationSummary> getRelations(final String applicationId, final String entityId) { |
| Entity entity = brooklyn().getEntity(applicationId, entityId); |
| if (entity != null) { |
| return EntityRelationUtils.getRelations(entity); |
| } |
| return Collections.emptyList(); |
| } |
| |
| @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, int limit, Boolean recurse) { |
| Entity entity = brooklyn().getEntity(applicationId, entityId); |
| return TaskTransformer.fromTasks(MutableList.copyOf(BrooklynTaskTags.getTasksInEntityContext(mgmt().getExecutionManager(), entity)), |
| limit, recurse, entity, ui); |
| } |
| |
| /** API does not guarantee order, but this is a the one we use (when there are lots of tasks): |
| * prefer top-level tasks and to recent tasks, |
| * balanced such that the following are equal: |
| * <li>something manually submitted here, submitted two hours ago |
| * <li>something submitted from another entity, submitted ten minutes ago |
| * <li>anything in progress, submitted one minute ago |
| * <li>anything not started, submitted ten seconds ago |
| * <li>anything completed, submitted one second ago |
| * <p> |
| * So if there was a manual "foo" effector invoked via REST on this entity a day ago, |
| * a "bar" effector invoked from a parent effector would only be preferred |
| * if invoked in the last two hours; |
| * active subtasks of "bar" would be preferred if submitted within the last 12 minutes, |
| * unstarted subtasks if submitted within 2 minutes, |
| * and completed subtasks if within the last 12 seconds. |
| * Thus there is a heavy bias over time to show the top-level tasks, |
| * but there is also a bias for detail for very recent activity. |
| * <p> |
| * It's far from perfect but provides a way -- when there are lots of tasks -- |
| * that we can show important things, where important things are the top level |
| * and very recent. |
| */ |
| @Beta |
| public static class InterestingTasksFirstComparator implements Comparator<Task<?>> { |
| Entity context; |
| public InterestingTasksFirstComparator() { this(null); } |
| public InterestingTasksFirstComparator(Entity entity) { this.context = entity; } |
| @Override |
| public int compare(Task<?> o1, Task<?> o2) { |
| // absolute pref for submitted items |
| if (!Objects.equal(o1.isSubmitted(), o2.isSubmitted())) { |
| return o1.isSubmitted() ? -1 : 1; |
| } |
| // followed by absolute pref for active items |
| if (!Objects.equal(o1.isDone(), o2.isDone())) { |
| return !o1.isDone() ? -1 : 1; |
| } |
| |
| // followed by absolute pref for things not started yet |
| if (!Objects.equal(o1.isBegun(), o2.isBegun())) { |
| return !o1.isBegun() ? -1 : 1; |
| } |
| |
| // we no longer do this analysis, but instead we first take at least one of each task with a distinct name |
| |
| // if (!o1.isDone()) { |
| // // among active items, scheduled ones |
| // if (!Objects.equal(o1 instanceof ScheduledTask, o2 instanceof ScheduledTask)) { |
| // return !(o1 instanceof ScheduledTask) ? -1 : 1; |
| // } |
| // } |
| |
| // // if all else is equal: |
| // // big pref for top-level tasks (manual operations), where submitter null, or submitted by other entities |
| // int weight = 0; |
| // Task<?> o1s = o1.getSubmittedByTask(); |
| // Task<?> o2s = o2.getSubmittedByTask(); |
| // if (!Objects.equal(o1s==null, o2s==null)) { |
| // weight += 20 * 60 * (o1s == null ? -1 : 1); |
| // } |
| // // then pref for things invoked by other entities |
| // if (context!=null && o1s!=null && o2s!=null) { |
| // boolean o1se = context.equals(BrooklynTaskTags.getContextEntity(o1s)); |
| // boolean o2se = context.equals(BrooklynTaskTags.getContextEntity(o2s)); |
| // if (!Objects.equal(o1se, o2se)) { |
| // weight += 10 * 60 * (o2se ? -1 : 1); |
| // } |
| // } |
| |
| // then sort based on how recently the task changed state |
| long now = System.currentTimeMillis(); |
| long t1 = o1.isDone() ? o1.getEndTimeUtc() : o1.isBegun() ? o1.getStartTimeUtc() : o1.getSubmitTimeUtc(); |
| long t2 = o2.isDone() ? o2.getEndTimeUtc() : o2.isBegun() ? o2.getStartTimeUtc() : o2.getSubmitTimeUtc(); |
| long u1 = now - t1; |
| long u2 = now - t2; |
| // // so smaller = more recent |
| // // and if there is a weight, increase the other side so it is de-emphasised |
| // // IE if weight was -10 that means T1 is "10 times more interesting" |
| // // or more precisely, a task T1 from 10 mins ago equals a task T2 from 1 min ago |
| // if (weight<0) u2 *= -weight; |
| // else if (weight>0) u1 *= weight; |
| if (u1!=u2) return u1 > u2 ? 1 : -1; |
| |
| // // if equal under mapping, use weight |
| // if (weight!=0) return weight < 0 ? -1 : 1; |
| |
| // lastly use ID to ensure canonical order |
| return o1.getId().compareTo(o2.getId()); |
| } |
| } |
| |
| @Override @Deprecated |
| public List<TaskSummary> listTasks(String applicationId, String entityId) { |
| return listTasks(applicationId, entityId, -1, false); |
| } |
| |
| @Override |
| public TaskSummary getTask(final String application, final String entityToken, String taskId) { |
| // TODO deprecate in favour of ActivityApi.get ? |
| Entity entity =brooklyn().getApplication(application); |
| if (entity != null && !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { |
| throw WebResourceUtils.forbidden("User '%s' is not authorized to see the task '%s' for the entity '%s'", |
| Entitlements.getEntitlementContext().user(), taskId, entity); |
| } |
| 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 void addTag(String applicationId, String entityId, Object tag) { |
| Entity entity = brooklyn().getEntity(applicationId, entityId); |
| entity.tags().addTag(tag); |
| } |
| |
| @Override |
| public boolean deleteTag(String applicationId, String entityId, Object tag) { |
| Entity entity = brooklyn().getEntity(applicationId, entityId); |
| return entity.tags().removeTag(tag); |
| } |
| |
| @Override |
| public void upsertTag(String applicationId, String entityId, String tagKey, Object tagValue) { |
| Entity entity = brooklyn().getEntity(applicationId, entityId); |
| BrooklynTags.upsertSingleKeyMapValueTag(entity.tags(), tagKey, tagValue); |
| } |
| |
| @Override |
| public Object getTag(String applicationId, String entityId, String tagKey) { |
| Entity entity = brooklyn().getEntity(applicationId, entityId); |
| return BrooklynTags.findSingleKeyMapValue(tagKey, Object.class, entity.tags().getTags()); |
| } |
| |
| @Override |
| public Response getIcon(String applicationId, String entityId) { |
| Entity entity = brooklyn().getEntity(applicationId, entityId); |
| String url = RegisteredTypes.getIconUrl(entity); |
| 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(entity).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 instance = brooklyn().getEntity(application, entity); |
| if (instance != null && !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.RENAME_ENTITY, instance)) { |
| throw WebResourceUtils.forbidden("User '%s' is not authorized to rename the entity '%s'", |
| Entitlements.getEntitlementContext().user(), entity); |
| } |
| instance.setDisplayName(newName); |
| return status(Response.Status.OK).build(); |
| } |
| |
| @Override |
| public Response expunge(String application, String entity, boolean release) { |
| Entity instance = brooklyn().getEntity(application, entity); |
| Task<?> task = brooklyn().expunge(instance, 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.findFirstNamedStringTag(BrooklynTags.YAML_SPEC_KIND, entity.tags().getTags()); |
| if (spec == null) |
| return null; |
| return (String) WebResourceUtils.getValueForDisplay(spec.getContents(), false, true); |
| } |
| |
| @Override |
| public List<Object> getSpecList(String applicationId, String entityId) { |
| Entity entity = brooklyn().getEntity(applicationId, entityId); |
| List<SpecSummary> specTag = BrooklynTags.findSpecHierarchyTag(entity.tags().getTags()); |
| return (List<Object>) resolving(specTag).preferJson(true).resolve(); |
| } |
| } |