/* * 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.axis2.engine; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * A DeployableChain is a container which manages dependencies between Deployables. You * deploy() them in, then call rebuild() which will set up a chain, correctly ordered according * to the constraints in the Deployables. */ public class DeployableChain<T> { /** The actual things (handlers or phases) */ List<Object> chain = new ArrayList<Object>(); Deployable first; Deployable last; /** A Map of name -> List (of Strings). Each List contains the key's successors */ Map<String, Set<String>> activeConstraints = new LinkedHashMap<String, Set<String>>(); /** A Map of name -> Deployable for all deployed items */ private Map<String, Deployable> deployed = new LinkedHashMap<String, Deployable>(); /** * Deploy a Deployable into this chain. Note that this does NOT order yet. The idea * is to deploy everything first, then call rebuild() to generate the fully ordered chain. * This method will validate the Deployable, including confirming that it's compatible with * any previously deployed item of the same name. * * @param deployable Deployable to deploy. * @throws Exception if there is a deployment error. */ public void deploy(Deployable deployable) throws Exception { String name = deployable.getName(); Set<String> mySuccessors = deployable.getSuccessors(); Set<String> myPredecessors = deployable.getPredecessors(); if (deployable.isFirst()) { if (first != null) { throw new Exception("'" + first.getName() + "' is already first, can't deploy '" + name + "' as first also."); } if (myPredecessors != null) { throw new Exception("Deploying '" + name + "' - can't both be first and have predecessors!"); } first = deployable; } if (deployable.isLast()) { if (last != null) { throw new Exception("'" + last.getName() + "' is already last, can't deploy '" + name + "' as last also."); } if (mySuccessors != null) { throw new Exception("Deploying '" + name + "' - can't both be last and have successors!"); } last = deployable; } Deployable previous = (Deployable)deployed.get(name); if (previous == null) { deployed.put(name, deployable); } else { // If something by this name already exists, ensure it's compatible if (previous.isFirst() != deployable.isFirst()) { throw new Exception("Can't deploy '" + name + "', values for first don't match!"); } if (previous.isLast() != deployable.isLast()) { throw new Exception("Can't deploy '" + name + "', values for last don't match!"); } Object target = previous.getTarget(); if (target != null) { if (deployable.getTarget() != null && !target.equals(deployable.getTarget())) { throw new Exception("Can't deploy '" + name + "', targets must either match or be null."); } } else { previous.setTarget(deployable.getTarget()); } } if (mySuccessors != null && !mySuccessors.isEmpty()) { Set<String> successors = activeConstraints.get(name); if (successors == null) { successors = new LinkedHashSet<String>(); activeConstraints.put(name, successors); } successors.addAll(mySuccessors); } if (myPredecessors != null) { for (Object myPredecessor : myPredecessors) { String predecessor = (String)myPredecessor; // define relationship for each one addRelationship(predecessor, name); } } } /** * Find the correct (minimum) index for a given name. Finds the active constraints * for the name, then walks that list, making sure that each one is already placed. * If a given successor isn't yet placed, we recursively call this method to place it, * passing a set of "what's left to do" and a set of "what we've seen". * * This results in chain containing the names (Strings). * * TODO: investigate optimizing this algorithm a bit * * @param name the target deployable's name * @param remaining a Set of the names of things we have yet to deploy * @param seen a Set of the names of things we've already seen (to detect loops) * @return an index >=0 or -1 if there was a dependency cycle * @throws Exception if there's a cyclic dependency */ private int getMinIndex(String name, Set<String> remaining, Set<String> seen) throws Exception { if (seen.contains(name)) { // We return -1 here instead of throwing the Exception so we can build a better // error message below. return -1; } Set<String> successors = activeConstraints.get(name); if (successors == null || successors.isEmpty()) { // Never put anything after the thing marked "last"... int index = (last == null) ? chain.size() : chain.size() - 1; chain.add(index, name); remaining.remove(name); return index; } int minIndex = -1; for (Object successor : successors) { String otherName = (String)successor; int otherIdx = chain.indexOf(otherName); if (otherIdx > -1) { if ((minIndex == -1) || (minIndex > otherIdx)) { minIndex = otherIdx; } } else { // This guy isn't in our list yet - put him in IF he's real (exists in // the deployed list) and we haven't already placed him (exists in // the remaining set). if ((deployed.get(otherName) != null) && remaining.contains(otherName)) { seen.add(name); // Not in list yet, go place it int min = getMinIndex(otherName, remaining, seen); if (minIndex == -1 || min < minIndex) { minIndex = min; } if (minIndex == -1) { throw new Exception("Trying to put '" + name + "' before '" + otherName + "' - incompatible constraints!"); } } } } if (minIndex == -1) minIndex = 0; chain.add(minIndex, name); remaining.remove(name); return minIndex; } /** * Taking into account all the active constraints, order the list. This blows * away the old order. Could improve this slightly with a "dirty" flag. * * @throws Exception if there's an ordering conflict */ public void rebuild() throws Exception { chain.clear(); Set<String> keys = new LinkedHashSet<String>(); keys.addAll(deployed.keySet()); // First goes first. if (first != null) { chain.add(first.getName()); keys.remove(first.getName()); } // Last goes next, and everything else will get inserted before it if it exists. if (last != null) { Set<String> afterLast = activeConstraints.get(last.getName()); if (afterLast != null) { throw new Exception("Can't have anything which goes after '" + last.getName() + "', which has been declared last."); } chain.add(last.getName()); keys.remove(last.getName()); } while (!keys.isEmpty()) { String name = (String)keys.iterator().next(); getMinIndex(name, keys, new LinkedHashSet<String>()); } // Now we've got a chain of names. Convert to actual things before we return. for (int i = 0; i < chain.size(); i++) { String name = (String)chain.get(i); chain.set(i, ((Deployable)deployed.get(name)).getTarget()); } } /** * Adds a before/after relationship to the active constraints. * * @param before name of the Deployable that must come first * @param after name of the Deployable that must come later */ public void addRelationship(String before, String after) { Set<String> successors = activeConstraints.get(before); if (successors == null) { successors = new LinkedHashSet<String>(); activeConstraints.put(before, successors); } successors.add(after); } /** * Get the chain - once rebuild() has been called this will be the chain of target objects. * * @return a List of target objects in the correct order */ public List<T> getChain() { // todo - should this call rebuild() automatically (if dirty flag is set)? return (List<T>) chain; } }