001 package org.hackystat.sensor.ant.issue; 002 003 import java.lang.reflect.InvocationTargetException; 004 import java.lang.reflect.Method; 005 import java.util.Arrays; 006 import java.util.List; 007 import javax.xml.datatype.DatatypeConstants; 008 import javax.xml.datatype.XMLGregorianCalendar; 009 import org.hackystat.sensorbase.resource.sensordata.jaxb.Property; 010 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData; 011 import org.hackystat.utilities.tstamp.Tstamp; 012 013 /** 014 * An IssueEntry represent an issue in issue tracking system, always reflect the most current state. 015 * @author Shaoxuan Zhang 016 * 017 */ 018 public class IssueEntry { 019 020 /** property key of ID. */ 021 public static final String ID_PROPERTY_KEY = "IssueId"; 022 /** property key of TYPE. */ 023 public static final String TYPE_PROPERTY_KEY = "Type"; 024 /** property key of STATUS. */ 025 public static final String STATUS_PROPERTY_KEY = "Status"; 026 /** property key of PRIORITY. */ 027 public static final String PRIORITY_PROPERTY_KEY = "Priority"; 028 /** property key of MILESTONE. */ 029 public static final String MILESTONE_PROPERTY_KEY = "Milestone"; 030 /** property key of OWNER. */ 031 public static final String OWNER_PROPERTY_KEY = "Owner"; 032 033 /** timestamp separator in property value. */ 034 public static final String TIMESTAMP_SEPARATOR = "--"; 035 036 private int issueId; 037 private String type = ""; 038 private String status = ""; 039 private String priority = ""; 040 private String milestone = ""; 041 private String owner = ""; 042 /** open time of this issue, or the earliest time this issue being detected.*/ 043 private XMLGregorianCalendar openedTime = null; 044 /** close time of this issue, null if this issue is still open. */ 045 private XMLGregorianCalendar closedTime = null; 046 private XMLGregorianCalendar modifiedTime = null; 047 048 private SensorData sensorData = null; 049 050 /** 051 * Update this IssueEntry as well as the associated sensordata to the given issue table column. 052 * @param line the content of the issue table column. 053 * @param runTimestamp the run time. 054 * @param isVerbose if do the update verbosely. 055 * @return true if the sensordata is modified. 056 */ 057 public boolean upToDate(String[] line, XMLGregorianCalendar runTimestamp, boolean isVerbose) { 058 boolean modified = false; 059 modified |= checkFieldUpdate(TYPE_PROPERTY_KEY, line[1], runTimestamp, isVerbose); 060 modified |= checkFieldUpdate(STATUS_PROPERTY_KEY, line[2], runTimestamp, isVerbose); 061 modified |= checkFieldUpdate(PRIORITY_PROPERTY_KEY, line[3], runTimestamp, isVerbose); 062 modified |= checkFieldUpdate(MILESTONE_PROPERTY_KEY, line[4], runTimestamp, isVerbose); 063 modified |= checkFieldUpdate(OWNER_PROPERTY_KEY, line[5], runTimestamp, isVerbose); 064 if (modified) { 065 sensorData.setLastMod(runTimestamp); 066 } 067 return modified; 068 } 069 070 /** 071 * Check field update with the new value. 072 * @param fieldName name of the field. 073 * @param value new value. 074 * @param runTimestamp timestamp of the new value. 075 * @param isVerbose if do the update verbosely. 076 * @return true if the new value is different from the current value. 077 */ 078 private boolean checkFieldUpdate(String fieldName, String value, 079 XMLGregorianCalendar runTimestamp, boolean isVerbose) { 080 Method getMethod = null; 081 Method setMethod = null; 082 try { 083 getMethod = this.getClass().getMethod("get" + fieldName, new Class[]{}); 084 setMethod = this.getClass().getMethod("set" + fieldName, new Class[]{String.class}); 085 String oldValue = ""; 086 String newValue = value; 087 oldValue = (String)getMethod.invoke(this, new Object[]{}); 088 if (oldValue.equals(newValue)) { 089 return false; 090 } 091 if (isVerbose) { 092 System.out.println(fieldName + " has been changed from [" + oldValue + 093 "] to [" + newValue + "]."); 094 } 095 sensorData.addProperty(fieldName, newValue + TIMESTAMP_SEPARATOR + runTimestamp); 096 setMethod.invoke(this, new Object[]{newValue}); 097 return true; 098 } 099 catch (IllegalArgumentException e) { 100 e.printStackTrace(); 101 } 102 catch (IllegalAccessException e) { 103 e.printStackTrace(); 104 } 105 catch (InvocationTargetException e) { 106 e.printStackTrace(); 107 } 108 catch (SecurityException e) { 109 e.printStackTrace(); 110 } 111 catch (NoSuchMethodException e) { 112 e.printStackTrace(); 113 } 114 return false; 115 } 116 /** 117 * @param data The associated SensorData 118 * @throws Exception if error 119 */ 120 public IssueEntry(final SensorData data) throws Exception { 121 this.sensorData = data; 122 this.issueId = getIssueId(data); 123 if (this.issueId < 0) { 124 throw new Exception("Issue Id not found, " + 125 "probably because it is not Issue SensorData or malformatted"); 126 } 127 this.type = getLatestValueWithKey(this.sensorData, TYPE_PROPERTY_KEY); 128 this.status = getLatestValueWithKey(this.sensorData, STATUS_PROPERTY_KEY); 129 this.priority = getLatestValueWithKey(this.sensorData, PRIORITY_PROPERTY_KEY); 130 this.milestone = getLatestValueWithKey(this.sensorData, MILESTONE_PROPERTY_KEY); 131 this.owner = getLatestValueWithKey(this.sensorData, OWNER_PROPERTY_KEY); 132 } 133 134 /** 135 * Return the issue id of the issue sensordata. 136 * @param data the issue sensordata. 137 * @return the issue id. -1 if there is no property with key = IssueId. 138 */ 139 public static int getIssueId(final SensorData data) { 140 for (Property property : data.getProperties().getProperty()) { 141 if (ID_PROPERTY_KEY.equals(property.getKey())) { 142 return Integer.valueOf(property.getValue()); 143 } 144 } 145 return -1; 146 } 147 148 /** 149 * Get the last update time of this issue sensor data. 150 * If LastModification time is available, it will be return. 151 * Otherwise, it will process through properties to find the 152 * lastest timestamp in properties' timestamp. 153 * @return the timestamp of last udpate. NULL if no relative information found. 154 */ 155 public XMLGregorianCalendar getLastUpdateTime() { 156 if (sensorData.getLastMod() != null) { 157 return sensorData.getLastMod(); 158 } 159 XMLGregorianCalendar timestamp = null; 160 List<String> keys = Arrays.asList(new String[]{TYPE_PROPERTY_KEY, STATUS_PROPERTY_KEY, 161 PRIORITY_PROPERTY_KEY, MILESTONE_PROPERTY_KEY, OWNER_PROPERTY_KEY}); 162 for (Property property : sensorData.getProperties().getProperty()) { 163 if (keys.contains(property.getKey())) { 164 try { 165 XMLGregorianCalendar valueTimestamp = extractTimestamp(property.getValue()); 166 if (timestamp == null || Tstamp.greaterThan(valueTimestamp, timestamp)) { 167 timestamp = valueTimestamp; 168 } 169 } 170 catch (Exception e) { 171 System.out.println("Error when extracting timestamp from " + property.getValue() + 172 " Exception message: " + e.getMessage()); 173 } 174 } 175 } 176 return timestamp; 177 } 178 179 /** 180 * Extract timestamp from formatted string. 181 * @param value the string. 182 * @return the timestamp. 183 * @throws Exception if the string is not formatted. 184 */ 185 private static XMLGregorianCalendar extractTimestamp(String value) throws Exception { 186 int startIndex = value.indexOf(TIMESTAMP_SEPARATOR) + TIMESTAMP_SEPARATOR.length(); 187 return Tstamp.makeTimestamp(value.substring(startIndex)); 188 } 189 190 /** 191 * Extract value from formatted string. 192 * @param string the string. 193 * @return the value. 194 */ 195 private static String extractValue(String string) { 196 return string.substring(0, string.indexOf(TIMESTAMP_SEPARATOR)); 197 } 198 199 /** 200 * Return the latest value with the given key from the SensorData. 201 * @param sensorData the SensorData. 202 * @param key the property key 203 * @return the latest value, null if not found. 204 * @throws Exception if error when parsing property values. 205 */ 206 public static final String getLatestValueWithKey(SensorData sensorData, String key) 207 throws Exception { 208 XMLGregorianCalendar latestTime = null; 209 String value = ""; 210 for (Property property : sensorData.getProperties().getProperty()) { 211 if (key.equals(property.getKey())) { 212 XMLGregorianCalendar newTimestamp = extractTimestamp(property.getValue()); 213 if (latestTime == null || 214 latestTime.compare(newTimestamp) == DatatypeConstants.LESSER) { 215 latestTime = newTimestamp; 216 value = extractValue(property.getValue()); 217 } 218 } 219 } 220 return value; 221 } 222 223 224 /** 225 * @param id the issueId to set 226 */ 227 protected void setIssueId(int id) { 228 this.issueId = id; 229 } 230 /** 231 * @return the issueId 232 */ 233 public int getIssueId() { 234 return issueId; 235 } 236 /** 237 * @param type the type to set 238 */ 239 public void setType(String type) { 240 this.type = type; 241 } 242 /** 243 * @return the type 244 */ 245 public String getType() { 246 return type; 247 } 248 /** 249 * @param status the status to set 250 */ 251 public void setStatus(String status) { 252 this.status = status; 253 } 254 /** 255 * @return the status 256 */ 257 public String getStatus() { 258 return status; 259 } 260 /** 261 * @param priority the priority to set 262 */ 263 public void setPriority(String priority) { 264 this.priority = priority; 265 } 266 /** 267 * @return the priority 268 */ 269 public String getPriority() { 270 return priority; 271 } 272 /** 273 * @param milestone the milestone to set 274 */ 275 public void setMilestone(String milestone) { 276 this.milestone = milestone; 277 } 278 /** 279 * @return the milestone 280 */ 281 public String getMilestone() { 282 return milestone; 283 } 284 /** 285 * @param owner the owner to set 286 */ 287 public void setOwner(String owner) { 288 this.owner = owner; 289 } 290 /** 291 * @return the owner 292 */ 293 public String getOwner() { 294 return owner; 295 } 296 /** 297 * @param openedTime the openedTime to set 298 */ 299 public void setOpenedTime(XMLGregorianCalendar openedTime) { 300 this.openedTime = openedTime; 301 } 302 /** 303 * @return the openedTime 304 */ 305 public XMLGregorianCalendar getOpenedTime() { 306 return openedTime; 307 } 308 /** 309 * @param closedTime the closedTime to set 310 */ 311 protected void setClosedTime(XMLGregorianCalendar closedTime) { 312 this.closedTime = closedTime; 313 } 314 /** 315 * @return the closedTime 316 */ 317 public XMLGregorianCalendar getClosedTime() { 318 return closedTime; 319 } 320 321 /** 322 * @param sensorData the sensorData to set 323 */ 324 protected void setSensorData(SensorData sensorData) { 325 this.sensorData = sensorData; 326 } 327 328 /** 329 * @return the sensorData 330 */ 331 public SensorData getSensorData() { 332 return sensorData; 333 } 334 335 336 /** 337 * @param modifiedTime the modifiedTime to set 338 */ 339 protected void setModifiedTime(XMLGregorianCalendar modifiedTime) { 340 this.modifiedTime = modifiedTime; 341 } 342 343 344 /** 345 * @return the modifiedTime 346 */ 347 public XMLGregorianCalendar getModifiedTime() { 348 return modifiedTime; 349 } 350 351 352 }