package com.yahoo.dtf.components;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import com.yahoo.dtf.DTFProperties;
import com.yahoo.dtf.actions.Action;
import com.yahoo.dtf.actions.properties.BaseProperty;
import com.yahoo.dtf.actions.protocol.range.RangeCreate;
import com.yahoo.dtf.config.Config;
import com.yahoo.dtf.config.Properties;
import com.yahoo.dtf.config.RangeProperty;
import com.yahoo.dtf.exception.DTFException;
import com.yahoo.dtf.exception.ParseException;
/**
* A ComponentHook that will make sure that the component always has the latest
* version of all of the properties found on the runner. Currently keeps track
* of all properties sent so that it can make sure to not re-send existing and
* unchanged properties.
*
* This current implementation also handles the RangeProperties in a not so
* elegant manner, but it works quite well for the time being.
*
* @author rlgomes
*/
public class PropertyState implements ComponentHook,ComponentUnlockHook {
private final static String SENT_PROPS = "dtf.sendprops.";
/*
* Keep track of the sent properties per component
*
* XXX: need to clean up these contexts on an unlock or thread death
*/
private synchronized static Hashtable<String,String>
getSentProperties(String id) {
String tname = Thread.currentThread().getName();
Hashtable<String,String> sent =
(Hashtable<String,String>) Action.getContext(SENT_PROPS + id + tname);
if ( sent == null ) {
sent = new Hashtable<String,String>();
Action.registerContext(SENT_PROPS + id + tname, sent);
}
return sent;
}
/*
* Static list of some DTF specific properties that should not be sent
* over to the component otherwise they will change properties that are
* essential to the correct behavior of the DTF framework.
*/
private static HashMap<String, Integer> PROPERTIES_TO_IGNORE =
new HashMap<String, Integer>();
static {
PROPERTIES_TO_IGNORE.put(DTFProperties.DTF_NODE_TYPE, 0);
PROPERTIES_TO_IGNORE.put(DTFProperties.DTF_NODE_OS, 0);
PROPERTIES_TO_IGNORE.put(DTFProperties.DTF_NODE_OS_ARCH, 0);
PROPERTIES_TO_IGNORE.put(DTFProperties.DTF_NODE_OS_VER, 0);
}
public static void addPropertiesToIgnore(String key) {
PROPERTIES_TO_IGNORE.put(key, 0);
}
public ArrayList<Action> handleComponent(String id) throws DTFException {
Config config = Action.getConfig();
Properties props = config.getProperties();
Enumeration keys = props.keys();
ArrayList<Action> result = new ArrayList<Action>();
Hashtable<String, String> sent = getSentProperties(id);
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
if ( !PROPERTIES_TO_IGNORE.containsKey(key) ) {
if ( config.isType(key, RangeProperty.class) ) {
/*
* Ranges are treated differently because they need to be
* reconstructed on the other side.
*
* XXX: would like to make this more generic so it can be
* applied for other things other than ranges.
*/
RangeProperty rp = config.getPropertyAs(key);
String hash = sent.get(rp.getName());
if ( hash == null || !hash.equals(""+rp.hashCode()) ) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
rp.suspendState(dos);
try {
dos.close();
} catch (IOException e) {
throw new ParseException("Error transporting range",e);
}
RangeCreate rc = new RangeCreate();
rc.setName(rp.getName());
rc.bytes(baos.toByteArray());
result.add(rc);
sent.put(rp.getName(), "" + rp.hashCode());
}
} else {
String value = (String) config.getProperty(key);
if ( value == null ) {
continue;
}
String hash = sent.get(key);
BaseProperty prop = new BaseProperty();
if ( hash != null && hash.equals(value) )
continue;
prop.setName(key);
prop.setValue(value);
result.add(prop);
sent.put(key,value);
}
}
}
return result;
}
/**
* We need to make sure that when a component is unlocked we free up all
* of the property state information we may have had for that component.
*/
public void unlockComponent(String id) throws DTFException {
Hashtable<String, String> sent = getSentProperties(id);
sent.clear();
}
}