/* * 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.ode.bpel.common; import java.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * This class implements a set of correlation keys. * * The example of canonical forms of correlation key sets are: * * <ul> * <li>@2</li> * <li>@2[12~a~b]</li> * <li>@2[12~a~b],[25~b~c]</li> * </ul> * * The first example shows an empty correlation key set. The second shows a set with one correlation key inside. * The third shows a set with two keys inside. The correlation keys are sorted by the correlation set ids. * * @author sean * */ public class CorrelationKeySet implements Serializable { private static final long serialVersionUID = 1L; public final static String VERSION_1 = "1"; public final static String VERSION_2 = "2"; @SuppressWarnings("unused") private String version = VERSION_2; private final Set<CorrelationKey> correlationKeys = new TreeSet<CorrelationKey>(new CorrelationKeyComparator()); /** * Default Constructor */ public CorrelationKeySet() { } /** * Restores the state by parsing the given canonical form of correlation key set. * * @param canonicalForm canonical form of correlation key set */ public CorrelationKeySet(String canonicalForm) { restore(canonicalForm); } /** * Adds a correlation key to this correlation key set. If a correlation key with the same correlation set id * already exists, the old one is replaced with the given new one. * * @param ck a correlation key to add * @return returns this correlation key set */ public CorrelationKeySet add(CorrelationKey ck) { for( CorrelationKey key : correlationKeys ) { if( key.getCorrelationSetName().equals(ck.getCorrelationSetName()) ) { correlationKeys.remove(ck); break; } } correlationKeys.add(ck); return this; } /** * Checks if this correlation key set contains the opaque correlation key as the only key * in this correlation key set. * * @return returns true if the correlation key set is opaque */ public boolean isOpaque() { return correlationKeys.size() == 1 && correlationKeys.iterator().next().getCorrelationSetName().equals("-1"); } /** * Checks if an incoming message with this correlation key set can be accepted by the given * correlation key set. * * @param candidateKeySet a correlation key set stored in a route * @param isAllRoute use true if the route="all" is set * @return return true if routable */ public boolean isRoutableTo(CorrelationKeySet candidateKeySet, boolean isAllRoute) { boolean isRoutable = containsAll(candidateKeySet); if( isAllRoute ) { isRoutable = isRoutable || candidateKeySet.isOpaque() && isEmpty(); } return isRoutable; } /** * Checks if this correlation key set contains all correlation keys from the given correlation key set. * * @param c a correlation key set * @return return true if this correlation key set is a superset */ public boolean containsAll(CorrelationKeySet c) { Iterator<CorrelationKey> e = c.iterator(); while (e.hasNext()) if (!contains(e.next())) return false; return true; } /** * Returns true if this correlation key set contains no correlation keys. * * @return returns true if empty */ public boolean isEmpty() { return correlationKeys.isEmpty(); } /** * Returns true if this correlation key set contains the give correlation key. * * @param correlationKey a correlation key * @return */ public boolean contains(CorrelationKey correlationKey) { Iterator<CorrelationKey> e = correlationKeys.iterator(); if (correlationKey == null) { while (e.hasNext()) if (e.next() == null) return true; } else { while (e.hasNext()) { if (correlationKey.equals(e.next())) return true; } } return false; } /** * Returns an iterator on the correlation keys that this correlation key set contains. * * @return an iterator */ public Iterator<CorrelationKey> iterator() { return correlationKeys.iterator(); } /** * Removes all correlation keys in this correlation keys. */ public void clear() { correlationKeys.clear(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((correlationKeys == null) ? 0 : correlationKeys.hashCode()); return result; } @Override public boolean equals(Object o) { if( o == null || !(o instanceof CorrelationKeySet) ) { return false; } CorrelationKeySet another = (CorrelationKeySet)o; if( correlationKeys.size() != another.correlationKeys.size() ) { return false; } return containsAll(another); } /** * Finds all subsets of this correlation key set. * * @return a list of all subset correlation key sets */ public List<CorrelationKeySet> findSubSets() { List<CorrelationKeySet> subSets = new ArrayList<CorrelationKeySet>(); // if the key set contains a opaque key and at least one non-opaque key, take out the opaque key CorrelationKey opaqueKey = null; boolean containsNonOpaque = false; CorrelationKeySet explicitKeySet = new CorrelationKeySet(); for( CorrelationKey ckey : correlationKeys ) { // assumes only ONE opaque key if there is if( ckey.getCorrelationSetName().equals("-1") ) { opaqueKey = ckey; } else { containsNonOpaque = true; } explicitKeySet.add(ckey); } if( opaqueKey != null && containsNonOpaque ) { explicitKeySet.correlationKeys.remove(opaqueKey); } // we are generating (2 powered by the number of correlation keys) number of sub-sets for( int setIndex = 0; setIndex < Math.pow(2, explicitKeySet.correlationKeys.size()); setIndex++ ) { CorrelationKeySet subKeySet = new CorrelationKeySet(); int bitPattern = setIndex; // the bitPattern will be 0b0000, 0b0001, 0b0010 and so on Iterator<CorrelationKey> ckeys = explicitKeySet.iterator(); while( ckeys.hasNext() && bitPattern > 0 ) { // bitPattern > 0 condition saves half of the iterations CorrelationKey ckey = ckeys.next(); if( (bitPattern & 0x01) > 0 ) { subKeySet.add(ckey); } bitPattern = bitPattern >> 1; } if(!subKeySet.isEmpty()) { // we don't want an empty set subSets.add(subKeySet); } } if( subSets.isEmpty() ) { subSets.add(new CorrelationKeySet()); } return subSets; } /** * Returns a canonical form of this correlation key set. * * @return */ public String toCanonicalString() { StringBuffer buf = new StringBuffer(); for( CorrelationKey ckey : correlationKeys ) { if( buf.length() > 0 ) { buf.append(","); } buf.append("[").append(escapeRightBracket(ckey.toCanonicalString())).append("]"); } return "@" + VERSION_2 + buf.toString(); } private static String escapeRightBracket(String str) { if (str == null) return null; StringBuffer buf = new StringBuffer(); char[] chars = str.toCharArray(); for (char achar : chars) { if (achar == ']') { buf.append("]]"); } else { buf.append(achar); } } return buf.toString(); } @Override public String toString() { return correlationKeys.toString(); } /** * Restores the state of this correlation key set from a canonical form. * * @param canonicalForm a canonical form of correlation key set */ public void restore(String canonicalForm) { if( canonicalForm == null || canonicalForm.trim().length() == 0 ) return; if( canonicalForm.startsWith("@") ) { parseCanonicalForm(canonicalForm); } else { version = VERSION_1; add( new CorrelationKey(canonicalForm) ); } } private static enum ParserState { INITIAL, MET_ALPHA, MET_LEFT_BRACKET, MET_RIGHT_BRACKET, MET_COMMA } // parses a canonical form of correlation key set through an automata subsystem(FSM) private void parseCanonicalForm(String canonicalForm) { ParserState state = ParserState.INITIAL; StringBuffer buf = new StringBuffer(); for( int i = 0; i < canonicalForm.length(); i++ ) { char ch = canonicalForm.charAt(i); if( state == ParserState.INITIAL ) { if( ch == '@' ) { state = ParserState.MET_ALPHA; } else { buf.append(ch); state = ParserState.MET_LEFT_BRACKET; } } else if( state == ParserState.MET_ALPHA ) { if( ch == '[' ) { version = buf.toString(); buf.setLength(0); state = ParserState.MET_LEFT_BRACKET; } else { buf.append(ch); } } else if( state == ParserState.MET_LEFT_BRACKET ) { if( ch == ']' ) { state = ParserState.MET_RIGHT_BRACKET; } else { buf.append(ch); } } else if( state == ParserState.MET_RIGHT_BRACKET ) { if( ch == ']' ) { buf.append(ch); state = ParserState.MET_LEFT_BRACKET; } else if( ch == ',' ) { if( buf.toString().trim().length() != 0 ) { add( new CorrelationKey(buf.toString()) ); } buf.setLength(0); state = ParserState.MET_COMMA; } else if( ch == '?' ) { // this is only a convenient feature for testing if( buf.toString().trim().length() != 0 ) { add( new OptionalCorrelationKey(buf.toString()) ); } buf.setLength(0); state = ParserState.MET_COMMA; } } else if( state == ParserState.MET_COMMA ) { if( ch == '[' ) { state = ParserState.MET_LEFT_BRACKET; } } } if( buf.toString().trim().length() != 0 ) { if( state == ParserState.MET_ALPHA ) { version = buf.toString(); } else { add( new CorrelationKey(buf.toString()) ); } } } private class CorrelationKeyComparator implements Serializable, Comparator<CorrelationKey> { private static final long serialVersionUID = 1L; public int compare(CorrelationKey o1, CorrelationKey o2) { if( o1 == null || o2 == null ) { return 0; } // used only in sorting the correlation keys in the CorrelationKeySet; does not matter with the values return o1.getCorrelationSetName().compareTo(o2.getCorrelationSetName()); } } }