/* * 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.typereg; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.TreeMap; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; 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.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; public class TypePlanTransformers { private static final Logger log = LoggerFactory.getLogger(TypePlanTransformers.class); private static Collection<BrooklynTypePlanTransformer> getAll() { return ImmutableList.copyOf(ServiceLoader.load(BrooklynTypePlanTransformer.class)); } private static Collection<Class<? extends BrooklynTypePlanTransformer>> OVERRIDE; @SafeVarargs @VisibleForTesting public synchronized static void forceAvailable(Class<? extends BrooklynTypePlanTransformer> ...classes) { OVERRIDE = Arrays.asList(classes); } public synchronized static void clearForced() { OVERRIDE = null; } public static Collection<BrooklynTypePlanTransformer> all(ManagementContext mgmt) { // TODO cache these in the TypeRegistry, looking for new ones periodically or supplying a way to register them Collection<Class<? extends BrooklynTypePlanTransformer>> override = OVERRIDE; Collection<BrooklynTypePlanTransformer> result = new ArrayList<BrooklynTypePlanTransformer>(); if (override!=null) { for (Class<? extends BrooklynTypePlanTransformer> o1: override) { try { result.add(o1.newInstance()); } catch (Exception e) { Exceptions.propagate(e); } } } else { result.addAll(getAll()); } for(BrooklynTypePlanTransformer t : result) { t.setManagementContext(mgmt); } return result; } /** returns a list of {@link BrooklynTypePlanTransformer} instances for this {@link ManagementContext} * which may be able to handle the given plan; the list is sorted with highest-score transformer first */ @Beta public static List<BrooklynTypePlanTransformer> forType(ManagementContext mgmt, RegisteredType type, RegisteredTypeLoadingContext constraint) { Multimap<Double,BrooklynTypePlanTransformer> byScoreMulti = ArrayListMultimap.create(); Collection<BrooklynTypePlanTransformer> transformers = all(mgmt); for (BrooklynTypePlanTransformer transformer : transformers) { double score = transformer.scoreForType(type, constraint); if (score>0) byScoreMulti.put(score, transformer); } Map<Double, Collection<BrooklynTypePlanTransformer>> tree = new TreeMap<Double, Collection<BrooklynTypePlanTransformer>>(byScoreMulti.asMap()); List<Collection<BrooklynTypePlanTransformer>> highestFirst = new ArrayList<Collection<BrooklynTypePlanTransformer>>(tree.values()); Collections.reverse(highestFirst); return ImmutableList.copyOf(Iterables.concat(highestFirst)); } /** transforms the given type to an instance, if possible * <p> * callers should generally use one of the create methods on {@link BrooklynTypeRegistry} rather than using this method directly. */ @Beta public static Maybe<Object> transform(ManagementContext mgmt, RegisteredType type, RegisteredTypeLoadingContext constraint) { if (type==null) return Maybe.absent("type cannot be null"); if (type.getPlan()==null) return Maybe.absent("type plan cannot be null, when instantiating "+type); List<BrooklynTypePlanTransformer> transformers = forType(mgmt, type, constraint); Collection<String> transformersWhoDontSupport = new ArrayList<String>(); Collection<Exception> failuresFromTransformers = new ArrayList<Exception>(); for (BrooklynTypePlanTransformer t: transformers) { try { Object result = t.create(type, constraint); if (result==null) { transformersWhoDontSupport.add(t.getFormatCode() + " (returned null)"); continue; } return Maybe.of(result); } catch (UnsupportedTypePlanException e) { transformersWhoDontSupport.add(t.getFormatCode() + (Strings.isNonBlank(e.getMessage()) ? " ("+e.getMessage()+")" : "")); } catch (@SuppressWarnings("deprecation") org.apache.brooklyn.core.plan.PlanNotRecognizedException e) { // just in case (shouldn't happen) transformersWhoDontSupport.add(t.getFormatCode() + (Strings.isNonBlank(e.getMessage()) ? " ("+e.getMessage()+")" : "")); } catch (Throwable e) { Exceptions.propagateIfFatal(e); failuresFromTransformers.add(new PropagatedRuntimeException("Transformer for "+t.getFormatCode()+" gave an error creating this plan: "+ Exceptions.collapseText(e), e)); } } // failed Exception result; if (!failuresFromTransformers.isEmpty()) { // at least one thought he could do it if (log.isDebugEnabled()) { log.debug("Failure transforming plan; returning summary failure, but for reference " + "potentially application transformers were "+transformers+", " + "available ones are "+MutableList.builder().addAll(all(mgmt)) // when all(mgmt) has a cache, reinstate this and add the word "other" above // .removeAll(transformers) .build()+"; " + "failures: "+failuresFromTransformers); } result = failuresFromTransformers.size()==1 ? Exceptions.create(null, failuresFromTransformers) : Exceptions.create("All plan transformers failed", failuresFromTransformers); } else { if (transformers.isEmpty()) { result = new UnsupportedTypePlanException("Invalid plan; format could not be recognized, none of the available transformers "+all(mgmt)+" support "+type); } else { result = new UnsupportedTypePlanException("Invalid plan; potentially applicable transformers "+transformers+" do not support it, and other available transformers "+ // // the removeAll call below won't work until "all" caches it // MutableList.builder().addAll(all(mgmt)).removeAll(transformers).build()+" "+ "do not accept it"); } } return Maybe.absent(result); } }