/*
* 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 static com.google.common.base.Preconditions.checkNotNull;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
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.location.Location;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
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.typereg.RegisteredType;
import org.apache.brooklyn.camp.CampPlatform;
import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys;
import org.apache.brooklyn.camp.spi.AssemblyTemplate;
import org.apache.brooklyn.camp.spi.AssemblyTemplate.Builder;
import org.apache.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
import org.apache.brooklyn.camp.spi.pdp.DeploymentPlan;
import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker;
import org.apache.brooklyn.core.objs.BasicSpecParameter;
import org.apache.brooklyn.core.objs.BrooklynObjectInternal.ConfigurationSupportInternal;
import org.apache.brooklyn.entity.stock.BasicApplicationImpl;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.stream.Streams;
import org.apache.brooklyn.util.yaml.Yamls;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
/** package-private; as {@link RegisteredType} becomes standard hopefully we can remove this */
class CampInternalUtils {
static EntitySpec<? extends Application> createWrapperApp(AssemblyTemplate template, BrooklynClassLoadingContext loader) {
BrooklynComponentTemplateResolver resolver = BrooklynComponentTemplateResolver.Factory.newInstance(
loader, buildWrapperAppTemplate(template));
EntitySpec<Application> wrapperSpec = resolver.resolveSpec(ImmutableSet.<String>of());
resetSpecIfTemplateHasNoExplicitParameters(template, wrapperSpec);
// caller always sets WRAPPER_APP config; should we do it here?
return wrapperSpec;
}
static void resetSpecIfTemplateHasNoExplicitParameters(AssemblyTemplate template, EntitySpec<? extends Application> wrapperSpec) {
if (!template.getCustomAttributes().containsKey(BrooklynCampReservedKeys.BROOKLYN_PARAMETERS)) {
// Clear out default parameters (coming from the wrapper app's class) so they don't overwrite the entity's params on unwrap.
wrapperSpec.parameters(ImmutableList.<SpecParameter<?>>of());
}
}
private static AssemblyTemplate buildWrapperAppTemplate(AssemblyTemplate template) {
Builder<? extends AssemblyTemplate> builder = AssemblyTemplate.builder();
builder.type("brooklyn:" + BasicApplicationImpl.class.getName());
builder.id(template.getId());
builder.name(template.getName());
builder.sourceCode(template.getSourceCode());
for (Entry<String, Object> entry : template.getCustomAttributes().entrySet()) {
builder.customAttribute(entry.getKey(), entry.getValue());
}
builder.instantiator(template.getInstantiator());
AssemblyTemplate wrapTemplate = builder.build();
return wrapTemplate;
}
static AssemblyTemplateInstantiator getInstantiator(AssemblyTemplate at) {
try {
return at.getInstantiator().newInstance();
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
static AssemblyTemplate registerDeploymentPlan(String plan, BrooklynClassLoadingContext loader, CampPlatform camp) {
BrooklynLoaderTracker.setLoader(loader);
try {
return camp.pdp().registerDeploymentPlan(new StringReader(plan));
} finally {
BrooklynLoaderTracker.unsetLoader(loader);
}
}
static PolicySpec<?> createPolicySpec(String yamlPlan, BrooklynClassLoadingContext loader, Set<String> encounteredCatalogTypes) {
DeploymentPlan plan = makePlanFromYaml(loader.getManagementContext(), yamlPlan);
//Would ideally re-use the PolicySpecResolver
//but it is CAMP specific and there is no easy way to get hold of it.
Object policies = checkNotNull(plan.getCustomAttributes().get(BasicBrooklynCatalog.POLICIES_KEY), "policy config");
if (!(policies instanceof Iterable<?>)) {
throw new IllegalStateException("The value of " + BasicBrooklynCatalog.POLICIES_KEY + " must be an Iterable.");
}
Object policy = Iterables.getOnlyElement((Iterable<?>)policies);
return createPolicySpec(loader, policy, encounteredCatalogTypes);
}
@SuppressWarnings("unchecked")
static PolicySpec<?> createPolicySpec(BrooklynClassLoadingContext loader, Object policy, Set<String> encounteredCatalogTypes) {
Map<String, Object> itemMap;
if (policy instanceof String) {
itemMap = ImmutableMap.<String, Object>of("type", policy);
} else if (policy instanceof Map) {
itemMap = (Map<String, Object>) policy;
} else {
throw new IllegalStateException("Policy expected to be string or map. Unsupported object type " + policy.getClass().getName() + " (" + policy.toString() + ")");
}
String versionedId = (String) checkNotNull(Yamls.getMultinameAttribute(itemMap, "policy_type", "policyType", "type"), "policy type");
PolicySpec<? extends Policy> spec = resolvePolicySpec(versionedId, loader, encounteredCatalogTypes);
Map<String, Object> brooklynConfig = (Map<String, Object>) itemMap.get(BrooklynCampReservedKeys.BROOKLYN_CONFIG);
if (brooklynConfig != null) {
spec.configure(brooklynConfig);
}
List<?> parameters = (List<?>) itemMap.get(BrooklynCampReservedKeys.BROOKLYN_PARAMETERS);
initParameters(parameters, spec, loader);
return spec;
}
static LocationSpec<?> createLocationSpec(String yamlPlan, BrooklynClassLoadingContext loader, Set<String> encounteredTypes) {
DeploymentPlan plan = makePlanFromYaml(loader.getManagementContext(), yamlPlan);
Object locations = checkNotNull(plan.getCustomAttributes().get(BasicBrooklynCatalog.LOCATIONS_KEY), "location config");
if (!(locations instanceof Iterable<?>)) {
throw new IllegalStateException("The value of " + BasicBrooklynCatalog.LOCATIONS_KEY + " must be an Iterable.");
}
Object location = Iterables.getOnlyElement((Iterable<?>)locations);
return createLocationSpec(loader, location);
}
@SuppressWarnings("unchecked")
private static LocationSpec<?> createLocationSpec(BrooklynClassLoadingContext loader, Object location) {
Map<String, Object> itemMap;
if (location instanceof String) {
itemMap = ImmutableMap.<String, Object>of("type", location);
} else if (location instanceof Map) {
itemMap = (Map<String, Object>) location;
} else {
throw new IllegalStateException("Location expected to be string or map. Unsupported object type " + location.getClass().getName() + " (" + location.toString() + ")");
}
String type = (String) checkNotNull(Yamls.getMultinameAttribute(itemMap, "location_type", "locationType", "type"), "location type");
Map<String, Object> brooklynConfig = (Map<String, Object>) itemMap.get("brooklyn.config");
LocationSpec<?> locationSpec = resolveLocationSpec(type, brooklynConfig, loader);
List<?> explicitParams = (List<?>) itemMap.get(BrooklynCampReservedKeys.BROOKLYN_PARAMETERS);
initParameters(explicitParams, locationSpec, loader);
return locationSpec;
}
private static void initParameters(List<?> explicitParams, AbstractBrooklynObjectSpec<?, ?> spec, BrooklynClassLoadingContext loader) {
if (explicitParams != null) {
spec.parameters(BasicSpecParameter.fromConfigList(explicitParams, loader));
} else {
spec.parameters(BasicSpecParameter.fromSpec(loader.getManagementContext(), spec));
}
}
public static DeploymentPlan makePlanFromYaml(ManagementContext mgmt, String yaml) {
CampPlatform camp = getCampPlatform(mgmt);
return camp.pdp().parseDeploymentPlan(Streams.newReaderWithContents(yaml));
}
public static CampPlatform getCampPlatform(ManagementContext mgmt) {
CampPlatform result = mgmt.getConfig().getConfig(BrooklynCampConstants.CAMP_PLATFORM);
if (result!=null) {
return result;
} else {
throw new IllegalStateException("No CAMP Platform is registered with this Brooklyn management context.");
}
}
@SuppressWarnings("unchecked")
private static PolicySpec<? extends Policy> resolvePolicySpec(
String versionedId,
BrooklynClassLoadingContext loader,
Set<String> encounteredCatalogTypes) {
PolicySpec<? extends Policy> spec;
RegisteredType item = loader.getManagementContext().getTypeRegistry().get(versionedId);
if (item != null && !encounteredCatalogTypes.contains(item.getSymbolicName())) {
return loader.getManagementContext().getTypeRegistry().createSpec(item, null, PolicySpec.class);
} else {
// TODO-type-registry pass the loader in to the above, and allow it to load with the loader
spec = PolicySpec.create(loader.loadClass(versionedId, Policy.class));
}
return spec;
}
private static LocationSpec<?> resolveLocationSpec(
String type,
Map<String, Object> brooklynConfig,
BrooklynClassLoadingContext loader) {
Maybe<Class<? extends Location>> javaClass = loader.tryLoadClass(type, Location.class);
if (javaClass.isPresent()) {
LocationSpec<?> spec = LocationSpec.create(javaClass.get());
if (brooklynConfig != null) {
spec.configure(brooklynConfig);
}
return spec;
} else {
Maybe<Location> loc = loader.getManagementContext().getLocationRegistry().resolve(type, false, brooklynConfig);
if (loc.isPresent()) {
// TODO extensions?
Map<String, Object> locConfig = ((ConfigurationSupportInternal)loc.get().config()).getBag().getAllConfig();
Class<? extends Location> locType = loc.get().getClass();
Set<Object> locTags = loc.get().tags().getTags();
String locDisplayName = loc.get().getDisplayName();
return LocationSpec.create(locType)
.configure(locConfig)
.displayName(locDisplayName)
.tags(locTags);
} else {
throw new IllegalStateException("No class or resolver found for location type "+type);
}
}
}
}