/** * Copyright 2014 SAP AG * * Licensed 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.aim.api.measurement; import java.io.Serializable; import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import org.aim.logging.AIMLogger; import org.aim.logging.AIMLoggerFactory; import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.annotate.JsonTypeInfo; /** * The {@link AbstractRecord} encapsulates the common functionality of all * record classes. A record class is created for each monitoring event. * * @author Alexander Wert * */ @XmlAccessorType(XmlAccessType.FIELD) @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class") public abstract class AbstractRecord implements Serializable { private static final AIMLogger LOGGER = AIMLoggerFactory.getLogger(AbstractRecord.class); private static final long serialVersionUID = -4324134213075474222L; private static final Map<Class<? extends AbstractRecord>, Field[]> fields = new HashMap<Class<? extends AbstractRecord>, Field[]>(); public static final String PAR_TIMESTAMP = "timeStamp"; public static final String PAR_CALL_ID = "callId"; public static final String PAR_PROCESS_ID = "processId"; private static final String PROCESS_ID_CONSTANT = ManagementFactory.getRuntimeMXBean().getName(); @RecordValue(metric = true, name = PAR_TIMESTAMP, isTimestamp = true) long timeStamp; @RecordValue(name = PAR_CALL_ID, metric = true) long callId; @RecordValue(name = PAR_PROCESS_ID, metric = false) String processId; /** * Public default constructor required for json realization. */ public AbstractRecord() { setProcessId(PROCESS_ID_CONSTANT); // TODO: remove if possible due to overhead during monitoring for (Field field : getAllParameterFields()) { field.setAccessible(true); } } /** * Constructor. * * @param timestamp * timestamp of the record. */ public AbstractRecord(long timestamp) { this(); setTimeStamp(timestamp); } /** * * @return timestamp */ public long getTimeStamp() { return timeStamp; } /** * Sets the timestamp. * * @param timeStamp * the timestamp */ public void setTimeStamp(long timeStamp) { this.timeStamp = timeStamp; } /** * Converts the record to a string array. * * @return string array representation of the record */ public String[] toStringArray() { List<Object> list = toList(); String[] strArray = new String[list.size()]; int i = 0; for (Object o : list) { if (o instanceof String) { o = ((String) o).replace(";", "#sc#"); } strArray[i] = o.toString(); i++; } return strArray; } /** * Converts the record to an Object array. * * @return Object array representation of the record */ public Object[] toArray() { return toList().toArray(); } /** * Converts the record to an Object list. * * @return Object list representation of the record */ public List<Object> toList() { try { List<Object> values = new ArrayList<Object>(); for (Field field : getAllParameterFields()) { field.setAccessible(true); values.add(field.get(this)); } return values; } catch (Exception e) { throw new RuntimeException(e); } } /** * Fills the content of the record from the given string array. * * @param array * array of strings containing the values for the record */ public void fromStringArray(String[] array) { Field[] fieldsArray = getAllParameterFields(); if (array.length < fieldsArray.length) { throw new RuntimeException("Number of values does not match number of record fields!"); } try { for (int i = 0; i < fieldsArray.length; i++) { Field field = fieldsArray[i]; field.setAccessible(true); field.set(this, stringToType(array[i], field.getType())); } } catch (Exception e) { LOGGER.error("Monitoring error. Reason: {}", e); } } /** * Fills the content of the record from the given String list. * * @param list * list of string containing the values for the record */ public void fromStringList(List<String> list) { Field[] fieldsArray = getAllParameterFields(); if (list.size() < fieldsArray.length) { throw new RuntimeException("Number of values does not match number of record fields!"); } try { for (int i = 0; i < fieldsArray.length; i++) { Field field = fieldsArray[i]; field.setAccessible(true); field.set(this, stringToType(list.get(i), field.getType())); } } catch (Exception e) { LOGGER.error("Monitoring error. Reason: {}", e); } } /** * Fills the content of the record from the given Object array. * * @param array * array of Objects containing the values for the record */ public void fromArray(Object[] array) { Field[] fieldsArray = getAllParameterFields(); if (array.length < fieldsArray.length) { throw new RuntimeException("Number of values does not match number of record fields!"); } try { for (int i = 0; i < fieldsArray.length; i++) { Field field = fieldsArray[i]; field.setAccessible(true); field.set(this, array[i]); } } catch (Exception e) { LOGGER.error("Monitoring error. Reason: {}", e); } } /** * Fills the content of the record from the given Object list. * * @param list * list of Objects containing the values for the record */ public void fromList(List<Object> list) { Field[] fieldsArray = getAllParameterFields(); if (list.size() < fields.size()) { throw new RuntimeException("Number of values does not match number of record fields!"); } try { for (int i = 0; i < fields.size(); i++) { Field field = fieldsArray[i]; field.setAccessible(true); field.set(this, list.get(i)); } } catch (Exception e) { LOGGER.error("Monitoring error. Reason: {}", e); } } /** * Returns the list of all record fields. * * @return list of input/observation parameter fields */ @JsonIgnore public Field[] getAllParameterFields() { if (!fields.containsKey(this.getClass())) { List<Field> fieldList = new ArrayList<Field>(); Class<?> clazz = this.getClass(); while (!clazz.equals(Object.class)) { for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(RecordValue.class)) { fieldList.add(field); } } clazz = clazz.getSuperclass(); } Collections.sort(fieldList, new Comparator<Field>() { public int compare(Field o1, Field o2) { return o1.getName().compareTo(o2.getName()); } }); fields.put(this.getClass(), fieldList.toArray(new Field[0])); } return fields.get(this.getClass()); } /** * Returns a list of all parameter names (record properties). * * @return a list of all parameter names */ @JsonIgnore public List<String> getAllParameterNames() { List<String> values = new ArrayList<String>(); for (Field field : getAllParameterFields()) { values.add(field.getName()); } return values; } /** * Returns a list of all parameter names (record properties) which are * NON-metric properties (input parameters). * * @return a list of all parameter names (record properties) which are * NON-metric properties (input parameters) */ @JsonIgnore public List<String> getNonMetricParameterNames() { List<String> values = new ArrayList<String>(); for (Field field : getAllParameterFields()) { if (!field.getAnnotation(RecordValue.class).metric()) { try { values.add(field.getName()); } catch (Exception e) { throw new RuntimeException(e); } } } return values; } /** * Returns a list of all parameter names (record properties) which are * NON-metric properties (input parameters). * * @return a list of all parameter names (record properties) which are * NON-metric properties (input parameters) */ @JsonIgnore public List<Integer> getNonMetricParameterIndeces() { List<Integer> values = new ArrayList<Integer>(); int i = 0; for (Field field : getAllParameterFields()) { if (!field.getAnnotation(RecordValue.class).metric()) { try { values.add(i); } catch (Exception e) { throw new RuntimeException(e); } } i++; } return values; } /** * Returns a list of all parameter names (record properties) which are * metric properties (observation parameters). * * @return a list of all parameter names (record properties) which are * metric properties (observation parameters) */ @JsonIgnore public Set<String> getMetricParameterNames() { Set<String> values = new HashSet<String>(); for (Field field : getAllParameterFields()) { if (field.getAnnotation(RecordValue.class).metric()) { try { values.add(field.getName()); } catch (Exception e) { throw new RuntimeException(e); } } } return values; } /** * Returns a list of all parameter values (record properties values) which * are NON-metric properties (input parameters). * * @return a list of all parameter values (record properties values) which * are NON-metric properties (input parameters) */ @JsonIgnore public List<Object> getNonMetricValues() { List<Object> values = new ArrayList<Object>(); for (Field field : getAllParameterFields()) { if (!field.getAnnotation(RecordValue.class).metric()) { try { values.add(field.get(this)); } catch (Exception e) { throw new RuntimeException(e); } } } return values; } /** * Returns a list of all parameter values (record properties values) which * are metric properties (observation parameters). * * @return a list of all parameter values (record properties values) which * are metric properties (observation parameters) */ @JsonIgnore public List<Object> getMetricValues() { List<Object> values = new ArrayList<Object>(); for (Field field : getAllParameterFields()) { if (field.getAnnotation(RecordValue.class).metric()) { try { values.add(field.get(this)); } catch (Exception e) { throw new RuntimeException(e); } } } return values; } /** * Returns the value for the given field name. * * @param fieldName * name of the field * @return value of the field or null if a field with that name could not be * found */ @JsonIgnore public Object getValue(String fieldName) { try { for (Field field : getAllParameterFields()) { if (field.getName().equals(fieldName)) { return field.get(this); } } return null; } catch (Exception e) { throw new RuntimeException(e); } } /** * Returns the type for the given field name. * * @param fieldName * name of the field * @return type of the field or null if a field with that name could not be * found */ @JsonIgnore public Class<?> getType(String fieldName) { try { for (Field field : getAllParameterFields()) { if (field.getName().equals(fieldName)) { return field.getType(); } } return null; } catch (Exception e) { throw new RuntimeException(e); } } /** * Returns a string representation of the record, where all values are * represented as strings separated by a semicolon (;). * * @return string representation of the record */ @Override public String toString() { StringBuilder builder = new StringBuilder(); try { for (Field field : getAllParameterFields()) { if (field.getType().equals(String.class)) { builder.append((field.get(this) != null) ? (field.get(this).toString().replace(";", "#sc#")) : ("")); } else { builder.append((field.get(this) != null) ? (field.get(this).toString()) : ("")); } builder.append(';'); } } catch (Exception e) { throw new RuntimeException(e); } builder.append(this.getClass().getCanonicalName()); return builder.toString(); } /** * Creates a specific record object from a string representation. * * @param stringRep * string representation of the record * @return a specific record object or null if record could not be created * from string */ public static AbstractRecord fromString(String stringRep) { try { String className = stringRep.substring(stringRep.lastIndexOf(';') + 1, stringRep.length()); String[] values = stringRep.substring(0, stringRep.lastIndexOf(';')).split(";"); AbstractRecord record = (AbstractRecord) Class.forName(className).newInstance(); record.fromStringArray(values); return record; } catch (Exception e) { LOGGER.warn("Failed creating record from string, reason: {}", e); return null; } } /** * Relativises the timestamp of this record against the passed * relativiseAgainst timestamp. * * @param relativiseAgainst * timestamp to relativise against */ public void relativiseTimestamps(long relativiseAgainst) { try { for (Field field : getAllParameterFields()) { if (field.getAnnotation(RecordValue.class).isTimestamp()) { long newTimestamp = field.getLong(this) - relativiseAgainst; field.set(this, newTimestamp); } } } catch (Exception e) { LOGGER.error("Monitoring error. Reason: {}", e); } } /** * Creates an object of given type from a string * * @param str * string to be parsed * @param type * type of interest * @return an object of the passed type for the passed string */ private Object stringToType(String str, Type type) { if (type instanceof Class) { String typeName = ((Class<?>) type).getSimpleName(); if (typeName.equalsIgnoreCase("int") || typeName.equalsIgnoreCase("integer")) { return Integer.parseInt(str); } else if (typeName.equalsIgnoreCase("long")) { return Long.parseLong(str); } else if (typeName.equalsIgnoreCase("short")) { return Short.parseShort(str); } else if (typeName.equalsIgnoreCase("byte")) { return Byte.parseByte(str); } else if (typeName.equalsIgnoreCase("char") || typeName.equalsIgnoreCase("character")) { return new Character(str.charAt(0)); } else if (typeName.equalsIgnoreCase("double")) { return Double.parseDouble(str); } else if (typeName.equalsIgnoreCase("string")) { return str.replace("#sc#", ";"); } else if (typeName.equalsIgnoreCase("boolean")) { return Boolean.parseBoolean(str); } } LOGGER.error("Monitoring error. Invalid value type of a record parameter!"); return null; } /** * @return the callId */ public long getCallId() { return callId; } /** * @param callId * the callId to set */ public void setCallId(long callId) { this.callId = callId; } /** * @return the processId */ public String getProcessId() { return processId; } /** * @param processId * the processId to set */ public void setProcessId(String processId) { this.processId = processId; } }