Return-Path: X-Original-To: apmail-brooklyn-commits-archive@minotaur.apache.org Delivered-To: apmail-brooklyn-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 22E5D181D9 for ; Thu, 18 Feb 2016 15:47:21 +0000 (UTC) Received: (qmail 6523 invoked by uid 500); 18 Feb 2016 15:47:21 -0000 Delivered-To: apmail-brooklyn-commits-archive@brooklyn.apache.org Received: (qmail 6444 invoked by uid 500); 18 Feb 2016 15:47:21 -0000 Mailing-List: contact commits-help@brooklyn.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@brooklyn.apache.org Delivered-To: mailing list commits@brooklyn.apache.org Received: (qmail 5739 invoked by uid 99); 18 Feb 2016 15:47:20 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 18 Feb 2016 15:47:20 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id D41CEE0F7F; Thu, 18 Feb 2016 15:47:19 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: heneveld@apache.org To: commits@brooklyn.apache.org Date: Thu, 18 Feb 2016 15:47:34 -0000 Message-Id: <7e4af8520343428e91540f9b732ef556@git.apache.org> In-Reply-To: <185968724a0a448c81570330c9742d65@git.apache.org> References: <185968724a0a448c81570330c9742d65@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [16/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java new file mode 100644 index 0000000..a0795cb --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java @@ -0,0 +1,117 @@ +/* + * 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.security.provider; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringTokenizer; + +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.config.StringConfigMap; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.rest.BrooklynWebConfig; +import org.apache.brooklyn.rest.security.PasswordHasher; + +/** + * Security provider which validates users against passwords according to property keys, + * as set in {@link BrooklynWebConfig#USERS} and {@link BrooklynWebConfig#PASSWORD_FOR_USER(String)} + */ +public class ExplicitUsersSecurityProvider extends AbstractSecurityProvider implements SecurityProvider { + + public static final Logger LOG = LoggerFactory.getLogger(ExplicitUsersSecurityProvider.class); + + protected final ManagementContext mgmt; + private boolean allowAnyUserWithValidPass; + private Set allowedUsers = null; + + public ExplicitUsersSecurityProvider(ManagementContext mgmt) { + this.mgmt = mgmt; + initialize(); + } + + private synchronized void initialize() { + if (allowedUsers != null) return; + + StringConfigMap properties = mgmt.getConfig(); + + allowedUsers = new LinkedHashSet(); + String users = properties.getConfig(BrooklynWebConfig.USERS); + if (users == null) { + LOG.warn("REST has no users configured; no one will be able to log in!"); + } else if ("*".equals(users)) { + LOG.info("REST allowing any user (so long as valid password is set)"); + allowAnyUserWithValidPass = true; + } else { + StringTokenizer t = new StringTokenizer(users, ","); + while (t.hasMoreElements()) { + allowedUsers.add(("" + t.nextElement()).trim()); + } + LOG.info("REST allowing users: " + allowedUsers); + } + } + + @Override + public boolean authenticate(HttpSession session, String user, String password) { + if (session==null || user==null) return false; + + if (!allowAnyUserWithValidPass) { + if (!allowedUsers.contains(user)) { + LOG.debug("REST rejecting unknown user "+user); + return false; + } + } + + if (checkExplicitUserPassword(mgmt, user, password)) { + return allow(session, user); + } + return false; + } + + /** checks the supplied candidate user and password against the + * expect password (or SHA-256 + SALT thereof) defined as brooklyn properties. + */ + public static boolean checkExplicitUserPassword(ManagementContext mgmt, String user, String password) { + BrooklynProperties properties = (BrooklynProperties) mgmt.getConfig(); + String expectedPassword = properties.getConfig(BrooklynWebConfig.PASSWORD_FOR_USER(user)); + String salt = properties.getConfig(BrooklynWebConfig.SALT_FOR_USER(user)); + String expectedSha256 = properties.getConfig(BrooklynWebConfig.SHA256_FOR_USER(user)); + + return checkPassword(password, expectedPassword, expectedSha256, salt); + } + /** + * checks a candidate password against the expected credential defined for a given user. + * the expected credentials can be supplied as an expectedPassword OR as + * a combination of the SHA-256 hash of the expected password plus a defined salt. + * the combination of the SHA+SALT allows credentials to be supplied in a non-plaintext manner. + */ + public static boolean checkPassword(String candidatePassword, String expectedPassword, String expectedPasswordSha256, String salt) { + if (expectedPassword != null) { + return expectedPassword.equals(candidatePassword); + } else if (expectedPasswordSha256 != null) { + String hashedCandidatePassword = PasswordHasher.sha256(salt, candidatePassword); + return expectedPasswordSha256.equals(hashedCandidatePassword); + } + + return false; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java new file mode 100644 index 0000000..d3636e9 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java @@ -0,0 +1,132 @@ +/* + * 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.security.provider; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.InitialDirContext; +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.CharMatcher; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.config.StringConfigMap; +import org.apache.brooklyn.rest.BrooklynWebConfig; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; + +import java.util.Arrays; +import java.util.List; + +/** + * A {@link SecurityProvider} implementation that relies on LDAP to authenticate. + * + * @author Peter Veentjer. + */ +public class LdapSecurityProvider extends AbstractSecurityProvider implements SecurityProvider { + + public static final Logger LOG = LoggerFactory.getLogger(LdapSecurityProvider.class); + + public static final String LDAP_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; + + private final String ldapUrl; + private final String ldapRealm; + private final String organizationUnit; + + public LdapSecurityProvider(ManagementContext mgmt) { + StringConfigMap properties = mgmt.getConfig(); + ldapUrl = properties.getConfig(BrooklynWebConfig.LDAP_URL); + Strings.checkNonEmpty(ldapUrl, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_URL); + ldapRealm = CharMatcher.isNot('"').retainFrom(properties.getConfig(BrooklynWebConfig.LDAP_REALM)); + Strings.checkNonEmpty(ldapRealm, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_REALM); + + if(Strings.isBlank(properties.getConfig(BrooklynWebConfig.LDAP_OU))) { + LOG.info("Setting LDAP ou attribute to: Users"); + organizationUnit = "Users"; + } else { + organizationUnit = CharMatcher.isNot('"').retainFrom(properties.getConfig(BrooklynWebConfig.LDAP_OU)); + } + Strings.checkNonEmpty(ldapRealm, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_OU); + } + + public LdapSecurityProvider(String ldapUrl, String ldapRealm, String organizationUnit) { + this.ldapUrl = ldapUrl; + this.ldapRealm = ldapRealm; + this.organizationUnit = organizationUnit; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public boolean authenticate(HttpSession session, String user, String password) { + if (session==null || user==null) return false; + checkCanLoad(); + + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, ldapUrl); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, getUserDN(user)); + env.put(Context.SECURITY_CREDENTIALS, password); + + try { + new InitialDirContext(env); + return allow(session, user); + } catch (NamingException e) { + return false; + } + } + + /** + * Returns the LDAP path for the user + * + * @param user + * @return String + */ + protected String getUserDN(String user) { + List domain = Lists.transform(Arrays.asList(ldapRealm.split("\\.")), new Function() { + @Override + public String apply(String input) { + return "dc=" + input; + } + }); + + String dc = Joiner.on(",").join(domain).toLowerCase(); + return "cn=" + user + ",ou=" + organizationUnit + "," + dc; + } + + static boolean triedLoading = false; + public synchronized static void checkCanLoad() { + if (triedLoading) return; + try { + Class.forName(LDAP_CONTEXT_FACTORY); + triedLoading = true; + } catch (Throwable e) { + throw Exceptions.propagate(new ClassNotFoundException("Unable to load LDAP classes ("+LDAP_CONTEXT_FACTORY+") required for Brooklyn LDAP security provider")); + } + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java new file mode 100644 index 0000000..57d1400 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java @@ -0,0 +1,35 @@ +/* + * 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.security.provider; + +import javax.servlet.http.HttpSession; + +/** + * The SecurityProvider is responsible for doing authentication. + * + * A class should either have a constructor receiving a BrooklynProperties or it should have a no-arg constructor. + */ +public interface SecurityProvider { + + public boolean isAuthenticated(HttpSession session); + + public boolean authenticate(HttpSession session, String user, String password); + + public boolean logout(HttpSession session); +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java new file mode 100644 index 0000000..652b31c --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java @@ -0,0 +1,42 @@ +/* + * 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.apache.brooklyn.core.mgmt.internal.AccessManager; +import org.apache.brooklyn.rest.domain.AccessSummary; + +import com.google.common.collect.ImmutableMap; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.rest.api.AccessApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.resourceUriBuilder; + +/** + * @author Adam Lowe + */ +public class AccessTransformer { + + public static AccessSummary accessSummary(AccessManager manager, UriBuilder ub) { + URI selfUri = resourceUriBuilder(ub, AccessApi.class).build(); + ImmutableMap links = ImmutableMap.of("self", selfUri); + + return new AccessSummary(manager.isLocationProvisioningAllowed(), links); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java new file mode 100644 index 0000000..27f0f4f --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.rest.transform; + +import static org.apache.brooklyn.rest.domain.Status.ACCEPTED; +import static org.apache.brooklyn.rest.domain.Status.RUNNING; +import static org.apache.brooklyn.rest.domain.Status.STARTING; +import static org.apache.brooklyn.rest.domain.Status.STOPPED; +import static org.apache.brooklyn.rest.domain.Status.STOPPING; +import static org.apache.brooklyn.rest.domain.Status.UNKNOWN; +import static org.apache.brooklyn.rest.domain.Status.DESTROYED; +import static org.apache.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 org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.rest.domain.ApplicationSpec; +import org.apache.brooklyn.rest.domain.ApplicationSummary; +import org.apache.brooklyn.rest.domain.Status; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableMap; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.rest.api.ApplicationApi; +import org.apache.brooklyn.rest.api.EntityApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.resourceUriBuilder; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +public class ApplicationTransformer { + + public static Function fromApplication(final UriBuilder ub) { + return new Function() { + @Override + public ApplicationSummary apply(Application application) { + return summaryFromApplication(application, ub); + } + }; + }; + + 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 locations = Collections2.transform(application.getLocations(), new Function() { + @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, UriBuilder ub) { + Map links; + if (application.getId() == null) { + links = Collections.emptyMap(); + } else { + URI selfUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(application.getId()); + URI entitiesUri = resourceUriBuilder(ub, EntityApi.class).build(application.getId()); + links = ImmutableMap.of( + "self", selfUri, + "entities", entitiesUri); + } + + return new ApplicationSummary(application.getId(), specFromApplication(application), statusFromApplication(application), links); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java new file mode 100644 index 0000000..c8477cf --- /dev/null +++ b/rest/rest-resources/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 org.apache.brooklyn.core.BrooklynVersion.BrooklynFeature; +import org.apache.brooklyn.rest.domain.BrooklynFeatureSummary; + +public class BrooklynFeatureTransformer { + + public static final Function FROM_FEATURE = new Function() { + @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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java new file mode 100644 index 0000000..e584a9a --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java @@ -0,0 +1,188 @@ +/* + * 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.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.entity.EntityType; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.objs.SpecParameter; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.policy.PolicySpec; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.core.entity.EntityDynamicType; +import org.apache.brooklyn.core.mgmt.BrooklynTags; +import org.apache.brooklyn.core.objs.BrooklynTypes; +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.domain.EffectorSummary; +import org.apache.brooklyn.rest.domain.EntityConfigSummary; +import org.apache.brooklyn.rest.domain.LocationConfigSummary; +import org.apache.brooklyn.rest.domain.PolicyConfigSummary; +import org.apache.brooklyn.rest.domain.SensorSummary; +import org.apache.brooklyn.rest.domain.SummaryComparators; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.javalang.Reflections; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.rest.api.CatalogApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +public class CatalogTransformer { + + private static final org.slf4j.Logger log = LoggerFactory.getLogger(CatalogTransformer.class); + + public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { + Set config = Sets.newLinkedHashSet(); + Set sensors = Sets.newTreeSet(SummaryComparators.nameComparator()); + Set effectors = Sets.newTreeSet(SummaryComparators.nameComparator()); + + EntitySpec spec = null; + + try { + spec = (EntitySpec) b.getCatalog().createSpec((CatalogItem) item); + EntityDynamicType typeMap = BrooklynTypes.getDefinedEntityType(spec.getType()); + EntityType type = typeMap.getSnapshot(); + + for (SpecParameter input: spec.getParameters()) + config.add(EntityTransformer.entityConfigSummary(input)); + 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(), ub), + makeTags(spec, item), config, sensors, effectors, + item.isDeprecated(), makeLinks(item, ub)); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, CatalogItem item, UriBuilder ub) { + try { + switch (item.getCatalogItemType()) { + case TEMPLATE: + case ENTITY: + return catalogEntitySummary(b, item, ub); + case POLICY: + return catalogPolicySummary(b, item, ub); + case LOCATION: + return catalogLocationSummary(b, item, ub); + 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(), ub), item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { + Set config = ImmutableSet.of(); + return new CatalogPolicySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getJavaType(), item.getPlanYaml(), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), config, + item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { + Set config = ImmutableSet.of(); + return new CatalogLocationSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getJavaType(), item.getPlanYaml(), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), config, + item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + protected static Map makeLinks(CatalogItem item, UriBuilder ub) { + return MutableMap.of().addIfNotNull("self", getSelfLink(item, ub)); + } + + protected static URI getSelfLink(CatalogItem item, UriBuilder ub) { + String itemId = item.getId(); + switch (item.getCatalogItemType()) { + case TEMPLATE: + return serviceUriBuilder(ub, CatalogApi.class, "getApplication").build(itemId, item.getVersion()); + case ENTITY: + return serviceUriBuilder(ub, CatalogApi.class, "getEntity").build(itemId, item.getVersion()); + case POLICY: + return serviceUriBuilder(ub, CatalogApi.class, "getPolicy").build(itemId, item.getVersion()); + case LOCATION: + return serviceUriBuilder(ub, CatalogApi.class, "getLocation").build(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, UriBuilder ub) { + if (b.isUrlServerSideAndSafe(iconUrl)) { + return serviceUriBuilder(ub, CatalogApi.class, "getIcon").build(item.getSymbolicName(), item.getVersion()).toString(); + } + return iconUrl; + } + + private static Set makeTags(EntitySpec spec, CatalogItem item) { + // Combine tags on item with an InterfacesTag. + Set tags = MutableSet.copyOf(item.tags().getTags()); + if (spec != null) { + Class type; + if (spec.getImplementation() != null) { + type = spec.getImplementation(); + } else { + type = spec.getType(); + } + if (type != null) { + tags.add(new BrooklynTags.TraitsTag(Reflections.getAllInterfaces(type))); + } + } + return tags; + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java new file mode 100644 index 0000000..b374bb3 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java @@ -0,0 +1,91 @@ +/* + * 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 org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.effector.ParameterType; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.rest.domain.EffectorSummary; +import org.apache.brooklyn.rest.domain.EffectorSummary.ParameterSummary; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.core.task.ValueResolver; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.rest.api.ApplicationApi; +import org.apache.brooklyn.rest.api.EffectorApi; +import org.apache.brooklyn.rest.api.EntityApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +public class EffectorTransformer { + + public static EffectorSummary effectorSummary(final Entity entity, Effector effector, UriBuilder ub) { + URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId()); + URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId()); + URI selfUri = serviceUriBuilder(ub, EffectorApi.class, "invoke").build(entity.getApplicationId(), entity.getId(), effector.getName()); + return new EffectorSummary(effector.getName(), effector.getReturnTypeName(), + ImmutableSet.copyOf(Iterables.transform(effector.getParameters(), + new Function, EffectorSummary.ParameterSummary>() { + @Override + public EffectorSummary.ParameterSummary apply(@Nullable ParameterType parameterType) { + return parameterSummary(entity, parameterType); + } + })), effector.getDescription(), ImmutableMap.of( + "self", selfUri, + "entity", entityUri, + "application", applicationUri + )); + } + + public static EffectorSummary effectorSummaryForCatalog(Effector effector) { + Set> parameters = ImmutableSet.copyOf(Iterables.transform(effector.getParameters(), + new Function, 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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java new file mode 100644 index 0000000..f77bf81 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java @@ -0,0 +1,182 @@ +/* + * 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.api.catalog.CatalogConfig; +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.objs.SpecParameter; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.render.RendererHints; +import org.apache.brooklyn.rest.domain.EntityConfigSummary; +import org.apache.brooklyn.rest.domain.EntitySummary; +import org.apache.brooklyn.util.collections.MutableMap; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.rest.api.ApplicationApi; +import org.apache.brooklyn.rest.api.CatalogApi; +import org.apache.brooklyn.rest.api.EntityApi; +import org.apache.brooklyn.rest.api.EntityConfigApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +/** + * @author Adam Lowe + */ +public class EntityTransformer { + + public static final Function fromEntity(final UriBuilder ub) { + return new Function() { + @Override + public EntitySummary apply(Entity entity) { + return EntityTransformer.entitySummary(entity, ub); + } + }; + }; + + public static EntitySummary entitySummary(Entity entity, UriBuilder ub) { + URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId()); + URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId()); + ImmutableMap.Builder lb = ImmutableMap.builder() + .put("self", entityUri); + if (entity.getParent()!=null) { + URI parentUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getParent().getId()); + lb.put("parent", parentUri); + } + +// UriBuilder urib = serviceUriBuilder(ub, EntityApi.class, "getChildren").build(entity.getApplicationId(), entity.getId()); + // TODO: change all these as well :S + lb.put("application", 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.getIconUrl()!=null) + lb.put("iconUrl", URI.create(entityUri + "/icon")); + + if (entity.getCatalogItemId() != null) { + String versionedId = entity.getCatalogItemId(); + URI catalogUri; + if (CatalogUtils.looksLikeVersionedId(versionedId)) { + String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(versionedId); + String version = CatalogUtils.getVersionFromVersionedId(versionedId); + catalogUri = serviceUriBuilder(ub, CatalogApi.class, "getEntity").build(symbolicName, version); + } else { + catalogUri = serviceUriBuilder(ub, CatalogApi.class, "getEntity_0_7_0").build(versionedId); + } + lb.put("catalog", catalogUri); + } + + String type = entity.getEntityType().getName(); + return new EntitySummary(entity.getId(), entity.getDisplayName(), type, entity.getCatalogItemId(), lb.build()); + } + + public static List entitySummaries(Iterable entities, final UriBuilder ub) { + return Lists.newArrayList(transform( + entities, + new Function() { + @Override + public EntitySummary apply(Entity entity) { + return EntityTransformer.entitySummary(entity, ub); + } + })); + } + + protected static EntityConfigSummary entityConfigSummary(ConfigKey config, String label, Double priority, Map links) { + Map 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(Entity entity, ConfigKey config, UriBuilder ub) { + /* + * 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; + + URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId()); + URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId()); + URI selfUri = serviceUriBuilder(ub, EntityConfigApi.class, "get").build(entity.getApplicationId(), entity.getId(), config.getName()); + + MutableMap.Builder lb = MutableMap.builder() + .put("self", selfUri) + .put("application", applicationUri) + .put("entity", entityUri) + .put("action:json", selfUri); + + Iterable 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 URI applicationUri(Application entity, UriBuilder ub) { + return serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId()); + } + + public static URI entityUri(Entity entity, UriBuilder ub) { + return serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), 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); + } + + public static EntityConfigSummary entityConfigSummary(SpecParameter input) { + Double priority = input.isPinned() ? Double.valueOf(1d) : null; + return entityConfigSummary(input.getConfigKey(), input.getLabel(), priority, null); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java new file mode 100644 index 0000000..41b0f22 --- /dev/null +++ b/rest/rest-resources/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 org.apache.brooklyn.api.mgmt.ha.ManagementNodeSyncRecord; +import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord; +import org.apache.brooklyn.rest.domain.HighAvailabilitySummary; +import org.apache.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 nodes = Maps.newLinkedHashMap(); + for (Map.Entry entry : memento.getManagementNodes().entrySet()) { + nodes.put(entry.getKey(), haNodeSummary(entry.getValue())); + } + + // TODO What links? + ImmutableMap.Builder lb = ImmutableMap.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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java new file mode 100644 index 0000000..db3c4f1 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java @@ -0,0 +1,202 @@ +/* + * 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 org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationDefinition; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.config.Sanitizer; +import org.apache.brooklyn.core.location.BasicLocationDefinition; +import org.apache.brooklyn.core.location.LocationConfigKeys; +import org.apache.brooklyn.core.location.internal.LocationInternal; +import org.apache.brooklyn.rest.domain.LocationSummary; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.collect.ImmutableMap; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.rest.api.LocationApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +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, org.apache.brooklyn.rest.domain.LocationSpec locationSpec, UriBuilder ub) { + return newInstance(null, id, locationSpec, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ub); + } + @SuppressWarnings("deprecation") + public static LocationSummary newInstance(ManagementContext mgmt, String id, org.apache.brooklyn.rest.domain.LocationSpec locationSpec, LocationDetailLevel level, UriBuilder ub) { + // TODO: Remove null checks on mgmt when newInstance(String, LocationSpec) is deleted + Map 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 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(); + } + } + } + } + + URI selfUri = serviceUriBuilder(ub, LocationApi.class, "get").build(id); + return new LocationSummary( + id, + locationSpec.getName(), + locationSpec.getSpec(), + null, + copyConfig(config, level), + ImmutableMap.of("self", selfUri)); + } + + /** @deprecated since 0.7.0 use method taking management context and detail specifier */ + @Deprecated + public static LocationSummary newInstance(LocationDefinition l, UriBuilder ub) { + return newInstance(null, l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ub); + } + + public static LocationSummary newInstance(ManagementContext mgmt, LocationDefinition l, LocationDetailLevel level, UriBuilder ub) { + // TODO: Can remove null checks on mgmt when newInstance(LocationDefinition) is deleted + Map 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 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(); + } + } + } + } + + URI selfUri = serviceUriBuilder(ub, LocationApi.class, "get").build(l.getId()); + return new LocationSummary( + l.getId(), + l.getName(), + l.getSpec(), + null, + copyConfig(config, level), + ImmutableMap.of("self", selfUri)); + } + + private static Map copyConfig(Map entries, LocationDetailLevel level) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (level!=LocationDetailLevel.NONE) { + for (Map.Entry 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, UriBuilder ub) { + 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 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 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 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 config = level==LocationDetailLevel.NONE ? null : copyConfig(configOrig, level); + + URI selfUri = serviceUriBuilder(ub, LocationApi.class, "get").build(l.getId()); + URI parentUri = l.getParent() == null ? null : serviceUriBuilder(ub, LocationApi.class, "get").build(l.getParent().getId()); + URI specUri = specId == null ? null : serviceUriBuilder(ub, LocationApi.class, "get").build(specId); + return new LocationSummary( + l.getId(), + l.getDisplayName(), + spec, + l.getClass().getName(), + config, + MutableMap.of("self", selfUri) + .addIfNotNull("parent", parentUri) + .addIfNotNull("spec", specUri) + .asUnmodifiable() ); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java new file mode 100644 index 0000000..18e7d57 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java @@ -0,0 +1,97 @@ +/* + * 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.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.policy.Policies; +import org.apache.brooklyn.rest.domain.ApplicationSummary; +import org.apache.brooklyn.rest.domain.PolicyConfigSummary; +import org.apache.brooklyn.rest.domain.PolicySummary; +import org.apache.brooklyn.rest.resources.PolicyConfigResource; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; + +import com.google.common.collect.ImmutableMap; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.rest.api.ApplicationApi; +import org.apache.brooklyn.rest.api.EntityApi; +import org.apache.brooklyn.rest.api.PolicyApi; +import org.apache.brooklyn.rest.api.PolicyConfigApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.resourceUriBuilder; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +/** + * Converts from Brooklyn entities to restful API summary objects + */ +public class PolicyTransformer { + + public static PolicySummary policySummary(Entity entity, Policy policy, UriBuilder ub) { + URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId()); + URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId()); + URI configUri = resourceUriBuilder(ub, PolicyConfigApi.class).build(entity.getApplicationId(), entity.getId(), policy.getId()); + + URI selfUri = serviceUriBuilder(ub, PolicyApi.class, "getStatus").build(entity.getApplicationId(), entity.getId(), policy.getId()); + URI startUri = serviceUriBuilder(ub, PolicyApi.class, "start").build(entity.getApplicationId(), entity.getId(), policy.getId()); + URI stopUri = serviceUriBuilder(ub, PolicyApi.class, "stop").build(entity.getApplicationId(), entity.getId(), policy.getId()); + URI destroyUri = serviceUriBuilder(ub, PolicyApi.class, "destroy").build(entity.getApplicationId(), entity.getId(), policy.getId()); + + Map links = ImmutableMap.builder() + .put("self", selfUri) + .put("config", configUri) + .put("start", startUri) + .put("stop", stopUri) + .put("destroy", destroyUri) + .put("application", applicationUri) + .put("entity", 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, Entity entity, Policy policy, ConfigKey config, UriBuilder ub) { + PolicyConfigSummary summary = policyConfigSummary(utils, entity, policy, config, ub); +// 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, Entity entity, Policy policy, ConfigKey config, UriBuilder ub) { + URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId()); + URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId()); + URI policyUri = serviceUriBuilder(ub, PolicyApi.class, "getStatus").build(entity.getApplicationId(), entity.getId(), policy.getId()); + URI configUri = serviceUriBuilder(ub, PolicyConfigApi.class, "get").build(entity.getApplicationId(), entity.getId(), policy.getId(), config.getName()); + + Map links = ImmutableMap.builder() + .put("self", configUri) + .put("application", applicationUri) + .put("entity", entityUri) + .put("policy", 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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java new file mode 100644 index 0000000..19820d0 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java @@ -0,0 +1,88 @@ +/* + * 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 org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.core.config.render.RendererHints; +import org.apache.brooklyn.rest.domain.SensorSummary; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.collect.Iterables; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.rest.api.ApplicationApi; +import org.apache.brooklyn.rest.api.EntityApi; +import org.apache.brooklyn.rest.api.SensorApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +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, UriBuilder ub) { + URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId()); + URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId()); + URI selfUri = serviceUriBuilder(ub, SensorApi.class, "get").build(entity.getApplicationId(), entity.getId(), sensor.getName()); + + MutableMap.Builder lb = MutableMap.builder() + .put("self", selfUri) + .put("application", applicationUri) + .put("entity", entityUri) + .put("action:json", selfUri); + + if (sensor instanceof AttributeSensor) { + Iterable 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 void addNamedAction(MutableMap.Builder lb, RendererHints.NamedAction na , Entity entity, Sensor sensor) { + addNamedAction(lb, na, entity.getAttribute( ((AttributeSensor) sensor) ), sensor, entity); + } + + @SuppressWarnings("unchecked") + static void addNamedAction(MutableMap.Builder lb, RendererHints.NamedAction na, T value, Object context, Entity contextEntity) { + if (na instanceof RendererHints.NamedActionWithUrl) { + try { + String v = ((RendererHints.NamedActionWithUrl) 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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java new file mode 100644 index 0000000..cb74164 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java @@ -0,0 +1,153 @@ +/* + * 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 org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.HasTaskChildren; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags.WrappedStream; +import org.apache.brooklyn.rest.domain.LinkWithMetadata; +import org.apache.brooklyn.rest.domain.TaskSummary; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.task.TaskInternal; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import javax.ws.rs.core.UriBuilder; +import org.apache.brooklyn.rest.api.ActivityApi; +import org.apache.brooklyn.rest.api.EntityApi; +import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder; + +public class TaskTransformer { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(TaskTransformer.class); + + public static final Function, TaskSummary> fromTask(final UriBuilder ub) { + return new Function, TaskSummary>() { + @Override + public TaskSummary apply(@Nullable Task input) { + return taskSummary(input, ub); + } + }; + }; + + public static TaskSummary taskSummary(Task task, UriBuilder ub) { + try { + Preconditions.checkNotNull(task); + Entity entity = BrooklynTaskTags.getContextEntity(task); + String entityId; + String entityDisplayName; + URI entityLink; + + String selfLink = asLink(task, ub).getLink(); + + if (entity != null) { + entityId = entity.getId(); + entityDisplayName = entity.getDisplayName(); + entityLink = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId()); + } else { + entityId = null; + entityDisplayName = null; + entityLink = null; + } + + List children = Collections.emptyList(); + if (task instanceof HasTaskChildren) { + children = new ArrayList(); + for (Task t: ((HasTaskChildren)task).getChildren()) { + children.add(asLink(t, ub)); + } + } + + Map streams = new MutableMap(); + for (WrappedStream stream: BrooklynTaskTags.streams(task)) { + MutableMap metadata = MutableMap.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 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(), ub), + task.isDone() ? null : task instanceof TaskInternal ? asLink(((TaskInternal)task).getBlockingTask(), ub) : 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, UriBuilder ub) { + if (t==null) return null; + MutableMap data = new MutableMap(); + 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()); + } + URI taskUri = serviceUriBuilder(ub, ActivityApi.class, "get").build(t.getId()); + return new LinkWithMetadata(taskUri.toString(), data); + } +}