blob: 776ca514d0cf7be12ad3e0dbfd3ee3473a7b1110 [file] [log] [blame]
/*
* 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;
}
}