package com.yahoo.dtf.actions;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.yahoo.dtf.DTFConstants;
import com.yahoo.dtf.actions.protocol.SerializationException;
import com.yahoo.dtf.actions.reference.RefWrapper;
import com.yahoo.dtf.comm.Comm;
import com.yahoo.dtf.components.Components;
import com.yahoo.dtf.config.Config;
import com.yahoo.dtf.config.DTFStream;
import com.yahoo.dtf.config.transform.TransformerFactory;
import com.yahoo.dtf.debug.Trace;
import com.yahoo.dtf.exception.DTFException;
import com.yahoo.dtf.exception.InterruptionException;
import com.yahoo.dtf.exception.ParseException;
import com.yahoo.dtf.exception.QueryException;
import com.yahoo.dtf.exception.RecorderException;
import com.yahoo.dtf.exception.ResultsException;
import com.yahoo.dtf.functions.Functions;
import com.yahoo.dtf.logger.DTFLogger;
import com.yahoo.dtf.logger.RemoteLogger;
import com.yahoo.dtf.query.Cursor;
import com.yahoo.dtf.recorder.Recorder;
import com.yahoo.dtf.recorder.RecorderBase;
import com.yahoo.dtf.references.References;
import com.yahoo.dtf.results.Results;
import com.yahoo.dtf.results.ResultsBase;
import com.yahoo.dtf.state.ActionState;
import com.yahoo.dtf.state.DTFState;
import com.yahoo.dtf.storage.StorageFactory;
import com.yahoo.dtf.streaming.DTFInputStream;
import com.yahoo.dtf.streaming.StringInputStream;
import com.yahoo.dtf.util.ByteArrayUtil;
import com.yahoo.dtf.util.StringUtil;
import com.yahoo.dtf.xml.DTFXSDHandler;
abstract public class Action implements Externalizable {
private static DTFLogger _logger = DTFLogger.getLogger(Action.class);
private ArrayList<Action> _list = null;
private int line = -1;
private int column = -1;
private String filename = null;
public Action() {
_list = new ArrayList<Action>();
}
public int getLine() { return line; }
public void setLine(int line) { this.line = line; }
public int getColumn() { return column; }
public void setColumn(int column) { this.column = column; }
public void setFilename(String filename) { this.filename = filename; }
public String getFilename() { return filename; }
public void addAction(Action action) {
_list.add(action);
}
public void addActions(List actions) {
for (int i = 0; i < actions.size(); i++)
addAction((Action) actions.get(i));
}
public boolean hasChildren() { return _list.size() != 0; }
public ArrayList<Action> children() {
return new FinalArrayList<Action>(_list);
}
public void clearChildren() {
_list.clear();
}
protected Action getAction(int index) {
Action action = (Action)_list.get(index);
if (action instanceof RefWrapper) {
try {
action = ((RefWrapper)action).lookupReference();
} catch (ParseException e) {
throw new RuntimeException("This shouldn't happen.",e);
}
}
return action;
}
/**
* DTF's internal instance of function.
*
* @param type
* @return boolean
*/
public boolean anInstanceOf(Class type) {
return type.isInstance(this);
}
/**
* Put together an array list of the actions of the specified class type that
* are direct children of this tag.
*
* @param <T>
* @param T
* @return
*/
public <T> ArrayList<T> findActions(Class T) {
ArrayList<T> result = new ArrayList<T>();
for (int i = 0; i < _list.size(); i++) {
if (((Action)_list.get(i)).anInstanceOf(T))
/*
* XXX: whenever I have sometime I should use generics on the
* remaining calls to make everything work smoothly.
*/
result.add((T)getAction(i));
}
return result;
}
/**
* Put together an array list of the actions that are descedents of this one
* tag, this will include every action recursively down the action tree.
*
* @param <T>
* @param T
* @return
*/
public <T> ArrayList<T> findAllActions(Class T) {
ArrayList<T> result = new ArrayList<T>();
for (int i = 0; i < _list.size(); i++) {
T t = (T)getAction(i);
if (((Action)t).anInstanceOf(T)) {
result.add(t);
}
ArrayList<T> sub = ((Action)t).findAllActions(T);
result.addAll(sub);
}
return result;
}
/**
* Find the first action of the specified class type.
*
* @param classType
* @return
*/
public Action findFirstAction(Class classType) {
for (int i = 0; i < _list.size(); i++) {
if (((Action)_list.get(i)).anInstanceOf(classType))
return getAction(i);
}
return null;
}
/**
* Execute the underlying children of this tag and make sure to keep the
* execution state in check as to which tag is being executed.
*
* @throws DTFException
*/
public void executeChildren() throws DTFException {
Action current = getState().getAction();
try {
for (int i = 0; i < _list.size(); i++) {
Action action = getAction(i);
getState().setAction((Action) action);
if ( Trace.isEnabled() )
Trace.trace(action);
action.execute();
}
} catch (DTFException e) {
throw e;
} catch (Throwable t) {
throw new DTFException("Uncaught Exception",t);
} finally {
getState().setAction(current);
}
}
public void executeSelf() throws DTFException {
getState().setAction(this);
if ( Trace.isEnabled() )
Trace.trace(this);
this.execute();
}
/*
* Internally used by ActionResult.
*/
protected void executeChildrenWithoutStateChange() throws DTFException {
for (int i = 0; i < _list.size(); i++) {
Action action = getAction(i);
if ( Trace.isEnabled() )
Trace.trace(action);
action.execute();
}
}
/**
* Execute the underlying children of this tag that are of the type
* specified and make sure to keep the execution state in check as to which
* tag is being executed.
*
* @throws DTFException
*/
public <T extends Action> void executeChildren(Class<T> classType) throws DTFException {
Action current = getState().getAction();
try {
for (int i = 0; i < _list.size(); i++) {
if (((Action)_list.get(i)).anInstanceOf(classType)) {
Action action = getAction(i);
getState().setAction(action);
if ( Trace.isEnabled() )
Trace.trace(action);
action.execute();
}
}
} catch (DTFException e) {
throw e;
} catch (Throwable t) {
throw new DTFException("Uncaught Exception",t);
} finally {
getState().setAction(current);
}
}
/**
*
* @param <T>
* @param children
* @throws DTFException
*/
public <T extends Action> void executeChildren(ArrayList<Action> children) throws DTFException {
Action current = getState().getAction();
try {
for (int i = 0; i < children.size(); i++) {
Action action = children.get(i);
getState().setAction(action);
if ( Trace.isEnabled() )
Trace.trace(action);
action.execute();
}
} catch (DTFException e) {
throw e;
} catch (Throwable t) {
throw new DTFException("Uncaught Exception",t);
} finally {
getState().setAction(current);
}
}
/**
* Simple utility method to be used in your own thread implementations or
* if you create flow control tags that have a loop like behaviour where
* they iterate a certain number of times. You can easily call this method
* on each loop or any point at which it is safe to interrupt and terminate
* the current thread execution.
*
* This method will generate a DTF specific InterruptionException which is
* handled nicely by DTF assuming that the test was currently interrupted.
*
* @throws InterruptionException
*
* @dtf.feature Thread Interruption
* @dtf.feature.group Tag Development
*
* @dtf.feature.desc
* <p>
* Thread interruption is a necessary evil in the DTF framework that must be
* considered by all developers of any tags. You have to think about the
* issue of having a long running tag that can't be interrupted in Java
* unless you allow it to be. You can call the utility method on Action called
* checkInterruption() and it will automatically validate if the currently
* running thread was interrupted and if so will throw an DTF specific
* exception InterruptionException that will be handled by the framework
* accordingly. So now in your code you can easily call this method where
* you feel you could interrupt your execution without leaving your code in
* a state that you can't continue to issue actions from.
* </p>
* <p>
* All of the flow control tags such as for, parallelloop, etc within DTF
* are already interruptable and will correctly clean up their underlying
* spawned threads. Now that your threads are interruptable you only need
* to register them with the {@dtf.link Thread Management} layer so that you
* can have the DTF framework interrupt your threads when necessary. As an
* example you can see here the code for a the for tag that checks for
* interruption during its normal execution:
* </p>
* <pre>
* public class For extends Loop {
* public For() { }
*
* public void execute() throws DTFException {
* Range range = RangeFactory.getRange(getRange());
* try {
* while (range.hasMoreElements()) {
* getConfig().setProperty(getProperty(), range.nextElement());
* executeChildren();
* checkInterruption();
* }
* } catch (InterruptionException e) {
* if ( getLogger().isDebugEnabled() )
* getLogger().debug("execution interrupted.");
* } catch (BreakException e) {
* // break point
* if ( getLogger().isDebugEnabled() )
* getLogger().debug("break point hit",e);
* }
* getConfig().remove(getProperty());
* }
* }
* </pre>
*
*/
public static void checkInterruption() throws InterruptionException {
if ( Thread.currentThread().isInterrupted() ) {
throw new InterruptionException(Thread.currentThread().getName() +
" execution interrupted.");
}
}
public static DTFState getState() { return ActionState.getInstance().getState(); }
public static Config getConfig() { return getState().getConfig(); }
public static Comm getComm() { return getState().getComm(); }
public static StorageFactory getStorageFactory() { return getState().getStorage(); }
public static Components getComponents() { return getState().getComponents(); }
public static Recorder getRecorder() { return getState().getRecorder(); }
public static Results getResults() { return getState().getResults(); }
public static Functions getFunctions() { return getState().getFunctions(); }
public static References getReferences() { return getState().getReferences(); }
public static void pushRecorder(RecorderBase recorder, String event) throws RecorderException {
Recorder rec = new Recorder(recorder,event);
rec.start();
rec.setParent(getState().getRecorder());
getState().setRecorder(rec);
}
public static void popRecorder() throws RecorderException {
Recorder rec = getState().getRecorder();
rec.stop();
getState().setRecorder(rec.getParent());
}
public static void pushResults(ResultsBase results) throws ResultsException {
Results res = new Results(results);
res.start();
res.setParent(getState().getResults());
getState().setResults(res);
}
public static void popResults() throws ResultsException {
Results res = getState().getResults();
res.stop();
getState().setResults(res.getParent());
}
public static void addCursor(String name, Cursor cursor) throws QueryException {
Cursor old = getState().getCursors().getCursor(name);
if ( old != null )
old.close();
getState().getCursors().addCursor(name, cursor);
}
public static Cursor retCursor(String name) {
return getState().getCursors().getCursor(name);
}
public static String getLocalID() {
return (String) getGlobalContext(DTFConstants.DTF_NODE_NAME);
}
public String getXMLLocation() {
DTFState state = getState();
if (state != null) {
if (getLine() != -1) {
String filename = getFilename();
return (filename != null ? " at " + getFilename() + ":" + getLine() + ":" + getColumn() : "");
}
}
return "";
}
public void setXMLLocation(Action action) {
setFilename(action.getFilename());
setLine(action.getLine());
setColumn(action.getColumn());
}
public static void registerContext(String key, Object value) {
getState().registerContext(key, value);
}
public static Object getContext(String key) {
return getState().getContext(key);
}
public static void unRegisterContext(String key) {
getState().unRegisterContext(key);
}
public static void registerGlobalContext(String key, Object value) {
getState().registerGlobalContext(key, value);
}
public static Object getGlobalContext(String key) {
return getState().getGlobalContext(key);
}
public static void unRegisterGlobalContext(String key) {
getState().unRegisterGlobalContext(key);
}
protected int toInt(String property, String value) throws ParseException {
try {
return new Integer(replaceProperties(value)).intValue();
} catch (NumberFormatException e) {
throw new ParseException("Value of property [" + property +
"] is not an int, got [" + value + "]",e);
}
}
protected int toInt(String property, String value, int defaultValue)
throws ParseException {
try {
value = replaceProperties(value);
if (value == null)
return defaultValue;
return new Integer(value).intValue();
} catch (NumberFormatException e) {
throw new ParseException("Value of property [" + property +
"] is not an int, got [" + value + "]",e);
}
}
protected double toDouble(String property,
String value,
double defaultValue) throws ParseException {
try {
value = replaceProperties(value);
if (value == null)
return defaultValue;
return new Double(value).doubleValue();
} catch (NumberFormatException e) {
throw new ParseException("Value of property [" + property +
"] is not a double, got [" + value + "]",e);
}
}
protected double toDouble(String property, String value) throws ParseException {
try {
return new Double(replaceProperties(value)).doubleValue();
} catch (NumberFormatException e) {
throw new ParseException("Value of property [" + property +
"] is not a double, got [" + value + "]",e);
}
}
protected boolean toBoolean(String property, String value) throws ParseException {
try {
value = replaceProperties(value);
return Boolean.valueOf(value).booleanValue();
} catch (NumberFormatException e) {
throw new ParseException("Value of property [" + property +
"] is not a boolean, got [" + value +
"]",e);
}
}
protected long toLong(String property,
String value,
long defaultValue) throws ParseException {
try {
value = replaceProperties(value);
if (value == null)
return defaultValue;
return new Long(value).longValue();
} catch (NumberFormatException e) {
throw new ParseException("Value of property [" + property +
"] is not an long, got [" + value + "]",e);
}
}
protected long toLong(String property,
String value ) throws ParseException {
try {
if (value == null)
throw new ParseException("Value of property [" + property +
"] is not an long, got [null]");
value = replaceProperties(value);
return new Long(value).longValue();
} catch (NumberFormatException e) {
throw new ParseException("Value of property [" + property +
"] is not an long, got [" + value +
"]",e);
}
}
/*
* All characters but $,{ and } because those would mean sub property that
* needs resolving. The other thing to avoid is just any character in the
* property name which we only allow ASCII characters and the usual
* Punctuation characters.
*/
private static Pattern pattern = Pattern.
compile("\\$\\{([\\s\\w\\d\\p{Punct}&&[^\\$\\{\\}]]+)\\}");
public static String replaceProperties(String string) throws ParseException {
return replaceProperties(string, false);
}
/**
* INTERNAL USE ONLY
*
* This function is used by special tags that require the ability to do a
* lazy evaluation of properties and be able to resolve the properties at
* the last possible moment. For an example of how this is used have a look
* at the Scanf tag.
*
* @param string
* @param relaxed
* @return
* @throws ParseException
*/
/*
* For a property to be considered as recursively resolving then we must
* find it being matched at the same point it was matched before and not
* anywhere else along in the string. That is what this class is used for,
* every key that is matched within a string is kept track of so that we
* can easily look it up and make sure it hasn't already been solved at this
* exact position.
*/
private static class MatchKey {
public int index = 0;
public String key = null;
public MatchKey(int index, String key ) {
this.index = index;
this.key = key;
}
@Override
public boolean equals(Object obj) {
if ( obj instanceof MatchKey ) {
MatchKey other = (MatchKey) obj;
return other.index == index && other.key.equals(key);
}
return false;
}
@Override
public int hashCode() {
return index + key.hashCode();
}
}
protected static String replaceProperties(String string, boolean relaxed)
throws ParseException {
if (string == null)
return null;
/*
* Not in replace mode means we're about to send the current action to
* a component so we don't do any property resolution on the runner side.
*/
if (!getState().replace())
return string;
Config config = getConfig();
boolean hasMatch = true;
ArrayList<MatchKey> replacements = new ArrayList<MatchKey>();
while ( hasMatch ) {
hasMatch = false;
Matcher match = pattern.matcher(string);
while ( match.find() ) {
String group = match.group();
String key = group.substring(2, group.length()-1);
String value = null;
if ( replacements.contains(new MatchKey(match.start(),key)) ) {
throw new ParseException("Recursive resolution detected ["
+ string + "]");
}
int tindex = key.indexOf(':');
int findex = key.indexOf('(');
/*
* Another silly workaround till we make a more robust property
* resolver as mentioned in the comment below.
*/
if ( tindex != -1 && findex != -1 ) {
if ( tindex < findex ) {
findex = -1;
} else {
tindex = -1;
}
}
/*
* XXX: This could use some cleaning up in the near future.
* probably move all of this into a PropertyResolver class
* of some sort we're we can do a better job at organizing
* properties/dynamic properties/transformers
*/
if ( tindex != -1 ) {
String expr = key.substring(tindex+1);
String expr_data = expr.substring(expr.indexOf(":")+1);
String auxkey = "${" + key.substring(0,tindex) + "}";
String aux = replaceProperties(auxkey,relaxed);
if ( !aux.equals(auxkey) ) {
value = TransformerFactory.
getTransformer(expr).apply(aux, expr_data);
}
} else if ( findex != -1) {
int fendex = key.indexOf(')');
if ( fendex != -1 ) {
String args = key.substring(findex+1,fendex);
key = key.substring(0,findex);
value = config.callDynamicProperty(key, args);
} else {
throw new ParseException("Property missing ')' in [" +
key + "]");
}
} else {
value = config.getProperty(key);
}
if (value != null) {
string = StringUtil.replace(string, group, value);
hasMatch = true;
} else {
if ( !relaxed ) {
throw new ParseException("Property [" + key +
"] not found.");
}
}
replacements.add(new MatchKey(match.start(),key));
}
}
if ( Trace.isEnabled() ) {
String original = string.substring(0,(string.length() > 1024 ? 1024 : string.length()));
Trace.tracePropertyResolution(original, string);
}
return string;
}
public static DTFInputStream replacePropertiesAsInputStream(String string)
throws ParseException {
return replacePropertiesAsInputStream(string,
Charset.defaultCharset().displayName());
}
public static DTFInputStream replacePropertiesAsInputStream(String string,
String encoding)
throws ParseException {
if (string == null)
return DTFStream.getStringAsStream("");
Config config = getConfig();
boolean hasMatch = true;
ArrayList<String> replacements = new ArrayList<String>();
while (hasMatch) {
hasMatch = false;
Matcher match = pattern.matcher(string);
if (match.find()) {
String group = match.group();
if ( replacements.contains(group) )
throw new ParseException("Recursive resolution detected ["
+ string + "]");
String key = group.substring(2, group.length()-1);
String value = null;
replacements.add(key);
int findex = key.indexOf('(');
if ( findex != -1) {
int fendex = key.indexOf(')');
if ( fendex != -1 ) {
String args = key.substring(findex+1,fendex);
key = key.substring(0,findex);
if ( config.isStream(key) ) {
DTFStream stream = config.getPropertyAsStream(key);
return stream.getValueAsStream(args);
}
} else {
throw new ParseException("Property missing ')' in [" +
key + "]");
}
} else {
value = config.getProperty(key);
}
if (value != null) {
if ( group.length() == string.length() )
string = value;
else
string = StringUtil.replace(string, group, value);
hasMatch = true;
} else {
throw new ParseException("Property [" + key +
"] not found.");
}
}
}
return DTFStream.getStringAsStream(replaceProperties(string));
}
public String getClassName() { return Action.getClassName(this.getClass()); }
public static String getClassName(Class aClass) {
String classname = aClass.getName();
return classname.substring(classname.lastIndexOf(".")+1,
classname.length());
}
private Field lookup(Class cl, String name) {
try {
Field f = cl.getField(name);
f.setAccessible(true);
return f;
} catch (Exception e) {
try {
Field f = cl.getDeclaredField(name);
f.setAccessible(true);
return f;
} catch (SecurityException e1) {
if ( cl.getSuperclass() != null ) {
return lookup(cl.getSuperclass(),name);
}
} catch (NoSuchFieldException e1k) {
if ( cl.getSuperclass() != null ) {
return lookup(cl.getSuperclass(),name);
}
}
}
return null;
}
protected Hashtable<String,Object> getAttribs(Class actionClass) {
ArrayList<String> attribs = null;
Hashtable<String, Object> result = new Hashtable<String, Object>();
if (!actionClass.getPackage().getName().contains("actions"))
return result;
try {
attribs = DTFXSDHandler.getInstance().
getAttributes(getClassName(actionClass));
} catch (DTFException e) {
throw new RuntimeException(e);
}
if (attribs == null)
return result;
Class myclass = this.getClass();
Class[] args = new Class[0];
for (int i = 0; i < attribs.size(); i++) {
String key = attribs.get(i);
try {
Field field = lookup(myclass, key);
if ( field != null ) {
field.setAccessible(true);
Object obj = field.get(this);
if (obj != null)
result.put(key, obj);
} else {
_logger.warn("Error getting attribute: " + key + " on class " + actionClass);
}
} catch (SecurityException e) {
_logger.warn("Error getting attribute: " + key +
" on class " + actionClass,e);
} catch (IllegalArgumentException e) {
_logger.warn("Error getting attribute: " + key +
" on class " + actionClass,e);
} catch (IllegalAccessException e) {
_logger.warn("Error getting attribute: " + key +
" on class " + actionClass,e);
}
}
return result;
}
private String TO_STRING_SEPERATOR = ",";
public String toString() {
StringBuffer result = new StringBuffer();
Class actionClass = this.getClass();
Hashtable attribs = getAttribs(actionClass);
Enumeration enumeration = attribs.keys();
result.append(getClassName(actionClass) + " {");
boolean oneappend = false;
while (enumeration.hasMoreElements()) {
String key = (String)enumeration.nextElement();
String value = attribs.get(key).toString();
if ( value.length() > 128 )
value = value.substring(0,128) + "...";
result.append(key + "=" + value + TO_STRING_SEPERATOR);
oneappend = true;
}
if (oneappend)
result.replace(result.length()-1, result.length(), "");
result.append("}");
return result.toString();
}
private static final String URI_RE = "[a-zA-Z]+:/{1,3}[^/^:]+/?.*";
private static Pattern URI_PATTERN = Pattern.compile(URI_RE);
public static URI parseURI(String uri) throws ParseException {
if (uri == null)
return null;
uri = replaceProperties(uri);
if (!URI_PATTERN.matcher(uri).matches()) {
throw new ParseException("Bad URI [" + uri +
"], must match " + URI_RE);
}
try {
return new URI(uri);
} catch (URISyntaxException e) {
throw new ParseException("Bad URI syntax.",e);
}
}
public static DTFLogger getLogger() {
return _logger;
}
/*
* Private for now because I think I need to solve this slightly differently
*/
private static RemoteLogger getRemoteLogger() {
return RemoteLogger.getInstance();
}
/**
* execute the behaviour of this action.
*
*/
abstract public void execute() throws DTFException;
private static HashMap<String, ArrayList<Field>> _fields =
new HashMap<String, ArrayList<Field>>();
public static ArrayList<Field> getAllFields(Class cl) {
Class x = cl;
ArrayList<Field> fields = null;
synchronized(_fields) {
fields = _fields.get(cl.getName());
if (fields == null) {
fields = new ArrayList<Field>();
while ( cl != null ) {
Field[] aux = cl.getDeclaredFields();
for (int i = 0; i < aux.length; i++) {
Field field = aux[i];
if (!Modifier.isStatic(field.getModifiers())) {
aux[i].setAccessible(true);
fields.add(aux[i]);
}
}
cl = cl.getSuperclass();
}
_fields.put(x.getName(), fields);
}
}
return fields;
}
private Hashtable<String,String> getProperties() throws DTFException {
Hashtable<String, String> result = new Hashtable<String, String>();
Class cl = getClass();
ArrayList<Field> fields = getAllFields(cl);
Field f = null;
boolean replace = getState().replace();
for (int i = 0; i < fields.size(); i++) {
try {
f = fields.get(i);
Object obj = f.get(this);
if ( !(obj instanceof List) ) {
String string = null;
if (obj instanceof byte[]) {
byte[] bytes = (byte[]) obj;
string = ByteArrayUtil.byteArrayToHexString(bytes,bytes.length);
} else {
string = (obj == null ? null : obj.toString());
}
/*
* Replace is not enabled when sending actions within a
* <component> tag but it is enabled when sending using
* other tags such as <share_set> which then guarantees
* the necessary properties are replaced on the sender side.
*/
if ( replace ) {
try {
string = replaceProperties(string);
} catch (ParseException e) {
throw new DTFException("Unable to replace properties [" + e.getMessage() + "]");
}
}
if (string != null) {
result.put("set" + f.getName(), string);
}
}
} catch (SecurityException e) {
throw new DTFException("Error retrieving [" + f.getName() + "], " + e.getMessage());
} catch (IllegalArgumentException e) {
throw new DTFException("Error retrieving [" + f.getName() + "], " + e.getMessage());
} catch (IllegalAccessException e) {
throw new DTFException("Error retrieving [" + f.getName() + "], " + e.getMessage());
}
}
return result;
}
public void writeExternal(ObjectOutput out) throws IOException {
Hashtable<String, String> attributes = null;
try {
attributes = getProperties();
} catch (DTFException e) {
/*
* If an error occurs here we have to be able to report it but
* still complete the description of the previous class because
* its already halfway on the pipe.
*/
out.writeInt(SKIP_FLAG);
// SKIP_FLAG will tell the reader to skip this action allowing the
// exception that is next to execute correctly and return the issue.
SerializationException se = new SerializationException();
se.setMessage(e.getMessage());
se.setLine(getLine());
se.setColumn(getColumn());
se.setFilename(getFilename());
out.writeObject(se);
return;
}
Iterator<Entry<String,String>> entries = attributes.entrySet().iterator();
out.writeInt(attributes.size());
while (entries.hasNext()) {
Entry<String,String> entry = entries.next();
out.writeUTF(entry.getKey());
String value = entry.getValue();
out.writeObject(value.getBytes("UTF-8"));
}
ArrayList<Action> children = children();
out.writeInt(children.size());
for (int i = 0; i < children.size(); i++) {
out.writeObject(children.get(i));
}
}
private final static int SKIP_FLAG = -1;
private boolean skip = false;
private static HashMap<Class, Method[]> methodLookup =
new HashMap<Class, Method[]>();
private Method[] lookupMethods(Class cl) {
Method[] methods = methodLookup.get(cl);
synchronized( methodLookup ) {
if ( methods == null ) {
methods = cl.getMethods();
methodLookup.put(cl,methods);
}
}
return methods;
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
Method[] methods = lookupMethods(this.getClass());
int props = in.readInt();
if ( props == SKIP_FLAG ) {
skip = true;
return;
}
for (int i = 0; i < props; i++) {
String methodname = ((String) in.readUTF());
byte[] buffer = (byte[])in.readObject();
String value = new String(buffer,0,buffer.length,"UTF-8");
for (int m = 0; m < methods.length; m++) {
if (methods[m].getName().equalsIgnoreCase(methodname)) {
Method setMethod = methods[m];
Object[] args = new Object[1];
try {
Class classType = setMethod.getParameterTypes()[0];
if (classType.equals(String.class)) {
args[0] = value;
} else if (classType.equals(Integer.TYPE)) {
args[0] = new Integer(value);
} else if (classType.equals(Boolean.TYPE)) {
args[0] = Boolean.valueOf(value);
} else if (classType.equals(Long.TYPE)) {
args[0] = new Long(value);
} else if (classType.equals(Double.TYPE)) {
args[0] = new Double(value);
} else if (classType.equals(Float.TYPE)) {
args[0] = new Float(value);
} else if (classType.equals(Short.TYPE)) {
args[0] = new Short(value);
}
setMethod.invoke(this, args);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Unable to serialize object. " + methodname,e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to serialize object. " + methodname,e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Unable to serialize object. " + methodname,e);
}
}
}
}
int children = in.readInt();
_list = new ArrayList<Action>(children);
for (int i = 0; i < children; i++) {
Action action = (Action)in.readObject();
if ( !action.skip ) addAction(action);
}
}
/**
* This method will duplicate this action but all of the properties within
* it will be resolved. It is used by the share points and is necessary
* because when you set a group of actions on a share point we need to
* keep a copy of those actions has all necessary properties nicely resolved
* so a subsequent get doesn't have to do anything but fetch the result.
*
* @return
* @throws DTFException
*/
public Action duplicate() throws DTFException {
Action action;
try {
action = (Action) this.getClass().newInstance();
Method[] methods = getClass().getMethods();
Hashtable<String,String> props = getProperties();
Iterator<Entry<String,String>> entries = props.entrySet().iterator();
while (entries.hasNext()) {
Entry<String,String> entry = entries.next();
String methodname = entry.getKey();
String value = entry.getValue();
for (int m = 0; m < methods.length; m++) {
if (methods[m].getName().equalsIgnoreCase(methodname)) {
Method setMethod = methods[m];
Object[] args = new Object[1];
try {
Class classType = setMethod.getParameterTypes()[0];
if (classType.equals(String.class)) {
args[0] = value;
} else if (classType.equals(Integer.TYPE)) {
args[0] = new Integer(value);
} else if (classType.equals(Boolean.TYPE)) {
args[0] = Boolean.valueOf(value);
} else if (classType.equals(Long.TYPE)) {
args[0] = new Long(value);
} else if (classType.equals(Double.TYPE)) {
args[0] = new Double(value);
} else if (classType.equals(Float.TYPE)) {
args[0] = new Float(value);
} else if (classType.equals(Short.TYPE)) {
args[0] = new Short(value);
}
setMethod.invoke(action, args);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Unable to serialize object. " + methodname,e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to serialize object. " + methodname,e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Unable to serialize object. " + methodname,e);
}
}
}
}
ArrayList<Action> children = children();
for (int i = 0; i < children.size(); i++)
action.addAction(children.get(i).duplicate());
} catch (InstantiationException e) {
throw new DTFException("Error during cloning.",e);
} catch (IllegalAccessException e) {
throw new DTFException("Error during cloning.",e);
} catch (DTFException e) {
throw new DTFException("Error during cloning.",e);
}
return action;
}
}