/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. This program is distributed
* in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.agent;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.util.GenericValueMap;
/**
* An object representing key/value pairs to be passed to, and returned from,
* remote method calls.
*
* This object provides a way for values to be sent and received in a way which
* abstracts the serialization or protocol implementation.
*/
public class AgentRemoteValue implements GenericValueMap, Externalizable {
private static final long serialVersionUID = 644682754857767836L;
private static Log _log = LogFactory.getLog(AgentRemoteValue.class);
private Map<String, String> vals = new LinkedHashMap<String, String>();;
private static final int MAX_VALUE_SIZE = 65535;
private static final String CHUNK_PREFIX = "-chunk";
/**
* Create a new AgentRemoteValue object with default innards.
*/
public AgentRemoteValue() {
}
/**
* Create a new AgentRemoteValue object with some default key/value pairs.
*
* @param keyvals an array of arrays containing 2 elements. The first
* element is the key, and the second is its associated value.
*
* @throws IllegalArgumentException indicating the passed array contained
* sub-arrays of size != 2.
*/
public AgentRemoteValue(String keyvals[][]) throws IllegalArgumentException {
for (int i = 0; i < keyvals.length; i++) {
if (keyvals[i].length != 2) {
throw new IllegalArgumentException("Arg index " + i + "didn't contain 2 values");
}
this.setValue(keyvals[i][0], keyvals[i][1]);
}
}
/**
* Setup a key/value pair.
*
* @param key Key to assign the value to
* @param val Value to assign to the key
*/
public void setValue(String key, String val) {
if (key == null || val == null) {
_log.warn("Invalid key/value found. Key='" + key + "' value='" + val + "'");
return;
}
this.vals.put(key, val);
}
/**
* Retrieve a value based on the key.
*
* @param key Key for which to get the value.
*
* @return the value of a previously set key
*/
public String getValue(String key) {
return (String) this.vals.get(key);
}
/**
* Get a value, interpreted as a long integer.
*
* @param key Key for which to get the value.
*
* @return The value for the key 'key', as a long value.
*
* @throws AgentRemoteException if the value cannot be interpted as a long.
*/
public long getValueAsLong(String key) throws AgentRemoteException {
String val = this.getValue(key);
try {
return Long.parseLong(val);
} catch (NumberFormatException exc) {
throw new AgentRemoteException("Value is not a long, '" + val + "'");
}
}
/**
* Get a value, interpreted as a double.
*
* @param key Key for which to get the value.
*
* @return The value for the key 'key', as a double.
*
* @throws AgentRemoteException if the value cannot be interpted as double.
*/
public double getValueAsDouble(String key) throws AgentRemoteException {
String val = this.getValue(key);
try {
return Double.parseDouble(val);
} catch (NumberFormatException exc) {
throw new AgentRemoteException("Value is not a double, '" + val + "'");
}
}
/**
* Get a value, interpreted as an integer.
*
* @param key Key for which to get the value.
*
* @return The value for the key 'key', as an integer value.
*
* @throws AgentRemoteException if the value cannot be interpted as an int.
*/
public int getValueAsInt(String key) throws AgentRemoteException {
String val = this.getValue(key);
try {
return Integer.parseInt(val);
} catch (NumberFormatException exc) {
throw new AgentRemoteException("Value is not an integer, '" + val + "'");
}
}
public void toStream(DataOutput os) throws IOException {
for (Entry<String, String> entry : this.vals.entrySet()) {
String key = entry.getKey();
String val = entry.getValue();
// check if value is too large for writeUTF
if (val.length() > MAX_VALUE_SIZE) {
writeChunkedValues(key, val, os);
} else {
os.writeUTF(key);
os.writeUTF(val);
}
}
os.writeUTF(new String(""));
}
private void writeChunkedValues(String key, String val, DataOutput os) throws IOException {
// now chunk the values
int num = 0;
while ((num * MAX_VALUE_SIZE) < val.length()) {
int start = num * MAX_VALUE_SIZE;
int end = ((start + MAX_VALUE_SIZE) > val.length()) ? val.length() : start +
MAX_VALUE_SIZE;
String chunk = val.substring(start, end);
os.writeUTF(CHUNK_PREFIX +"."+ key + "." + num);
os.writeUTF(chunk);
num++;
}
}
public Set getKeys() {
return this.vals.keySet();
}
public static AgentRemoteValue fromStream(DataInput is) throws IOException {
Map<String, String> chunkedValues = new LinkedHashMap<String, String>();
AgentRemoteValue res = new AgentRemoteValue();
String key, val;
key = is.readUTF();
while (key.length() != 0) {
val = is.readUTF();
if (Pattern.matches("^"+CHUNK_PREFIX+"[.].*[.](\\d)", key)) {
chunkedValues.put(key, val);
} else {
res.setValue(key, val);
}
key = is.readUTF();
}
for (Entry<String, String> entry : getUnchunkedValues(chunkedValues).entrySet()) {
res.setValue(entry.getKey(), entry.getValue());
}
return res;
}
private static Map<String, String> getUnchunkedValues(Map<String, String> chunkedValues) {
Map<String, String> unchunked = new LinkedHashMap<String, String>();
for (Entry<String, String> chunkedEntry : chunkedValues.entrySet()) {
String chunkParamKey = chunkedEntry.getKey().substring(CHUNK_PREFIX.length()+1);
String paramKey = chunkParamKey.substring(0, chunkParamKey.indexOf("."));
if (unchunked.containsKey(paramKey)) {
unchunked.put(paramKey, unchunked.get(paramKey).concat(chunkedEntry.getValue()));
} else {
unchunked.put(paramKey, chunkedEntry.getValue());
}
}
return unchunked;
}
public String toString() {
return this.vals.toString().replaceAll("(-P,? ?)([^ ,]+)", "$1******").replaceAll("(pass[^=]*=)(\\w*)", "$1******").replaceAll("(Pass[^=]*=)(\\w*)", "$1******");
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
AgentRemoteValue res = fromStream(in);
this.vals = res.vals;
}
public void writeExternal(ObjectOutput out) throws IOException {
toStream(out);
}
}