/* * 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.location.multi; import static com.google.common.base.Preconditions.checkNotNull; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationRegistry; import org.apache.brooklyn.api.location.LocationResolver; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.core.location.BasicLocationRegistry; import org.apache.brooklyn.core.location.LocationConfigUtils; import org.apache.brooklyn.core.location.LocationPropertiesFromBrooklynProperties; import org.apache.brooklyn.core.location.Locations; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.KeyValueParser; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; public class MultiLocationResolver implements LocationResolver { private static final Logger LOG = LoggerFactory.getLogger(MultiLocationResolver.class); private static final String MULTI = "multi"; private static final Pattern PATTERN = Pattern.compile("(" + MULTI + "|" + MULTI.toUpperCase() + ")" + ":" + "\\((.*)\\)$"); private volatile ManagementContext managementContext; @Override public void init(ManagementContext managementContext) { this.managementContext = checkNotNull(managementContext, "managementContext"); } @Override public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { // FIXME pass all flags into the location Map globalProperties = registry.getProperties(); Map<String,?> locationArgs; if (spec.equalsIgnoreCase(MULTI)) { locationArgs = MutableMap.copyOf(locationFlags); } else { Matcher matcher = PATTERN.matcher(spec); if (!matcher.matches()) { throw new IllegalArgumentException("Invalid location '" + spec + "'; must specify something like multi(targets=named:foo)"); } String args = matcher.group(2); // TODO we are ignoring locationFlags after this (apart from named), looking only at these args locationArgs = KeyValueParser.parseMap(args); } String namedLocation = (String) locationFlags.get("named"); Map<String, Object> filteredProperties = new LocationPropertiesFromBrooklynProperties().getLocationProperties(null, namedLocation, globalProperties); MutableMap<String, Object> flags = MutableMap.<String, Object>builder() .putAll(filteredProperties) .putAll(locationFlags) .removeAll("named") .putAll(locationArgs).build(); if (locationArgs.get("targets") == null) { throw new IllegalArgumentException("target must be specified in single-machine spec"); } // TODO do we need to pass location flags etc into the children to ensure they are inherited? List<Location> targets = Lists.newArrayList(); Object targetSpecs = locationArgs.remove("targets"); try { if (targetSpecs instanceof String) { for (String targetSpec : JavaStringEscapes.unwrapJsonishListIfPossible((String)targetSpecs)) { targets.add(managementContext.getLocationRegistry().resolve(targetSpec)); } } else if (targetSpecs instanceof Iterable) { for (Object targetSpec: (Iterable<?>)targetSpecs) { if (targetSpec instanceof String) { targets.add(managementContext.getLocationRegistry().resolve((String)targetSpec)); } else { Set<?> keys = ((Map<?,?>)targetSpec).keySet(); if (keys.size()!=1) throw new IllegalArgumentException("targets supplied to MultiLocation must be a list of single-entry maps (got map of size "+keys.size()+": "+targetSpec+")"); Object key = keys.iterator().next(); Object flagsS = ((Map<?,?>)targetSpec).get(key); targets.add(managementContext.getLocationRegistry().resolve((String)key, (Map<?,?>)flagsS)); } } } else throw new IllegalArgumentException("targets must be supplied to MultiLocation, either as string spec or list of single-entry maps each being a location spec"); MultiLocation result = managementContext.getLocationManager().createLocation(LocationSpec.create(MultiLocation.class) .configure(flags) .configure("subLocations", targets) .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation))); // TODO Important workaround for BasicLocationRegistry.resolveForPeeking. // That creates a location (from the resolver) and immediately unmanages it. // The unmanage *must* remove all the targets created here; otherwise we have a leak. // Adding the targets as children achieves this. for (Location target : targets) { target.setParent(result); } return result; } catch (Exception e) { // Must clean up after ourselves: don't leak sub-locations on error if (LOG.isDebugEnabled()) LOG.debug("Problem resolving MultiLocation; cleaning up any sub-locations and rethrowing: "+e); for (Location target : targets) { Locations.unmanage(target); } throw Exceptions.propagate(e); } } @Override public String getPrefix() { return MULTI; } @Override public boolean accepts(String spec, LocationRegistry registry) { return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true); } }