Repository: incubator-brooklyn Updated Branches: refs/heads/master ee9b59a74 -> 2a3603ba1
Illustration and test for PlanToSpecTransformer which uses XML, and tidies encountered in the process Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/2a3603ba Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/2a3603ba Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/2a3603ba Branch: refs/heads/master Commit: 2a3603ba19cc5551bfead2f04158e5f666431de7 Parents: ee9b59a Author: Alex Heneveld <[email protected]> Authored: Thu Aug 20 21:31:35 2015 +0100 Committer: Alex Heneveld <[email protected]> Committed: Thu Aug 20 21:33:02 2015 +0100 ---------------------------------------------------------------------- .../core/mgmt/EntityManagementUtils.java | 116 ++++++++++------ .../brooklyn/core/plan/PlanToSpecFactory.java | 93 +++++++++++-- .../core/plan/PlanToSpecTransformer.java | 32 +++-- .../core/plan/XmlPlanToSpecTransformer.java | 132 +++++++++++++++++++ .../core/plan/XmlPlanToSpecTransformerTest.java | 65 +++++++++ .../util/core/internal/TypeCoercionsTest.java | 7 + .../api/AssemblyTemplateSpecInstantiator.java | 4 +- .../BrooklynAssemblyTemplateInstantiator.java | 28 +--- .../spi/creation/CampToSpecTransformer.java | 10 +- .../test/lite/TestAppAssemblyInstantiator.java | 3 +- .../rest/resources/ApplicationResource.java | 4 +- .../brooklyn/util/exceptions/Exceptions.java | 4 +- 12 files changed, 403 insertions(+), 95 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java index 1391dc4..b86a0a8 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java @@ -18,8 +18,6 @@ */ package org.apache.brooklyn.core.mgmt; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; @@ -40,9 +38,9 @@ import org.apache.brooklyn.core.effector.Effectors; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityFunctions; import org.apache.brooklyn.core.entity.trait.Startable; -import org.apache.brooklyn.core.plan.PlanNotRecognizedException; import org.apache.brooklyn.core.plan.PlanToSpecFactory; import org.apache.brooklyn.core.plan.PlanToSpecTransformer; +import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.task.TaskBuilder; @@ -53,6 +51,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; +import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -65,8 +64,13 @@ public class EntityManagementUtils { private static final Logger log = LoggerFactory.getLogger(EntityManagementUtils.class); /** - * A marker config value which indicates that an application was created automatically - * to allow the management of a non-app entity. + * A marker config value which indicates that an {@link Application} entity was created automatically, + * needed because a plan might give multiple top-level entities or a non-Application top-level entity, + * but brooklyn requires an {@link Application} at the root. + * <p> + * Typically when such a wrapper app wraps another {@link Application} + * (or when we are adding to an existing entity and it wraps multiple {@link Entity} instances) + * it will be unwrapped. See {@link #newWrapperApp()} and {@link #unwrapApplication(EntitySpec)}. */ public static final ConfigKey<Boolean> WRAPPER_APP_MARKER = ConfigKeys.newBooleanConfigKey("brooklyn.wrapper_app"); @@ -77,35 +81,29 @@ public class EntityManagementUtils { return app; } - /** as {@link #createUnstarted(ManagementContext, EntitySpec)} but for a YAML spec */ - public static <T extends Application> T createUnstarted(ManagementContext mgmt, String yaml) { - EntitySpec<T> spec = createEntitySpec(mgmt, yaml); + /** as {@link #createUnstarted(ManagementContext, EntitySpec)} but for a string plan (e.g. camp yaml) */ + public static Application createUnstarted(ManagementContext mgmt, String plan) { + EntitySpec<? extends Application> spec = createEntitySpecForApplication(mgmt, plan); return createUnstarted(mgmt, spec); } - public static <T extends Application> EntitySpec<T> createEntitySpec(ManagementContext mgmt, String yaml) { - Collection<String> types = new ArrayList<String>(); - for (PlanToSpecTransformer c : PlanToSpecFactory.all(mgmt)) { - try { - return c.createApplicationSpec(yaml); - } catch (PlanNotRecognizedException e) { - types.add(c.getName()); + public static EntitySpec<? extends Application> createEntitySpecForApplication(ManagementContext mgmt, final String plan) { + return PlanToSpecFactory.attemptWithLoaders(mgmt, new Function<PlanToSpecTransformer, EntitySpec<? extends Application>>() { + @Override + public EntitySpec<? extends Application> apply(PlanToSpecTransformer input) { + return input.createApplicationSpec(plan); } - } - throw new PlanNotRecognizedException("Invalid plan, tried parsing with " + types); + }).get(); } @SuppressWarnings({ "unchecked", "rawtypes" }) - public static AbstractBrooklynObjectSpec<?, ?> createCatalogSpec(ManagementContext mgmt, CatalogItem<?, ?> item) { - Collection<String> types = new ArrayList<String>(); - for (PlanToSpecTransformer c : PlanToSpecFactory.all(mgmt)) { - try { - return c.createCatalogSpec((CatalogItem)item); - } catch (PlanNotRecognizedException e) { - types.add(c.getName()); + public static AbstractBrooklynObjectSpec<?, ?> createCatalogSpec(ManagementContext mgmt, final CatalogItem<?, ?> item) { + return PlanToSpecFactory.attemptWithLoaders(mgmt, new Function<PlanToSpecTransformer, AbstractBrooklynObjectSpec<?, ?>>() { + @Override + public AbstractBrooklynObjectSpec<?, ?> apply(PlanToSpecTransformer input) { + return input.createCatalogSpec((CatalogItem)item); } - } - throw new PlanNotRecognizedException("Invalid plan, tried parsing with " + types); + }).get(); } /** container for operation which creates something and which wants to return both @@ -159,14 +157,14 @@ public class EntityManagementUtils { ManagementContext mgmt = parent.getApplication().getManagementContext(); - EntitySpec<?> specA = createEntitySpec(mgmt, yaml); + EntitySpec<? extends Application> specA = createEntitySpecForApplication(mgmt, yaml); // see whether we can promote children List<EntitySpec<?>> specs = MutableList.of(); - if (canPromote(specA)) { + if (canPromoteChildrenInWrappedApplication(specA)) { // we can promote for (EntitySpec<?> specC: specA.getChildren()) { - collapseSpec(specA, specC); + mergeWrapperParentSpecToChildEntity(specA, specC); specs.add(specC); } } else { @@ -225,26 +223,64 @@ public class EntityManagementUtils { return CreationResult.of(children, task); } - /** worker method to combine specs */ + /** if an application should be unwrapped, it does so, returning the child; otherwise returns the argument passed in. + * use {@link #canPromoteWrappedApplication(EntitySpec)} to test whether it will unwrap. */ + public static EntitySpec<? extends Application> unwrapApplication(EntitySpec<? extends Application> wrapperApplication) { + if (canPromoteWrappedApplication(wrapperApplication)) { + @SuppressWarnings("unchecked") + EntitySpec<? extends Application> wrappedApplication = (EntitySpec<? extends Application>) Iterables.getOnlyElement( wrapperApplication.getChildren() ); + + // if promoted, apply the transformations done to the app + // (transformations will be done by the resolveSpec call above, but we are collapsing oldApp so transfer to app=newApp) + EntityManagementUtils.mergeWrapperParentSpecToChildEntity(wrapperApplication, wrappedApplication); + return wrappedApplication; + } + return wrapperApplication; + } + + /** Modifies the child so it includes the inessential setup of its parent, + * for use when unwrapping specific children, but a name or other item may have been set on the parent. + * See {@link #WRAPPER_APP_MARKER}. */ @Beta //where should this live long-term? - public static void collapseSpec(EntitySpec<?> sourceToBeCollapsed, EntitySpec<?> targetToBeExpanded) { - if (Strings.isEmpty(targetToBeExpanded.getDisplayName())) - targetToBeExpanded.displayName(sourceToBeCollapsed.getDisplayName()); - if (!sourceToBeCollapsed.getLocations().isEmpty()) - targetToBeExpanded.locations(sourceToBeCollapsed.getLocations()); + public static void mergeWrapperParentSpecToChildEntity(EntitySpec<? extends Application> wrapperParent, EntitySpec<?> wrappedChild) { + if (Strings.isEmpty(wrappedChild.getDisplayName())) + wrappedChild.displayName(wrapperParent.getDisplayName()); + if (!wrapperParent.getLocations().isEmpty()) + wrappedChild.locations(wrapperParent.getLocations()); // NB: this clobbers child config; might prefer to deeply merge maps etc // (but this should not be surprising, as unwrapping is often parameterising the nested blueprint, so outer config should dominate) - Map<ConfigKey<?>, Object> configWithoutWrapperMarker = Maps.filterKeys(sourceToBeCollapsed.getConfig(), Predicates.not(Predicates.<ConfigKey<?>>equalTo(EntityManagementUtils.WRAPPER_APP_MARKER))); - targetToBeExpanded.configure(configWithoutWrapperMarker); - targetToBeExpanded.configure(sourceToBeCollapsed.getFlags()); + Map<ConfigKey<?>, Object> configWithoutWrapperMarker = Maps.filterKeys(wrapperParent.getConfig(), Predicates.not(Predicates.<ConfigKey<?>>equalTo(EntityManagementUtils.WRAPPER_APP_MARKER))); + wrappedChild.configure(configWithoutWrapperMarker); + wrappedChild.configure(wrapperParent.getFlags()); // TODO copying tags to all entities is not ideal; // in particular the BrooklynTags.YAML_SPEC tag will show all entities if the root has multiple - targetToBeExpanded.tags(sourceToBeCollapsed.getTags()); + wrappedChild.tags(wrapperParent.getTags()); + } + + public static EntitySpec<? extends Application> newWrapperApp() { + return EntitySpec.create(BasicApplication.class).configure(WRAPPER_APP_MARKER, true); } + + /** returns true if the spec is for an empty-ish wrapper app contianing an application, + * for use when adding from a plan specifying an application which was wrapped because it had to be. + * @see #WRAPPER_APP_MARKER */ + public static boolean canPromoteWrappedApplication(EntitySpec<? extends Application> app) { + if (app.getChildren().size()!=1) + return false; - public static boolean canPromote(EntitySpec<?> spec) { + EntitySpec<?> childSpec = Iterables.getOnlyElement(app.getChildren()); + if (childSpec.getType()==null || !Application.class.isAssignableFrom(childSpec.getType())) + return false; + + return canPromoteChildrenInWrappedApplication(app); + } + + /** returns true if the spec is for an empty-ish wrapper app, + * for use when adding from a plan specifying multiple entities but nothing significant at the application level. + * @see #WRAPPER_APP_MARKER */ + public static boolean canPromoteChildrenInWrappedApplication(EntitySpec<?> spec) { return canPromoteBasedOnName(spec) && isWrapperApp(spec) && //equivalent to no keys starting with "brooklyn." http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java index 5d21420..6f74855 100644 --- a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java +++ b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java @@ -18,29 +18,102 @@ */ package org.apache.brooklyn.core.plan; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.ServiceLoader; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import com.google.common.collect.Lists; +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; public class PlanToSpecFactory { - public static PlanToSpecTransformer forMime(ManagementContext mgmt, String mime) { + + private static final Logger log = LoggerFactory.getLogger(PlanToSpecFactory.class); + + private static Collection<PlanToSpecTransformer> getAll() { + return ImmutableList.copyOf(ServiceLoader.load(PlanToSpecTransformer.class)); + } + + private static Collection<Class<? extends PlanToSpecTransformer>> OVERRIDE; + @SafeVarargs + @VisibleForTesting + public synchronized static void forceAvailable(Class<? extends PlanToSpecTransformer> ...classes) { + OVERRIDE = Arrays.asList(classes); + } + public synchronized static void clearForced() { + OVERRIDE = null; + } + + public static Collection<PlanToSpecTransformer> all(ManagementContext mgmt) { + Collection<Class<? extends PlanToSpecTransformer>> override = OVERRIDE; + Collection<PlanToSpecTransformer> result = new ArrayList<PlanToSpecTransformer>(); + if (override!=null) { + for (Class<? extends PlanToSpecTransformer> o1: override) { + try { + result.add(o1.newInstance()); + } catch (Exception e) { + Exceptions.propagate(e); + } + } + } else { + result.addAll(getAll()); + } + for(PlanToSpecTransformer t : result) { + t.injectManagementContext(mgmt); + } + return result; + } + + @Beta + public static PlanToSpecTransformer forPlanType(ManagementContext mgmt, String planType) { Collection<PlanToSpecTransformer> transformers = all(mgmt); for (PlanToSpecTransformer transformer : transformers) { - if (transformer.accepts(mime)) { + if (transformer.accepts(planType)) { return transformer; } } - throw new IllegalStateException("PlanToSpecTransformer for type " + mime + " not found. Registered transformers are: " + transformers); + throw new IllegalStateException("PlanToSpecTransformer for plan type " + planType + " not found. Registered transformers are: " + transformers); } - - public static Collection<PlanToSpecTransformer> all(ManagementContext mgmt) { - Collection<PlanToSpecTransformer> transformers = Lists.newArrayList(ServiceLoader.load(PlanToSpecTransformer.class)); - for(PlanToSpecTransformer t : transformers) { - t.injectManagementContext(mgmt); + + // TODO primitive loading mechanism, just tries all in order; we'll want something better as we get more plan transformers + @Beta + public static <T> Maybe<T> attemptWithLoaders(ManagementContext mgmt, Function<PlanToSpecTransformer,T> f) { + return attemptWithLoaders(all(mgmt), f); + } + + public static <T> Maybe<T> attemptWithLoaders(Iterable<PlanToSpecTransformer> transformers, Function<PlanToSpecTransformer,T> f) { + Collection<String> transformersWhoDontSupport = new ArrayList<String>(); + Collection<Exception> otherProblemsFromTransformers = new ArrayList<Exception>(); + for (PlanToSpecTransformer t: transformers) { + try { + return Maybe.of(f.apply(t)); + } catch (PlanNotRecognizedException e) { + transformersWhoDontSupport.add(t.getShortDescription() + + (Strings.isNonBlank(e.getMessage()) ? " ("+e.getMessage()+")" : "")); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + otherProblemsFromTransformers.add(new IllegalArgumentException("Transformer for "+t.getShortDescription()+" gave an error creating this plan", e)); + } + } + // failed + Exception result; + if (!otherProblemsFromTransformers.isEmpty()) { + // at least one thought he could do it + log.debug("Plan could not be transformed; failure will be propagated (other transformers tried = "+transformersWhoDontSupport+"): "+otherProblemsFromTransformers); + result = Exceptions.create(null, otherProblemsFromTransformers); + } else { + result = new PlanNotRecognizedException("Invalid plan; format could not be recognized, trying with: "+transformersWhoDontSupport); } - return transformers; + return Maybe.absent(result); } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java index 66f1b62..63d5c1f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java @@ -28,22 +28,34 @@ import org.apache.brooklyn.core.mgmt.ManagementContextInjectable; import com.google.common.annotations.Beta; -/** Pluggable {@link ServiceLoader} interface for defferent plan-interpreters. - * Implementations should take a plan and return an {@link EntitySpec}. +/** Pluggable {@link ServiceLoader} interface for different plan-interpreters, + * that is, different ways of taking an application plan and returning an {@link EntitySpec}, + * and a {@link CatalogItem} and returning an {@link AbstractBrooklynObjectSpec}. */ @Beta public interface PlanToSpecTransformer extends ManagementContextInjectable { - /** Human-readable name for this transformer */ - String getName(); + /** A short, human-readable name for this transformer */ + String getShortDescription(); - /** whether this accepts the given MIME format */ - boolean accepts(String mime); + /** whether this accepts the given plan type */ + // TODO determine semantics of plan type; for now, we try all using PlanToSpecFactory methods, + // that's okay when there's just a very few, but we'll want something better if that grows + @Beta + boolean accepts(String planType); - /** creates an {@link EntitySpec} given a plan, according to the transformation rules this understands */ - <T extends Application> EntitySpec<T> createApplicationSpec(String plan); + /** creates an {@link EntitySpec} given a complete plan textual description for a top-level application, + * according to the transformation rules this understands. + * <p> + * should throw {@link PlanNotRecognizedException} if not supported. */ + EntitySpec<? extends Application> createApplicationSpec(String plan) throws PlanNotRecognizedException; - /** creates an object spec given a catalog item, according to the transformation rules this understands */ - <T,SpecT extends AbstractBrooklynObjectSpec<T, SpecT>> AbstractBrooklynObjectSpec<T, SpecT> createCatalogSpec(CatalogItem<T, SpecT> item); + /** creates an object spec given a catalog item. + * <p> + * the catalog item might be known by type, or its source plan fragment text might be inspected and transformed. + * implementations will typically look at the {@link CatalogItem#getCatalogItemType()} first. + * <p> + * should throw {@link PlanNotRecognizedException} if this transformer does not know what to do with the plan. */ + <T,SpecT extends AbstractBrooklynObjectSpec<T, SpecT>> AbstractBrooklynObjectSpec<T, SpecT> createCatalogSpec(CatalogItem<T, SpecT> item) throws PlanNotRecognizedException; } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformer.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformer.java b/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformer.java new file mode 100644 index 0000000..5fc103d --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformer.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.core.plan; + +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.mgmt.EntityManagementUtils; +import org.apache.brooklyn.entity.stock.BasicApplication; +import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.stream.ReaderInputStream; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** Example implementation of {@link PlanToSpecTransformer} showing + * how implementations are meant to be written. */ +public class XmlPlanToSpecTransformer implements PlanToSpecTransformer { + + @SuppressWarnings("unused") + private ManagementContext mgmt; + + @Override + public void injectManagementContext(ManagementContext managementContext) { + mgmt = managementContext; + } + + @Override + public String getShortDescription() { + return "Dummy app structure created from the XML tree"; + } + + @Override + public boolean accepts(String mime) { + if ("test-xml".equals(mime)) return true; + return false; + } + + @SuppressWarnings("unchecked") + @Override + public EntitySpec<? extends Application> createApplicationSpec(String plan) { + Document dom = parseXml(plan); + EntitySpec<?> result = toEntitySpec(dom, 0); + if (Application.class.isAssignableFrom(result.getType())) { + return (EntitySpec<Application>) result; + } else { + return EntityManagementUtils.newWrapperApp().child(result); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public <T, SpecT extends AbstractBrooklynObjectSpec<T, SpecT>> AbstractBrooklynObjectSpec<T, SpecT> createCatalogSpec(CatalogItem<T, SpecT> item) { + if (item.getPlanYaml()==null) throw new PlanNotRecognizedException("Plan is null"); + if (item.getCatalogItemType()==CatalogItemType.ENTITY) { + return (EntitySpec)toEntitySpec(parseXml(item.getPlanYaml()), 1); + } + if (item.getCatalogItemType()==CatalogItemType.TEMPLATE) { + return (EntitySpec)toEntitySpec(parseXml(item.getPlanYaml()), 0); + } + throw new PlanNotRecognizedException("Type "+item.getCatalogItemType()+" not supported"); + } + + private Document parseXml(String plan) { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + Document dom; + + try { + //Using factory get an instance of document builder + DocumentBuilder db = dbf.newDocumentBuilder(); + + //parse using builder to get DOM representation of the XML file + dom = db.parse(new ReaderInputStream(new StringReader(plan))); + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + throw new PlanNotRecognizedException(e); + } + return dom; + } + + private EntitySpec<?> toEntitySpec(Node dom, int depth) { + if (dom.getNodeType()==Node.DOCUMENT_NODE) { + if (dom.getChildNodes().getLength()!=1) { + // NB: <?...?> entity preamble might break this + throw new IllegalStateException("Document for "+dom+" has "+dom.getChildNodes().getLength()+" nodes; 1 expected."); + } + return toEntitySpec(dom.getChildNodes().item(0), depth); + } + + EntitySpec<?> result = depth == 0 ? EntitySpec.create(BasicApplication.class) : EntitySpec.create(BasicEntity.class); + result.displayName(dom.getNodeName()); + if (dom.getAttributes()!=null) { + for (int i=0; i<dom.getAttributes().getLength(); i++) + result.configure(dom.getAttributes().item(i).getNodeName(), dom.getAttributes().item(i).getTextContent()); + } + if (dom.getChildNodes()!=null) { + for (int i=0; i<dom.getChildNodes().getLength(); i++) { + Node item = dom.getChildNodes().item(i); + if (item.getNodeType()==Node.ELEMENT_NODE) { + result.child(toEntitySpec(item, depth+1)); + } + } + } + return result; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformerTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformerTest.java b/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformerTest.java new file mode 100644 index 0000000..d6528df --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformerTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.plan; + +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.mgmt.ManagementContext; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.mgmt.EntityManagementUtils; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.python.google.common.collect.Iterables; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Tests the sample {@link XmlPlanToSpecTransformer} + * which illustrates how the {@link PlanToSpecTransformer} can be used. */ +public class XmlPlanToSpecTransformerTest { + + private ManagementContext mgmt; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + PlanToSpecFactory.forceAvailable(XmlPlanToSpecTransformer.class); + mgmt = LocalManagementContextForTests.newInstance(); + } + + @AfterMethod(alwaysRun = true) + public void tearDown() { + PlanToSpecFactory.clearForced(); + if (mgmt!=null) Entities.destroyAll(mgmt); + } + + @Test + public void testSimpleXmlPlanParse() { + EntitySpec<? extends Application> appSpec = EntityManagementUtils.createEntitySpecForApplication(mgmt, + "<root><a_kid foo=\"bar\"/></root>"); + Application app = EntityManagementUtils.createStarting(mgmt, appSpec).get(); + Entities.dumpInfo(app); + Assert.assertEquals(app.getDisplayName(), "root"); + Entity child = Iterables.getOnlyElement(app.getChildren()); + Assert.assertEquals(child.getDisplayName(), "a_kid"); + Assert.assertEquals(child.config().get(ConfigKeys.newStringConfigKey("foo")), "bar"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java index 8ce718b..ec89ebf 100644 --- a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java +++ b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.util.core.internal; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; import java.math.BigDecimal; import java.math.BigInteger; @@ -67,6 +68,7 @@ public class TypeCoercionsTest { assertEquals(TypeCoercions.coerce("true", Boolean.class), (Boolean)true); assertEquals(TypeCoercions.coerce("False", Boolean.class), (Boolean)false); assertEquals(TypeCoercions.coerce("true ", Boolean.class), (Boolean)true); + assertNull(TypeCoercions.coerce(null, Boolean.class), null); assertEquals(TypeCoercions.coerce("1", char.class), (Character)'1'); assertEquals(TypeCoercions.coerce("1", short.class), (Short)((short)1)); @@ -209,30 +211,35 @@ public class TypeCoercionsTest { @Test public void testListEntryCoercion() { + @SuppressWarnings("serial") List<?> s = TypeCoercions.coerce(ImmutableList.of("java.lang.Integer", "java.lang.Double"), new TypeToken<List<Class<?>>>() { }); Assert.assertEquals(s, ImmutableList.of(Integer.class, Double.class)); } @Test public void testListEntryToSetCoercion() { + @SuppressWarnings("serial") Set<?> s = TypeCoercions.coerce(ImmutableList.of("java.lang.Integer", "java.lang.Double"), new TypeToken<Set<Class<?>>>() { }); Assert.assertEquals(s, ImmutableSet.of(Integer.class, Double.class)); } @Test public void testListEntryToCollectionCoercion() { + @SuppressWarnings("serial") Collection<?> s = TypeCoercions.coerce(ImmutableList.of("java.lang.Integer", "java.lang.Double"), new TypeToken<Collection<Class<?>>>() { }); Assert.assertEquals(s, ImmutableList.of(Integer.class, Double.class)); } @Test public void testMapValueCoercion() { + @SuppressWarnings("serial") Map<?,?> s = TypeCoercions.coerce(ImmutableMap.of("int", "java.lang.Integer", "double", "java.lang.Double"), new TypeToken<Map<String, Class<?>>>() { }); Assert.assertEquals(s, ImmutableMap.of("int", Integer.class, "double", Double.class)); } @Test public void testMapKeyCoercion() { + @SuppressWarnings("serial") Map<?,?> s = TypeCoercions.coerce(ImmutableMap.of("java.lang.Integer", "int", "java.lang.Double", "double"), new TypeToken<Map<Class<?>, String>>() { }); Assert.assertEquals(s, ImmutableMap.of(Integer.class, "int", Double.class, "double")); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java index 88417e3..df7227b 100644 --- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java @@ -20,6 +20,7 @@ package org.apache.brooklyn.camp.brooklyn.api; import java.util.Set; +import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.camp.CampPlatform; import org.apache.brooklyn.camp.spi.AssemblyTemplate; @@ -28,8 +29,7 @@ import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext; public interface AssemblyTemplateSpecInstantiator extends AssemblyTemplateInstantiator { - EntitySpec<?> createSpec(AssemblyTemplate template, CampPlatform platform, BrooklynClassLoadingContext loader, boolean autoUnwrapIfAppropriate); + EntitySpec<? extends Application> createSpec(AssemblyTemplate template, CampPlatform platform, BrooklynClassLoadingContext loader, boolean autoUnwrapIfAppropriate); EntitySpec<?> createNestedSpec(AssemblyTemplate template, CampPlatform platform, BrooklynClassLoadingContext itemLoader, Set<String> encounteredCatalogTypes); - } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java index 63fa664..d582c45 100644 --- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java @@ -41,11 +41,11 @@ import org.apache.brooklyn.camp.spi.PlatformComponentTemplate; import org.apache.brooklyn.camp.spi.collection.ResolvableLink; import org.apache.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator; import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker; -import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.mgmt.EntityManagementUtils; -import org.apache.brooklyn.core.mgmt.HasBrooklynManagementContext; import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult; +import org.apache.brooklyn.core.mgmt.HasBrooklynManagementContext; import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; import org.apache.brooklyn.entity.stock.BasicApplicationImpl; @@ -56,7 +56,6 @@ import org.apache.brooklyn.util.net.Urls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -88,7 +87,6 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe return ((HasBrooklynManagementContext)platform).getBrooklynManagementContext(); } - @SuppressWarnings("unchecked") public EntitySpec<? extends Application> createSpec(AssemblyTemplate template, CampPlatform platform, BrooklynClassLoadingContext loader, boolean autoUnwrapIfPossible) { log.debug("CAMP creating application instance for {} ({})", template.getId(), template); @@ -106,12 +104,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe } if (autoUnwrapIfPossible && shouldUnwrap(template, app)) { - EntitySpec<? extends Application> oldApp = app; - app = (EntitySpec<? extends Application>) Iterables.getOnlyElement( app.getChildren() ); - - // if promoted, apply the transformations done to the app - // (transformations will be done by the resolveSpec call above, but we are collapsing oldApp so transfer to app=newApp) - EntityManagementUtils.collapseSpec(oldApp, app); + app = EntityManagementUtils.unwrapApplication(app); } return app; @@ -132,20 +125,9 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe } protected boolean shouldUnwrap(AssemblyTemplate template, EntitySpec<? extends Application> app) { - Object leaveWrapped = template.getCustomAttributes().get(NEVER_UNWRAP_APPS_PROPERTY); - if (leaveWrapped!=null) { - if (TypeCoercions.coerce(leaveWrapped, Boolean.class)) - return false; - } - - if (app.getChildren().size()!=1) + if (Boolean.TRUE.equals(TypeCoercions.coerce(template.getCustomAttributes().get(NEVER_UNWRAP_APPS_PROPERTY), Boolean.class))) return false; - - EntitySpec<?> childSpec = Iterables.getOnlyElement(app.getChildren()); - if (childSpec.getType()==null || !Application.class.isAssignableFrom(childSpec.getType())) - return false; - - return EntityManagementUtils.canPromote(app); + return EntityManagementUtils.canPromoteWrappedApplication(app); } private List<EntitySpec<?>> buildTemplateServicesAsSpecs(BrooklynClassLoadingContext loader, AssemblyTemplate template, CampPlatform platform) { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java index ebafa76..7ab200d 100644 --- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java @@ -41,8 +41,8 @@ public class CampToSpecTransformer implements PlanToSpecTransformer { private ManagementContext mgmt; @Override - public String getName() { - return YAML_CAMP_PLAN_TYPE; + public String getShortDescription() { + return "Brooklyn OASIS CAMP interpreter"; } @Override @@ -51,7 +51,7 @@ public class CampToSpecTransformer implements PlanToSpecTransformer { } @Override - public <T extends Application> EntitySpec<T> createApplicationSpec(String plan) { + public EntitySpec<? extends Application> createApplicationSpec(String plan) { CampPlatform camp = CampCatalogUtils.getCampPlatform(mgmt); AssemblyTemplate at = camp.pdp().registerDeploymentPlan( new StringReader(plan) ); AssemblyTemplateInstantiator instantiator; @@ -62,9 +62,7 @@ public class CampToSpecTransformer implements PlanToSpecTransformer { } if (instantiator instanceof AssemblyTemplateSpecInstantiator) { BrooklynClassLoadingContext loader = JavaBrooklynClassLoadingContext.create(mgmt); - @SuppressWarnings("unchecked") - EntitySpec<T> createSpec = (EntitySpec<T>) ((AssemblyTemplateSpecInstantiator) instantiator).createSpec(at, camp, loader, true); - return createSpec; + return ((AssemblyTemplateSpecInstantiator) instantiator).createSpec(at, camp, loader, true); } else { // The unknown instantiator can create the app (Assembly), but not a spec. // Currently, all brooklyn plans should produce the above. http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java index dc0c33d..831805f 100644 --- a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java +++ b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java @@ -21,6 +21,7 @@ package org.apache.brooklyn.camp.brooklyn.test.lite; import java.util.Map; import java.util.Set; +import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.camp.CampPlatform; @@ -58,7 +59,7 @@ public class TestAppAssemblyInstantiator extends BasicAssemblyTemplateInstantiat } @Override - public EntitySpec<?> createSpec(AssemblyTemplate template, CampPlatform platform, BrooklynClassLoadingContext loader, boolean autoUnwrap) { + public EntitySpec<? extends Application> createSpec(AssemblyTemplate template, CampPlatform platform, BrooklynClassLoadingContext loader, boolean autoUnwrap) { EntitySpec<TestApplication> app = EntitySpec.create(TestApplication.class) .configure(TestEntity.CONF_NAME, template.getName()) .configure(TestEntity.CONF_MAP_THING, MutableMap.of("type", template.getType(), "desc", template.getDescription())); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java index 5bc8df9..25e80e0 100644 --- a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java @@ -272,7 +272,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements } log.debug("Creating app from yaml:\n{}", yaml); - EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpec(mgmt(), yaml); + EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpecForApplication(mgmt(), yaml); if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) { throw WebResourceUtils.unauthorized("User '%s' is not authorized to start application %s", @@ -330,7 +330,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements //TODO infer encoding from request String potentialYaml = new String(inputToAutodetectType); - EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpec(mgmt(), potentialYaml); + EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpecForApplication(mgmt(), potentialYaml); // TODO not json - try ZIP, etc http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java index 347a212..583104b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java @@ -29,6 +29,8 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutionException; +import javax.annotation.Nullable; + import org.apache.brooklyn.util.text.Strings; import com.google.common.base.Predicate; @@ -267,7 +269,7 @@ public class Exceptions { return create(null, exceptions); } /** creates the given exception, but without propagating it, for use when caller will be wrapping */ - public static RuntimeException create(String prefix, Collection<? extends Throwable> exceptions) { + public static RuntimeException create(@Nullable String prefix, Collection<? extends Throwable> exceptions) { if (exceptions.size()==1) { Throwable e = exceptions.iterator().next(); if (Strings.isBlank(prefix)) return new PropagatedRuntimeException(e);
