http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerWebAppFixtureIntegrationTest.java ---------------------------------------------------------------------- diff --cc brooklyn-library/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerWebAppFixtureIntegrationTest.java index 0000000,0d21d03..e824e26 mode 000000,100644..100644 --- a/brooklyn-library/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerWebAppFixtureIntegrationTest.java +++ b/brooklyn-library/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerWebAppFixtureIntegrationTest.java @@@ -1,0 -1,174 +1,154 @@@ + /* + * 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.entity.webapp.tomcat; + + import java.io.File; + import java.net.InetAddress; + import java.net.Socket; + import java.net.SocketException; + import java.util.List; + import java.util.concurrent.Callable; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicReference; + + import org.apache.brooklyn.api.entity.EntitySpec; + import org.apache.brooklyn.core.location.PortRanges; + import org.apache.brooklyn.core.test.entity.TestApplication; + import org.apache.brooklyn.entity.software.base.SoftwareProcess; + import org.apache.brooklyn.entity.webapp.AbstractWebAppFixtureIntegrationTest; + import org.apache.brooklyn.entity.webapp.HttpsSslConfig; + import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcess; + import org.apache.brooklyn.test.support.TestResourceUnavailableException; + import org.apache.brooklyn.util.exceptions.Exceptions; + import org.apache.brooklyn.util.repeat.Repeater; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.testng.Assert; + import org.testng.annotations.AfterMethod; + import org.testng.annotations.DataProvider; + import org.testng.annotations.Test; + + import com.google.common.collect.ImmutableSet; + import com.google.common.collect.Lists; + + public class TomcatServerWebAppFixtureIntegrationTest extends AbstractWebAppFixtureIntegrationTest { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(TomcatServerWebAppFixtureIntegrationTest.class); + + @DataProvider(name = "basicEntities") + public Object[][] basicEntities() { + TestApplication tomcatApp = newTestApplication(); + TomcatServer tomcat = tomcatApp.createAndManageChild(EntitySpec.create(TomcatServer.class) + .configure(TomcatServer.HTTP_PORT, PortRanges.fromString(DEFAULT_HTTP_PORT))); + + + File keystoreFile; + try { + keystoreFile = createTemporaryKeyStore("myname", "mypass"); + keystoreFile.deleteOnExit(); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + + TestApplication tomcatHttpsApp = newTestApplication(); + TomcatServer httpsTomcat = tomcatHttpsApp.createAndManageChild(EntitySpec.create(TomcatServer.class) + .configure(TomcatServer.ENABLED_PROTOCOLS, ImmutableSet.of("https")) + .configure(TomcatServer.HTTPS_SSL_CONFIG, + new HttpsSslConfig().keyAlias("myname").keystorePassword("mypass").keystoreUrl(keystoreFile.getAbsolutePath()))); + + return new JavaWebAppSoftwareProcess[][] { + new JavaWebAppSoftwareProcess[] { tomcat }, + new JavaWebAppSoftwareProcess[] { httpsTomcat } + }; + } + + // exists to be able to test on this class from GUI in Eclipse IDE + @Test(groups = "Integration", dataProvider = "basicEntities") + public void canStartAndStop(final SoftwareProcess entity) { + super.canStartAndStop(entity); + } ++ + @Test(groups = "Integration", dataProvider = "basicEntities") + public void testReportsServiceDownWhenKilled(final SoftwareProcess entity) throws Exception { + super.testReportsServiceDownWhenKilled(entity); + } + + @Override - // as parent, but with spring travel + @DataProvider(name = "entitiesWithWarAndURL") + public Object[][] entitiesWithWar() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war"); + List<Object[]> result = Lists.newArrayList(); + + for (Object[] entity : basicEntities()) { + result.add(new Object[] { + entity[0], + "hello-world.war", + "hello-world/", + "" // no sub-page path + }); + } - - // TODO would be nice to test against spring web framework stock booking example - // but we'd need an external URL for that (we removed the binary from here for apache compliance reasons) -// TestApplication tomcatApp = newTestApplication(); -// TomcatServer tomcat = tomcatApp.createAndManageChild(EntitySpec.create(TomcatServer.class) -// .configure(TomcatServer.HTTP_PORT, PortRanges.fromString(DEFAULT_HTTP_PORT))); -// result.add(new Object[] { -// tomcat, -// "swf-booking-mvc.war", -// "swf-booking-mvc/", -// "spring/intro", -// }); - + return result.toArray(new Object[][] {}); + } + + @AfterMethod(alwaysRun=true, dependsOnMethods="shutdownApp") + public void ensureIsShutDown() throws Exception { + final AtomicReference<Socket> shutdownSocket = new AtomicReference<Socket>(); + final AtomicReference<SocketException> gotException = new AtomicReference<SocketException>(); + final Integer shutdownPort = (entity != null) ? entity.getAttribute(TomcatServer.SHUTDOWN_PORT) : null; + + if (shutdownPort != null) { + boolean socketClosed = Repeater.create("Checking WebApp has shut down") + .repeat(new Callable<Void>() { + public Void call() throws Exception { + if (shutdownSocket.get() != null) shutdownSocket.get().close(); + try { + shutdownSocket.set(new Socket(InetAddress.getLocalHost(), shutdownPort)); + gotException.set(null); + } catch (SocketException e) { + gotException.set(e); + } + return null; + }}) + .every(100, TimeUnit.MILLISECONDS) + .until(new Callable<Boolean>() { + public Boolean call() { + return (gotException.get() != null); + }}) + .limitIterationsTo(25) + .run(); + - if (socketClosed == false) { -// log.error("WebApp did not shut down - this is a failure of the last test run"); -// log.warn("I'm sending a message to the shutdown port {}", shutdownPort); -// OutputStreamWriter writer = new OutputStreamWriter(shutdownSocket.getOutputStream()); -// writer.write("SHUTDOWN\r\n"); -// writer.flush(); -// writer.close(); -// shutdownSocket.close(); ++ if (!socketClosed) { + throw new Exception("Last test run did not shut down WebApp entity "+entity+" (port "+shutdownPort+")"); + } + } else { + Assert.fail("Cannot shutdown, because shutdown-port not set for "+entity); + } + } + + public static void main(String ...args) throws Exception { + TomcatServerWebAppFixtureIntegrationTest t = new TomcatServerWebAppFixtureIntegrationTest(); + t.setUp(); + t.testReportsServiceDownWhenKilled((SoftwareProcess) t.basicEntities()[0][0]); + t.shutdownApp(); + t.ensureIsShutDown(); + t.shutdownMgmt(); + } + + }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java ---------------------------------------------------------------------- diff --cc brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java index 0000000,aa3a198..ab046d5 mode 000000,100644..100644 --- a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java +++ b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java @@@ -1,0 -1,262 +1,267 @@@ + /* + * 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.api.internal; + + import static com.google.common.base.Preconditions.checkNotNull; + + import java.io.Serializable; + import java.lang.reflect.Modifier; + import java.util.Collections; + import java.util.List; + import java.util.Map; + import java.util.Set; + + import org.apache.brooklyn.api.mgmt.EntityManager; + import org.apache.brooklyn.api.mgmt.Task; + import org.apache.brooklyn.api.objs.BrooklynObject; + import org.apache.brooklyn.api.objs.SpecParameter; + import org.apache.brooklyn.config.ConfigKey; + import org.apache.brooklyn.config.ConfigKey.HasConfigKey; + import org.apache.brooklyn.util.collections.MutableSet; + import org.apache.brooklyn.util.exceptions.Exceptions; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + import com.google.common.base.Objects; + import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableSet; + import com.google.common.collect.Iterables; + import com.google.common.collect.Maps; + + /** Defines a spec for creating a {@link BrooklynObject}. + * <p> + * In addition to the contract defined by the code, + * subclasses should provide a public static <code>create(Class)</code> + * method to create an instance of the spec for the target type indicated by the argument. + * <p> + * The spec is then passed to type-specific methods, + * e.g. {@link EntityManager#createEntity(org.apache.brooklyn.api.entity.EntitySpec)} + * to create a managed instance of the target type. */ + public abstract class AbstractBrooklynObjectSpec<T,SpecT extends AbstractBrooklynObjectSpec<T,SpecT>> implements Serializable { + + private static final long serialVersionUID = 3010955277740333030L; + + private static final Logger log = LoggerFactory.getLogger(AbstractBrooklynObjectSpec.class); + + private final Class<? extends T> type; + private String displayName; + private String catalogItemId; + private Set<Object> tags = MutableSet.of(); + private List<SpecParameter<?>> parameters = ImmutableList.of(); + + protected final Map<String, Object> flags = Maps.newLinkedHashMap(); + protected final Map<ConfigKey<?>, Object> config = Maps.newLinkedHashMap(); + + protected AbstractBrooklynObjectSpec(Class<? extends T> type) { + checkValidType(type); + this.type = type; + this.catalogItemId = ApiObjectsFactory.get().getCatalogItemIdFromContext(); + } + + @SuppressWarnings("unchecked") + protected SpecT self() { + return (SpecT) this; + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("type", getType()).toString(); + } + + protected abstract void checkValidType(Class<? extends T> type); + + public SpecT displayName(String val) { + displayName = val; + return self(); + } + + public SpecT catalogItemId(String val) { + catalogItemId = val; + return self(); + } + + public SpecT tag(Object tag) { + tags.add(tag); + return self(); + } + + /** adds the given tags */ + public SpecT tags(Iterable<Object> tagsToAdd) { + Iterables.addAll(this.tags, tagsToAdd); + return self(); + } + + public SpecT parameters(List<? extends SpecParameter<?>> parameters) { + this.parameters = ImmutableList.copyOf(checkNotNull(parameters, "parameters")); + return self(); + } + + /** + * @return The type (often an interface) this spec represents and which will be instantiated from it + */ + public Class<? extends T> getType() { + return type; + } + + /** + * @return The display name of the object + */ + public final String getDisplayName() { + return displayName; + } + + public final String getCatalogItemId() { + return catalogItemId; + } + + public final Set<Object> getTags() { + return ImmutableSet.copyOf(tags); + } + + /** A list of configuration options that the entity supports. */ + public final List<SpecParameter<?>> getParameters() { - return ImmutableList.copyOf(parameters); ++ //Could be null after rebind ++ if (parameters != null) { ++ return ImmutableList.copyOf(parameters); ++ } else { ++ return ImmutableList.of(); ++ } + } + + // TODO Duplicates method in BasicEntityTypeRegistry and InternalEntityFactory.isNewStyleEntity + protected final void checkIsNewStyleImplementation(Class<?> implClazz) { + try { + implClazz.getConstructor(new Class[0]); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Implementation "+implClazz+" must have a no-argument constructor"); + } catch (SecurityException e) { + throw Exceptions.propagate(e); + } + + if (implClazz.isInterface()) throw new IllegalStateException("Implementation "+implClazz+" is an interface, but must be a non-abstract class"); + if (Modifier.isAbstract(implClazz.getModifiers())) throw new IllegalStateException("Implementation "+implClazz+" is abstract, but must be a non-abstract class"); + } + + // TODO Duplicates method in BasicEntityTypeRegistry + protected final void checkIsImplementation(Class<?> val, Class<? super T> requiredInterface) { + if (!requiredInterface.isAssignableFrom(val)) throw new IllegalStateException("Implementation "+val+" does not implement "+requiredInterface.getName()); + if (val.isInterface()) throw new IllegalStateException("Implementation "+val+" is an interface, but must be a non-abstract class"); + if (Modifier.isAbstract(val.getModifiers())) throw new IllegalStateException("Implementation "+val+" is abstract, but must be a non-abstract class"); + } + + protected SpecT copyFrom(SpecT otherSpec) { + return displayName(otherSpec.getDisplayName()) + .configure(otherSpec.getConfig()) + .configure(otherSpec.getFlags()) + .tags(otherSpec.getTags()) + .catalogItemId(otherSpec.getCatalogItemId()) + .parameters(otherSpec.getParameters()); + } + + @Override + public boolean equals(Object obj) { + if (obj==null) return false; + if (!obj.getClass().equals(getClass())) return false; + AbstractBrooklynObjectSpec<?,?> other = (AbstractBrooklynObjectSpec<?,?>)obj; + if (!Objects.equal(getDisplayName(), other.getDisplayName())) return false; + if (!Objects.equal(getCatalogItemId(), other.getCatalogItemId())) return false; + if (!Objects.equal(getType(), other.getType())) return false; + if (!Objects.equal(getTags(), other.getTags())) return false; + if (!Objects.equal(getParameters(), other.getParameters())) return false; + return true; + } + + @Override + public int hashCode() { + return Objects.hashCode(getCatalogItemId(), getDisplayName(), getType(), getTags()); + } + + /** strings inserted as flags, config keys inserted as config keys; + * if you want to force one or the other, create a ConfigBag and convert to the appropriate map type */ + public SpecT configure(Map<?,?> val) { + for (Map.Entry<?, ?> entry: val.entrySet()) { + if (entry.getKey()==null) throw new NullPointerException("Null key not permitted"); + if (entry.getKey() instanceof CharSequence) + flags.put(entry.getKey().toString(), entry.getValue()); + else if (entry.getKey() instanceof ConfigKey<?>) + config.put((ConfigKey<?>)entry.getKey(), entry.getValue()); + else if (entry.getKey() instanceof HasConfigKey<?>) + config.put(((HasConfigKey<?>)entry.getKey()).getConfigKey(), entry.getValue()); + else { + log.warn("Spec "+this+" ignoring unknown config key "+entry.getKey()); + } + } + return self(); + } + + public SpecT configure(CharSequence key, Object val) { + flags.put(checkNotNull(key, "key").toString(), val); + return self(); + } + + public <V> SpecT configure(ConfigKey<V> key, V val) { + config.put(checkNotNull(key, "key"), val); + return self(); + } + + public <V> SpecT configureIfNotNull(ConfigKey<V> key, V val) { + return (val != null) ? configure(key, val) : self(); + } + + public <V> SpecT configure(ConfigKey<V> key, Task<? extends V> val) { + config.put(checkNotNull(key, "key"), val); + return self(); + } + + public <V> SpecT configure(HasConfigKey<V> key, V val) { + config.put(checkNotNull(key, "key").getConfigKey(), val); + return self(); + } + + public <V> SpecT configure(HasConfigKey<V> key, Task<? extends V> val) { + config.put(checkNotNull(key, "key").getConfigKey(), val); + return self(); + } + + public <V> SpecT removeConfig(ConfigKey<V> key) { + config.remove( checkNotNull(key, "key") ); + return self(); + } + + /** Clears the config map, removing any config previously set. */ + public void clearConfig() { + config.clear(); + } + + /** + * @return Read-only construction flags + * @see SetFromFlag declarations on the policy type + */ + public Map<String, ?> getFlags() { + return Collections.unmodifiableMap(flags); + } + + /** + * @return Read-only configuration values + */ + public Map<ConfigKey<?>, Object> getConfig() { + return Collections.unmodifiableMap(config); + } + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java ---------------------------------------------------------------------- diff --cc brooklyn-server/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java index 0000000,78c49d8..17a7fb3 mode 000000,100644..100644 --- a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java +++ b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java @@@ -1,0 -1,70 +1,78 @@@ + /* + * 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.api.typereg; + + import javax.annotation.Nullable; + + import org.apache.brooklyn.api.entity.Entity; + import org.apache.brooklyn.api.entity.EntitySpec; + import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; ++import org.apache.brooklyn.util.guava.Maybe; + ++import com.google.common.annotations.Beta; + import com.google.common.base.Predicate; + + + public interface BrooklynTypeRegistry { + + public enum RegisteredTypeKind { + /** a registered type which will create an {@link AbstractBrooklynObjectSpec} (e.g. {@link EntitySpec}) + * for the type registered (e.g. the {@link Entity} instance) */ + SPEC, + /** a registered type which will create the java type described */ + BEAN + // note: additional kinds should have the visitor in core/RegisteredTypeKindVisitor updated + // to flush out all places which want to implement support for all kinds + } + + Iterable<RegisteredType> getAll(); - Iterable<RegisteredType> getAll(Predicate<? super RegisteredType> filter); ++ Iterable<RegisteredType> getMatching(Predicate<? super RegisteredType> filter); + + /** @return The item matching the given given + * {@link RegisteredType#getSymbolicName() symbolicName} + * and optionally {@link RegisteredType#getVersion()}, + * taking the best version if the version is null or a default marker, + * returning null if no matches are found. */ + RegisteredType get(String symbolicName, String version); - /** as {@link #get(String, String)} but allows <code>"name:version"</code> - * (the {@link RegisteredType#getId()}) in addition to the unversioned name, - * using a default marker if no version can be inferred */ ++ /** as {@link #get(String, String)} but the given string here ++ * is allowed to match any of: ++ * <li>the given string as an ID including version (<code>"name:version"</code>) ++ * <li>the symbolic name unversioned, or ++ * <li>an alias */ + RegisteredType get(String symbolicNameWithOptionalVersion); - - // TODO remove -// /** as {@link #get(String, String)}, but applying the optionally supplied {@link RegisteredTypeLoadingContext} */ -// RegisteredType get(String symbolicName, String version, @Nullable RegisteredTypeLoadingContext context); -// /** as {@link #get(String)}, but applying the optionally supplied {@link RegisteredTypeLoadingContext} */ -// RegisteredType get(String symbolicNameWithOptionalVersion, @Nullable RegisteredTypeLoadingContext context); ++ ++ /** as {@link #get(String)} but further filtering for the additional context */ ++ public RegisteredType get(String symbolicNameOrAliasWithOptionalVersion, RegisteredTypeLoadingContext context); ++ /** returns a wrapper of the result of {@link #get(String, RegisteredTypeLoadingContext)} ++ * including a detailed message if absent */ ++ public Maybe<RegisteredType> getMaybe(String symbolicNameOrAliasWithOptionalVersion, RegisteredTypeLoadingContext context); + + // NB the seemingly more correct generics <T,SpecT extends AbstractBrooklynObjectSpec<T,SpecT>> + // cause compile errors, not in Eclipse, but in maven (?) - // TODO do these belong here, or in a separate master TypePlanTransformer ? see also BrooklynTypePlanTransformer ++ // TODO do these belong here, or in a separate master TypePlanTransformer ? see also BrooklynTypePlanTransformer ++ @Beta + <SpecT extends AbstractBrooklynObjectSpec<?,?>> SpecT createSpec(RegisteredType type, @Nullable RegisteredTypeLoadingContext optionalContext, @Nullable Class<SpecT> optionalSpecSuperType); ++ @Beta + <SpecT extends AbstractBrooklynObjectSpec<?,?>> SpecT createSpecFromPlan(@Nullable String planFormat, Object planData, @Nullable RegisteredTypeLoadingContext optionalContext, @Nullable Class<SpecT> optionalSpecSuperType); ++ @Beta + <T> T createBean(RegisteredType type, @Nullable RegisteredTypeLoadingContext optionalContext, @Nullable Class<T> optionalResultSuperType); ++ @Beta + <T> T createBeanFromPlan(String planFormat, Object planData, @Nullable RegisteredTypeLoadingContext optionalConstraint, @Nullable Class<T> optionalBeanSuperType); + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java ---------------------------------------------------------------------- diff --cc brooklyn-server/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java index 0000000,2674736..29b64d3 mode 000000,100644..100644 --- a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java +++ b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java @@@ -1,0 -1,92 +1,96 @@@ + /* + * 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.api.typereg; + + import java.util.Collection; + import java.util.Set; + -import javax.annotation.Nullable; - + import org.apache.brooklyn.api.entity.Entity; + import org.apache.brooklyn.api.entity.EntitySpec; + import org.apache.brooklyn.api.objs.BrooklynObject; + import org.apache.brooklyn.api.objs.Identifiable; + import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; + + import com.google.common.annotations.Beta; + + public interface RegisteredType extends Identifiable { + + @Override String getId(); + + RegisteredTypeKind getKind(); + + String getSymbolicName(); + String getVersion(); + + Collection<OsgiBundleWithUrl> getLibraries(); + + String getDisplayName(); + String getDescription(); + String getIconUrl(); + + /** @return all declared supertypes or super-interfaces of this registered type, + * consisting of a collection of {@link Class} or {@link RegisteredType} + * <p> + * This should normally include at least one {@link Class} object: + * For beans, this should include the java type that the {@link BrooklynTypeRegistry} will create. + * For specs, this should refer to the {@link BrooklynObject} type that the created spec will point at + * (e.g. the concrete {@link Entity}, not the {@link EntitySpec}). + * <p> + * This may not necessarily return the most specific java class or classes; + * such as if the concrete type is private and callers should know only about a particular public interface, + * or if precise type details are unavailable and all that is known at creation is some higher level interface/supertype + * (e.g. this may return {@link Entity} even though the spec points at a specific subclass, + * for instance because the YAML has not yet been parsed or OSGi bundles downloaded). + * <p> + * This may include other registered types such as marker interfaces. + */ + @Beta + Set<Object> getSuperTypes(); + + /** + * @return True if the item has been deprecated (i.e. its use is discouraged) + */ + boolean isDeprecated(); + + /** + * @return True if the item has been disabled (i.e. its use is forbidden, except for pre-existing apps) + */ + boolean isDisabled(); + ++ /** Alias words defined for this type */ ++ Set<String> getAliases(); ++ ++ /** Tags attached to this item */ ++ Set<Object> getTags(); ++ + /** @return implementation details, so that the framework can find a suitable {@link BrooklynTypePlanTransformer} + * which can then use this object to instantiate this type */ + TypeImplementationPlan getPlan(); + + public interface TypeImplementationPlan { + /** hint which {@link BrooklynTypePlanTransformer} instance(s) can be used, if known; + * this may be null if the relevant transformer was not declared when created, + * but in general we should look to determine the kind as early as possible + * and use that to retrieve the appropriate such transformer */ + String getPlanFormat(); + /** data for the implementation; may be more specific */ + Object getPlanData(); + } + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/PdpProcessor.java ---------------------------------------------------------------------- diff --cc brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/PdpProcessor.java index 0000000,8716aa5..ae42ee7 mode 000000,100644..100644 --- a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/PdpProcessor.java +++ b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/PdpProcessor.java @@@ -1,0 -1,186 +1,186 @@@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.apache.brooklyn.camp.spi.resolve; + + import java.io.InputStream; + import java.io.Reader; + import java.util.ArrayList; + import java.util.List; + import java.util.Map; + + import org.apache.brooklyn.camp.CampPlatform; + import org.apache.brooklyn.camp.spi.AssemblyTemplate; + import org.apache.brooklyn.camp.spi.instantiate.BasicAssemblyTemplateInstantiator; + import org.apache.brooklyn.camp.spi.pdp.Artifact; + import org.apache.brooklyn.camp.spi.pdp.AssemblyTemplateConstructor; + import org.apache.brooklyn.camp.spi.pdp.DeploymentPlan; + import org.apache.brooklyn.camp.spi.pdp.Service; + import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationContext; + import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode; + import org.apache.brooklyn.util.collections.MutableMap; + import org.apache.brooklyn.util.exceptions.Exceptions; + import org.apache.brooklyn.util.stream.Streams; + import org.apache.brooklyn.util.yaml.Yamls; + import org.apache.commons.compress.archivers.ArchiveEntry; + import org.apache.commons.compress.archivers.ArchiveInputStream; + import org.apache.commons.compress.archivers.ArchiveStreamFactory; + import org.yaml.snakeyaml.error.YAMLException; + + import com.google.common.annotations.VisibleForTesting; + + public class PdpProcessor { + + final CampPlatform campPlatform; + + final List<PdpMatcher> matchers = new ArrayList<PdpMatcher>(); + final List<PlanInterpreter> interpreters = new ArrayList<PlanInterpreter>(); + + public PdpProcessor(CampPlatform campPlatform) { + this.campPlatform = campPlatform; + } + + public DeploymentPlan parseDeploymentPlan(Reader yaml) { + return parseDeploymentPlan(Streams.readFully(yaml)); + } + + @SuppressWarnings("unchecked") + public DeploymentPlan parseDeploymentPlan(String yaml) { + Iterable<Object> template = Yamls.parseAll(yaml); + + Map<String, Object> dpRootUninterpreted = null; + try { + dpRootUninterpreted = Yamls.getAs(template, Map.class); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + throw new YAMLException("Plan not in acceptable format: "+(e.getMessage()!=null ? e.getMessage() : ""+e), e); + } + Map<String, Object> dpRootInterpreted = applyInterpreters(dpRootUninterpreted); + + return DeploymentPlan.of(dpRootInterpreted, yaml); + } + + /** create and return an AssemblyTemplate based on the given DP (yaml) */ + public AssemblyTemplate registerDeploymentPlan(Reader yaml) { + DeploymentPlan plan = parseDeploymentPlan(yaml); + return registerDeploymentPlan(plan); + } + + /** applies matchers to the given deployment plan to create an assembly template */ + public AssemblyTemplate registerDeploymentPlan(DeploymentPlan plan) { + AssemblyTemplateConstructor atc = new AssemblyTemplateConstructor(campPlatform); + + if (plan.getName()!=null) atc.name(plan.getName()); + if (plan.getDescription()!=null) atc.description(plan.getDescription()); + if (plan.getSourceCode()!=null) atc.sourceCode(plan.getSourceCode()); + // nothing done with origin just now... + + if (plan.getServices()!=null) { + for (Service svc: plan.getServices()) { + applyMatchers(svc, atc); + } + } + + if (plan.getArtifacts()!=null) { + for (Artifact art: plan.getArtifacts()) { + applyMatchers(art, atc); + } + } + + Map<String, Object> attrs = plan.getCustomAttributes(); + if (attrs!=null && !attrs.isEmpty()) { + Map<String, Object> customAttrs = attrs; + if (customAttrs.containsKey("id")) { + // id shouldn't be leaking to entities, see InternalEntityFactory.createEntityAndDescendantsUninitialized. + // If set it will go through to the spec because AbstractBrooklynObject has @SetFromFlag("id") on the id property. + // Follows logic in BrooklynEntityMatcher.apply(...). + customAttrs = MutableMap.copyOf(attrs); + customAttrs.put("planId", customAttrs.remove("id")); + } + atc.addCustomAttributes(customAttrs); + } + + if (atc.getInstantiator()==null) + // set a default instantiator which just invokes the component's instantiators + // (or throws unsupported exceptions, currently!) + atc.instantiator(BasicAssemblyTemplateInstantiator.class); + + return atc.commit(); + } + + public AssemblyTemplate registerPdpFromArchive(InputStream archiveInput) { + try { + ArchiveInputStream input = new ArchiveStreamFactory() + .createArchiveInputStream(archiveInput); + + while (true) { + ArchiveEntry entry = input.getNextEntry(); + if (entry==null) break; + // TODO unpack entry, create a space on disk holding the archive ? + } + + // use yaml... + throw new UnsupportedOperationException("in progress"); + + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + + // ---------------------------- + + public void addMatcher(PdpMatcher m) { + // TODO a list is a crude way to do matching ... but good enough to start + matchers.add(m); + } + + public List<PdpMatcher> getMatchers() { + return matchers; + } + + + protected void applyMatchers(Object deploymentPlanItem, AssemblyTemplateConstructor atc) { + for (PdpMatcher matcher: getMatchers()) { + if (matcher.accepts(deploymentPlanItem)) { + // TODO first accepting is a crude way to do matching ... but good enough to start + if (matcher.apply(deploymentPlanItem, atc)) + return; + } + } + throw new IllegalArgumentException("Deployment plan item cannot be matched. Please check your YAML. Item: "+deploymentPlanItem); + } + + // ---------------------------- + + public void addInterpreter(PlanInterpreter interpreter) { + interpreters.add(interpreter); + } + + /** returns a DeploymentPlan object which is the result of running the interpretation + * (with all interpreters) against the supplied deployment plan YAML object, + * essentially a post-parse processing step before matching */ + @SuppressWarnings("unchecked") + @VisibleForTesting - public Map<String, Object> applyInterpreters(Map<String, Object> originalDeploymentPlan) { ++ public Map<String, Object> applyInterpreters(Map<String, ?> originalDeploymentPlan) { + PlanInterpretationNode interpretation = new PlanInterpretationNode( + new PlanInterpretationContext(originalDeploymentPlan, interpreters)); + return (Map<String, Object>) interpretation.getNewValue(); + } + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationContext.java ---------------------------------------------------------------------- diff --cc brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationContext.java index 0000000,08053cb..26822aa mode 000000,100644..100644 --- a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationContext.java +++ b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationContext.java @@@ -1,0 -1,152 +1,152 @@@ + /* + * 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.camp.spi.resolve.interpret; + + import java.util.List; + import java.util.Map; + + import org.apache.brooklyn.camp.spi.resolve.PlanInterpreter; + + import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableMap; + + public class PlanInterpretationContext { + + private final Map<String,Object> originalDeploymentPlan; + private final List<PlanInterpreter> interpreters; + private final PlanInterpreter allInterpreter; + - public PlanInterpretationContext(Map<String,Object> originalDeploymentPlan, List<PlanInterpreter> interpreters) { ++ public PlanInterpretationContext(Map<String,?> originalDeploymentPlan, List<PlanInterpreter> interpreters) { + super(); + this.originalDeploymentPlan = ImmutableMap.copyOf(originalDeploymentPlan); + this.interpreters = ImmutableList.copyOf(interpreters); + this.allInterpreter = new PlanInterpreter() { + @Override + public boolean isInterestedIn(PlanInterpretationNode node) { + return true; + } + + @Override + public void applyYamlPrimitive(PlanInterpretationNode node) { + for (PlanInterpreter i: PlanInterpretationContext.this.interpreters) { + if (node.isExcluded()) + break; + if (i.isInterestedIn(node)) { + i.applyYamlPrimitive(node); + } + } + } + + @Override + public boolean applyMapBefore(PlanInterpretationNode node, Map<Object, Object> mapIn) { + boolean result = true; + for (PlanInterpreter i: PlanInterpretationContext.this.interpreters) { + if (node.isExcluded()) + break; + if (i.isInterestedIn(node)) { + boolean ri= i.applyMapBefore(node, mapIn); + result &= ri; + } + } + return result; + } + + @Override + public boolean applyMapEntry(PlanInterpretationNode node, Map<Object, Object> mapIn, Map<Object, Object> mapOut, + PlanInterpretationNode key, PlanInterpretationNode value) { + boolean result = true; + for (PlanInterpreter i: PlanInterpretationContext.this.interpreters) { + if (node.isExcluded()) + break; + if (i.isInterestedIn(key)) { + boolean ri = i.applyMapEntry(node, mapIn, mapOut, key, value); + result &= ri; + } + } + return result; + } + + @Override + public void applyMapAfter(PlanInterpretationNode node, Map<Object, Object> mapIn, Map<Object, Object> mapOut) { + for (PlanInterpreter i: PlanInterpretationContext.this.interpreters) { + if (node.isExcluded()) + break; + if (i.isInterestedIn(node)) { + i.applyMapAfter(node, mapIn, mapOut); + } + } + } + + @Override + public boolean applyListBefore(PlanInterpretationNode node, List<Object> listIn) { + boolean result = true; + for (PlanInterpreter i: PlanInterpretationContext.this.interpreters) { + if (node.isExcluded()) + break; + if (i.isInterestedIn(node)) { + boolean ri = i.applyListBefore(node, listIn); + result &= ri; + } + } + return result; + } + + @Override + public boolean applyListEntry(PlanInterpretationNode node, List<Object> listIn, List<Object> listOut, + PlanInterpretationNode value) { + boolean result = true; + for (PlanInterpreter i: PlanInterpretationContext.this.interpreters) { + if (node.isExcluded()) + break; + if (i.isInterestedIn(value)) { + boolean ri = i.applyListEntry(node, listIn, listOut, value); + result &= ri; + } + } + return result; + } + + @Override + public void applyListAfter(PlanInterpretationNode node, List<Object> listIn, List<Object> listOut) { + for (PlanInterpreter i: PlanInterpretationContext.this.interpreters) { + if (node.isExcluded()) + break; + if (i.isInterestedIn(node)) { + i.applyListAfter(node, listIn, listOut); + } + } + } + + }; + } + + /** returns an interpreter which recurses through all interpreters */ + PlanInterpreter getAllInterpreter() { + return allInterpreter; + } + + public Map<String,Object> getOriginalDeploymentPlan() { + return originalDeploymentPlan; + } + + public List<PlanInterpreter> getInterpreters() { + return interpreters; + } + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/BrooklynCampPlatform.java ---------------------------------------------------------------------- diff --cc brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/BrooklynCampPlatform.java index 0000000,14a7c59..7290c24 mode 000000,100644..100644 --- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/BrooklynCampPlatform.java +++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/BrooklynCampPlatform.java @@@ -1,0 -1,77 +1,103 @@@ + /* + * 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.camp.brooklyn; + ++import static com.google.common.base.Preconditions.checkState; ++ ++import java.util.Map; ++ + import org.apache.brooklyn.api.mgmt.ManagementContext; + import org.apache.brooklyn.api.mgmt.ManagementContext.PropertiesReloadListener; + import org.apache.brooklyn.camp.AggregatingCampPlatform; + import org.apache.brooklyn.camp.CampPlatform; + import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityMatcher; + import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslInterpreter; + import org.apache.brooklyn.camp.brooklyn.spi.platform.BrooklynImmutableCampPlatform; + import org.apache.brooklyn.camp.spi.PlatformRootSummary; -import org.apache.brooklyn.core.internal.BrooklynProperties; + import org.apache.brooklyn.core.mgmt.HasBrooklynManagementContext; ++import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; ++import org.apache.brooklyn.core.mgmt.internal.CampYamlParser; ++ ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableSet; + + /** {@link CampPlatform} implementation which includes Brooklyn entities + * (via {@link BrooklynImmutableCampPlatform}) + * and allows customisation / additions */ + public class BrooklynCampPlatform extends AggregatingCampPlatform implements HasBrooklynManagementContext { + + private final ManagementContext bmc; + + public BrooklynCampPlatform(PlatformRootSummary root, ManagementContext managementContext) { + super(root); + addPlatform(new BrooklynImmutableCampPlatform(root, managementContext)); + + this.bmc = managementContext; + + addMatchers(); + addInterpreters(); + + managementContext.addPropertiesReloadListener(new PropertiesReloadListener() { + private static final long serialVersionUID = -3739276553334749184L; + @Override public void reloaded() { + setConfigKeyAtManagmentContext(); + } + }); + } + + // --- brooklyn setup + + @Override + public ManagementContext getBrooklynManagementContext() { + return bmc; + } + + protected void addMatchers() { + // TODO artifacts + pdp().addMatcher(new BrooklynEntityMatcher(bmc)); + } + + protected void addInterpreters() { + pdp().addInterpreter(new BrooklynDslInterpreter()); + } + + public BrooklynCampPlatform setConfigKeyAtManagmentContext() { - ((BrooklynProperties)bmc.getConfig()).put(BrooklynCampConstants.CAMP_PLATFORM, this); ++ ((ManagementContextInternal)bmc).getBrooklynProperties().put(BrooklynCampConstants.CAMP_PLATFORM, this); ++ ((ManagementContextInternal)bmc).getBrooklynProperties().put(CampYamlParser.YAML_PARSER_KEY, new YamlParserImpl(this)); + return this; + } - ++ ++ public static class YamlParserImpl implements CampYamlParser { ++ private final BrooklynCampPlatform platform; ++ ++ YamlParserImpl(BrooklynCampPlatform platform) { ++ this.platform = platform; ++ } ++ ++ public Map<String, Object> parse(Map<String, Object> map) { ++ return platform.pdp().applyInterpreters(map); ++ } ++ ++ public Object parse(String val) { ++ Map<String, Object> result = platform.pdp().applyInterpreters(ImmutableMap.of("dummyKey", val)); ++ checkState(result.keySet().equals(ImmutableSet.of("dummyKey")), "expected single result, but got %s", result); ++ return result.get("dummyKey"); ++ } ++ } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java ---------------------------------------------------------------------- diff --cc brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java index 0000000,0147b9d..6734875 mode 000000,100644..100644 --- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java +++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java @@@ -1,0 -1,211 +1,213 @@@ + /* + * 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.camp.brooklyn.spi.creation; + + import java.util.List; + import java.util.Map; + + import org.apache.brooklyn.api.entity.EntityInitializer; + import org.apache.brooklyn.api.entity.EntitySpec; + import org.apache.brooklyn.api.mgmt.ManagementContext; + 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.Enricher; + import org.apache.brooklyn.api.sensor.EnricherSpec; + import org.apache.brooklyn.api.typereg.RegisteredType; + import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys; + import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynYamlTypeInstantiator.InstantiatorFromKey; + import org.apache.brooklyn.core.objs.BasicSpecParameter; + import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; + import org.apache.brooklyn.core.typereg.RegisteredTypes; + import org.apache.brooklyn.util.collections.MutableList; + import org.apache.brooklyn.util.core.config.ConfigBag; ++import org.apache.brooklyn.util.guava.Maybe; + + import com.google.common.annotations.Beta; + import com.google.common.collect.ImmutableList; + + /** + * Pattern for resolving "decorations" on service specs / entity specs, such as policies, enrichers, etc. + * @since 0.7.0 + */ + @Beta + public abstract class BrooklynEntityDecorationResolver<DT> { + + public final BrooklynYamlTypeInstantiator.Factory instantiator; + + protected BrooklynEntityDecorationResolver(BrooklynYamlTypeInstantiator.Factory instantiator) { + this.instantiator = instantiator; + } + + public abstract void decorate(EntitySpec<?> entitySpec, ConfigBag attrs); + + protected List<? extends DT> buildListOfTheseDecorationsFromEntityAttributes(ConfigBag attrs) { + Object value = getDecorationAttributeJsonValue(attrs); + if (value==null) return MutableList.of(); + if (value instanceof Iterable) { + return buildListOfTheseDecorationsFromIterable((Iterable<?>)value); + } else { + // in future may support types other than iterables here, + // e.g. a map short form where the key is the type + throw new IllegalArgumentException(getDecorationKind()+" body should be iterable, not " + value.getClass()); + } + } + + protected Map<?,?> checkIsMap(Object decorationJson) { + if (!(decorationJson instanceof Map)) + throw new IllegalArgumentException(getDecorationKind()+" value must be a Map, not " + + (decorationJson==null ? null : decorationJson.getClass()) ); + return (Map<?,?>) decorationJson; + } + + protected List<DT> buildListOfTheseDecorationsFromIterable(Iterable<?> value) { + List<DT> decorations = MutableList.of(); + for (Object decorationJson: value) + addDecorationFromJsonMap(checkIsMap(decorationJson), decorations); + return decorations; + } + + protected abstract String getDecorationKind(); + protected abstract Object getDecorationAttributeJsonValue(ConfigBag attrs); + + /** creates and adds decorations from the given json to the given collection; + * default impl requires a map and calls {@link #addDecorationFromJsonMap(Map, List)} */ + protected void addDecorationFromJson(Object decorationJson, List<DT> decorations) { + addDecorationFromJsonMap(checkIsMap(decorationJson), decorations); + } + protected abstract void addDecorationFromJsonMap(Map<?,?> decorationJson, List<DT> decorations); + + + public static class PolicySpecResolver extends BrooklynEntityDecorationResolver<PolicySpec<?>> { + + public PolicySpecResolver(BrooklynYamlTypeInstantiator.Factory loader) { super(loader); } + @Override protected String getDecorationKind() { return "Policy"; } + + @Override + public void decorate(EntitySpec<?> entitySpec, ConfigBag attrs) { + entitySpec.policySpecs(buildListOfTheseDecorationsFromEntityAttributes(attrs)); + } + + @Override + protected Object getDecorationAttributeJsonValue(ConfigBag attrs) { + return attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_POLICIES); + } + + @Override + protected void addDecorationFromJsonMap(Map<?, ?> decorationJson, List<PolicySpec<?>> decorations) { + InstantiatorFromKey decoLoader = instantiator.from(decorationJson).prefix("policy"); + + String policyType = decoLoader.getTypeName().get(); + ManagementContext mgmt = instantiator.loader.getManagementContext(); + - RegisteredType item = RegisteredTypes.validate(mgmt.getTypeRegistry().get(policyType), RegisteredTypeLoadingContexts.spec(Policy.class)); ++ Maybe<RegisteredType> item = RegisteredTypes.tryValidate(mgmt.getTypeRegistry().get(policyType), RegisteredTypeLoadingContexts.spec(Policy.class)); + PolicySpec<?> spec; - if (item!=null) { - spec = mgmt.getTypeRegistry().createSpec(item, null, PolicySpec.class); ++ if (!item.isNull()) { ++ // throw error if absent for any reason other than null ++ spec = mgmt.getTypeRegistry().createSpec(item.get(), null, PolicySpec.class); + } else { + Class<? extends Policy> type = decoLoader.getType(Policy.class); + spec = PolicySpec.create(type) + .parameters(BasicSpecParameter.fromClass(mgmt, type)); + } + spec.configure( decoLoader.getConfigMap() ); + decorations.add(spec); + } + } + + public static class EnricherSpecResolver extends BrooklynEntityDecorationResolver<EnricherSpec<?>> { + + public EnricherSpecResolver(BrooklynYamlTypeInstantiator.Factory loader) { super(loader); } + @Override protected String getDecorationKind() { return "Enricher"; } + + @Override + public void decorate(EntitySpec<?> entitySpec, ConfigBag attrs) { + entitySpec.enricherSpecs(buildListOfTheseDecorationsFromEntityAttributes(attrs)); + } + + @Override + protected Object getDecorationAttributeJsonValue(ConfigBag attrs) { + return attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_ENRICHERS); + } + + @Override + protected void addDecorationFromJsonMap(Map<?, ?> decorationJson, List<EnricherSpec<?>> decorations) { + InstantiatorFromKey decoLoader = instantiator.from(decorationJson).prefix("enricher"); + Class<? extends Enricher> type = decoLoader.getType(Enricher.class); + decorations.add(EnricherSpec.create(type) + .configure(decoLoader.getConfigMap()) + .parameters(BasicSpecParameter.fromClass(instantiator.loader.getManagementContext(), type))); + } + } + + public static class InitializerResolver extends BrooklynEntityDecorationResolver<EntityInitializer> { + + public InitializerResolver(BrooklynYamlTypeInstantiator.Factory loader) { super(loader); } + @Override protected String getDecorationKind() { return "Entity initializer"; } + + @Override + public void decorate(EntitySpec<?> entitySpec, ConfigBag attrs) { + entitySpec.addInitializers(buildListOfTheseDecorationsFromEntityAttributes(attrs)); + } + + @Override + protected Object getDecorationAttributeJsonValue(ConfigBag attrs) { + return attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_INITIALIZERS); + } + + @Override + protected void addDecorationFromJsonMap(Map<?, ?> decorationJson, List<EntityInitializer> decorations) { + decorations.add(instantiator.from(decorationJson).prefix("initializer").newInstance(EntityInitializer.class)); + } + } + + // Not much value from extending from BrooklynEntityDecorationResolver, but let's not break the convention + public static class SpecParameterResolver extends BrooklynEntityDecorationResolver<SpecParameter<?>> { + + protected SpecParameterResolver(BrooklynYamlTypeInstantiator.Factory instantiator) { super(instantiator); } + @Override protected String getDecorationKind() { return "Spec Parameter initializer"; } + + @Override + public void decorate(EntitySpec<?> entitySpec, ConfigBag attrs) { + List<? extends SpecParameter<?>> explicitParams = buildListOfTheseDecorationsFromEntityAttributes(attrs); + if (!explicitParams.isEmpty()) { + entitySpec.parameters(explicitParams); + } + if (entitySpec.getParameters().isEmpty()) { + entitySpec.parameters(BasicSpecParameter.fromSpec(instantiator.loader.getManagementContext(), entitySpec)); + } + } + + @Override + protected List<SpecParameter<?>> buildListOfTheseDecorationsFromIterable(Iterable<?> value) { + return BasicSpecParameter.fromConfigList(ImmutableList.copyOf(value), instantiator.loader); + } + + @Override + protected Object getDecorationAttributeJsonValue(ConfigBag attrs) { + return attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_PARAMETERS); + } + + @Override + protected void addDecorationFromJsonMap(Map<?, ?> decorationJson, List<SpecParameter<?>> decorations) { + throw new IllegalStateException("Not called"); + } + } + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java ---------------------------------------------------------------------- diff --cc brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java index 0000000,06dfaa3..909564c mode 000000,100644..100644 --- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java +++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java @@@ -1,0 -1,174 +1,172 @@@ + /* + * 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.camp.brooklyn; + + import java.io.Reader; + import java.io.StringReader; + import java.util.Set; + + import org.apache.brooklyn.api.catalog.BrooklynCatalog; + 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.api.mgmt.Task; + import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer; + import org.apache.brooklyn.core.catalog.internal.CatalogUtils; + import org.apache.brooklyn.core.entity.Entities; + import org.apache.brooklyn.core.entity.trait.Startable; + import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; + import org.apache.brooklyn.core.mgmt.EntityManagementUtils; + import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; + import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; + import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; + import org.apache.brooklyn.util.collections.MutableMap; + import org.apache.brooklyn.util.core.ResourceUtils; + import org.apache.brooklyn.util.stream.Streams; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.testng.annotations.AfterMethod; + import org.testng.annotations.BeforeMethod; + + import com.google.common.base.Joiner; + + public abstract class AbstractYamlTest { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractYamlTest.class); + protected static final String TEST_VERSION = "0.1.2"; + + private ManagementContext brooklynMgmt; + protected BrooklynCatalog catalog; + protected BrooklynCampPlatform platform; + protected BrooklynCampPlatformLauncherNoServer launcher; + private boolean forceUpdate; + + public AbstractYamlTest() { + super(); + } + + protected ManagementContext mgmt() { return brooklynMgmt; } + + @BeforeMethod(alwaysRun = true) + public void setUp() { + forceUpdate = false; + launcher = new BrooklynCampPlatformLauncherNoServer() { + @Override + protected LocalManagementContext newMgmtContext() { + return newTestManagementContext(); + } + }; + launcher.launch(); + brooklynMgmt = launcher.getBrooklynMgmt(); + catalog = brooklynMgmt.getCatalog(); + platform = launcher.getCampPlatform(); + } + + protected LocalManagementContext newTestManagementContext() { + // TODO they don't all need osgi, just a few do, so could speed it up by specifying when they do + return LocalManagementContextForTests.newInstanceWithOsgi(); + } + + @AfterMethod(alwaysRun = true) + public void tearDown() { + if (brooklynMgmt != null) Entities.destroyAll(brooklynMgmt); + if (launcher != null) launcher.stopServers(); + } + + protected void waitForApplicationTasks(Entity app) { + Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(brooklynMgmt.getExecutionManager(), app); + getLogger().info("Waiting on " + tasks.size() + " task(s)"); + for (Task<?> t : tasks) { + t.blockUntilEnded(); + } + } + + protected Reader loadYaml(String yamlFileName, String ...extraLines) throws Exception { + String input = new ResourceUtils(this).getResourceAsString(yamlFileName).trim(); + StringBuilder builder = new StringBuilder(input); + for (String l: extraLines) + builder.append("\n").append(l); + return new StringReader(builder.toString()); + } + + protected Entity createAndStartApplication(String... multiLineYaml) throws Exception { + return createAndStartApplication(joinLines(multiLineYaml)); + } + - protected Entity createAndStartApplication(String input) throws Exception { - return createAndStartApplication(new StringReader(input)); ++ protected Entity createAndStartApplication(Reader input) throws Exception { ++ return createAndStartApplication(Streams.readFully(input)); + } + - protected Entity createAndStartApplication(Reader input) throws Exception { ++ protected Entity createAndStartApplication(String input) throws Exception { + EntitySpec<?> spec = - mgmt().getTypeRegistry().createSpecFromPlan(CampTypePlanTransformer.FORMAT, Streams.readFully(input), RegisteredTypeLoadingContexts.spec(Application.class), EntitySpec.class); ++ mgmt().getTypeRegistry().createSpecFromPlan(CampTypePlanTransformer.FORMAT, input, RegisteredTypeLoadingContexts.spec(Application.class), EntitySpec.class); + final Entity app = brooklynMgmt.getEntityManager().createEntity(spec); + // start the app (happens automatically if we use camp to instantiate, but not if we use crate spec approach) + app.invoke(Startable.START, MutableMap.<String,String>of()).get(); + return app; + } + + protected Entity createStartWaitAndLogApplication(Reader input) throws Exception { + Entity app = createAndStartApplication(input); + waitForApplicationTasks(app); - + getLogger().info("App started: "+app); - + return app; + } + + protected EntitySpec<?> createAppEntitySpec(String... yaml) { + return EntityManagementUtils.createEntitySpecForApplication(mgmt(), joinLines(yaml)); + } + + protected void addCatalogItems(Iterable<String> catalogYaml) { + addCatalogItems(joinLines(catalogYaml)); + } + + protected void addCatalogItems(String... catalogYaml) { + addCatalogItems(joinLines(catalogYaml)); + } + + protected void addCatalogItems(String catalogYaml) { + mgmt().getCatalog().addItems(catalogYaml, forceUpdate); + } + + protected void deleteCatalogEntity(String catalogItem) { + mgmt().getCatalog().deleteCatalogItem(catalogItem, TEST_VERSION); + } + + protected Logger getLogger() { + return LOG; + } + + protected String joinLines(Iterable<String> catalogYaml) { + return Joiner.on("\n").join(catalogYaml); + } + + protected String joinLines(String... catalogYaml) { + return Joiner.on("\n").join(catalogYaml); + } + + protected String ver(String id) { + return CatalogUtils.getVersionedId(id, TEST_VERSION); + } + + public void forceCatalogUpdate() { + forceUpdate = true; + } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigYamlTest.java ---------------------------------------------------------------------- diff --cc brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigYamlTest.java index 0000000,e075080..210f158 mode 000000,100644..100644 --- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigYamlTest.java +++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigYamlTest.java @@@ -1,0 -1,202 +1,218 @@@ + /* + * 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.camp.brooklyn; + + import static org.testng.Assert.assertEquals; + import static org.testng.Assert.fail; + + import java.io.StringReader; + import java.util.Map; + + import com.google.common.collect.Iterables; + import org.apache.brooklyn.api.entity.Entity; ++import org.apache.brooklyn.api.mgmt.ExecutionContext; + import org.apache.brooklyn.api.mgmt.ManagementContext; + import org.apache.brooklyn.config.ConfigKey; + import org.apache.brooklyn.core.config.ConfigKeys; + import org.apache.brooklyn.core.config.external.AbstractExternalConfigSupplier; + import org.apache.brooklyn.core.config.external.ExternalConfigSupplier; + import org.apache.brooklyn.core.internal.BrooklynProperties; ++import org.apache.brooklyn.core.location.AbstractLocation; + import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; ++import org.apache.brooklyn.core.mgmt.internal.CampYamlParser; + import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; + import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; + import org.apache.brooklyn.core.test.entity.TestApplication; + import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; ++import org.apache.brooklyn.util.core.task.DeferredSupplier; ++import org.apache.brooklyn.util.core.task.Tasks; + import org.apache.brooklyn.util.exceptions.Exceptions; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.testng.annotations.Test; + + import com.google.common.base.Joiner; + + @Test + public class ExternalConfigYamlTest extends AbstractYamlTest { + private static final Logger log = LoggerFactory.getLogger(ExternalConfigYamlTest.class); + + @Override + protected LocalManagementContext newTestManagementContext() { + BrooklynProperties props = BrooklynProperties.Factory.newEmpty(); + props.put("brooklyn.external.myprovider", MyExternalConfigSupplier.class.getName()); + props.put("brooklyn.external.myprovider.mykey", "myval"); + props.put("brooklyn.external.myproviderWithoutMapArg", MyExternalConfigSupplierWithoutMapArg.class.getName()); + + return LocalManagementContextForTests.builder(true) + .useProperties(props) + .build(); + } + + @Test ++ public void testCampYamlParserHandlesExternalisedConfig() throws Exception { ++ CampYamlParser parser = mgmt().getConfig().getConfig(CampYamlParser.YAML_PARSER_KEY); ++ ++ DeferredSupplier<?> supplier = (DeferredSupplier<?>) parser.parse("$brooklyn:external(\"myprovider\", \"mykey\")"); ++ ++ ExecutionContext exec = mgmt().getServerExecutionContext(); ++ String result = Tasks.resolveValue(supplier, String.class, exec); ++ assertEquals(result, "myval"); ++ } ++ ++ @Test + public void testExternalisedConfigReferencedFromYaml() throws Exception { + ConfigKey<String> MY_CONFIG_KEY = ConfigKeys.newStringConfigKey("my.config.key"); + + String yaml = Joiner.on("\n").join( + "services:", + "- serviceType: org.apache.brooklyn.core.test.entity.TestApplication", + " brooklyn.config:", + " my.config.key: $brooklyn:external(\"myprovider\", \"mykey\")"); + + TestApplication app = (TestApplication) createAndStartApplication(new StringReader(yaml)); + waitForApplicationTasks(app); + + assertEquals(app.getConfig(MY_CONFIG_KEY), "myval"); + } + + @Test + public void testExternalisedLocationConfigReferencedFromYaml() throws Exception { + ConfigKey<String> MY_CONFIG_KEY = ConfigKeys.newStringConfigKey("my.config.key"); + + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestApplication", + "location:", + " localhost:", + " my.config.key: $brooklyn:external(\"myprovider\", \"mykey\")"); + + TestApplication app = (TestApplication) createAndStartApplication(new StringReader(yaml)); + waitForApplicationTasks(app); + assertEquals(Iterables.getOnlyElement( app.getLocations() ).config().get(MY_CONFIG_KEY), "myval"); + } + + @Test(groups="Integration") + public void testExternalisedLocationConfigSetViaProvisioningPropertiesReferencedFromYaml() throws Exception { + String yaml = Joiner.on("\n").join( + "services:", + "- type: "+EmptySoftwareProcess.class.getName(), + " provisioning.properties:", + " credential: $brooklyn:external(\"myprovider\", \"mykey\")", + "location: localhost"); + + Entity app = createAndStartApplication(new StringReader(yaml)); + waitForApplicationTasks(app); + Entity entity = Iterables.getOnlyElement( app.getChildren() ); + assertEquals(Iterables.getOnlyElement( entity.getLocations() ).config().get(CloudLocationConfig.ACCESS_CREDENTIAL), "myval"); + } + + @Test + public void testExternalisedConfigFromSupplierWithoutMapArg() throws Exception { + ConfigKey<String> MY_CONFIG_KEY = ConfigKeys.newStringConfigKey("my.config.key"); + + String yaml = Joiner.on("\n").join( + "services:", + "- serviceType: org.apache.brooklyn.core.test.entity.TestApplication", + " brooklyn.config:", + " my.config.key: $brooklyn:external(\"myproviderWithoutMapArg\", \"mykey\")"); + + TestApplication app = (TestApplication) createAndStartApplication(new StringReader(yaml)); + waitForApplicationTasks(app); + + assertEquals(app.getConfig(MY_CONFIG_KEY), "myHardcodedVal"); + } + + @Test + public void testWhenExternalisedConfigSupplierDoesNotExist() throws Exception { + BrooklynProperties props = BrooklynProperties.Factory.newEmpty(); + props.put("brooklyn.external.myprovider", "wrong.classname.DoesNotExist"); + + try { + LocalManagementContextForTests.builder(true) + .useProperties(props) + .build(); + fail(); + } catch (Exception e) { + if (Exceptions.getFirstThrowableOfType(e, ClassNotFoundException.class) == null) { + throw e; + } + } + } + + @Test + public void testWhenExternalisedConfigSupplierDoesNotHavingRightConstructor() throws Exception { + BrooklynProperties props = BrooklynProperties.Factory.newEmpty(); + props.put("brooklyn.external.myprovider", MyExternalConfigSupplierWithWrongConstructor.class.getName()); + + try { + LocalManagementContext mgmt2 = LocalManagementContextForTests.builder(true) + .useProperties(props) + .build(); + mgmt2.terminate(); + fail(); + } catch (Exception e) { + if (!e.toString().contains("No matching constructor")) { + throw e; + } + } + } + + @Override + protected Logger getLogger() { + return log; + } + + public static class MyExternalConfigSupplier extends AbstractExternalConfigSupplier { + private final Map<String, String> conf; + + public MyExternalConfigSupplier(ManagementContext mgmt, String name, Map<String, String> conf) { + super(mgmt, name); + this.conf = conf; + } + + @Override public String get(String key) { + return conf.get(key); + } + } + + public static class MyExternalConfigSupplierWithoutMapArg extends AbstractExternalConfigSupplier { + public MyExternalConfigSupplierWithoutMapArg(ManagementContext mgmt, String name) { + super(mgmt, name); + } + + @Override public String get(String key) { + return key.equals("mykey") ? "myHardcodedVal" : null; + } + } + + public static class MyExternalConfigSupplierWithWrongConstructor implements ExternalConfigSupplier { + public MyExternalConfigSupplierWithWrongConstructor(double d) { + } + + @Override public String getName() { + return "myname"; + } + + @Override public String get(String key) { + return null; + } + } + + }
