001 package org.hackystat.dailyprojectdata.resource.issuechange; 002 003 import java.io.StringWriter; 004 import java.lang.reflect.InvocationTargetException; 005 import java.lang.reflect.Method; 006 import java.util.logging.Logger; 007 import javax.xml.bind.JAXBContext; 008 import javax.xml.bind.Marshaller; 009 import javax.xml.datatype.XMLGregorianCalendar; 010 import javax.xml.parsers.DocumentBuilder; 011 import javax.xml.parsers.DocumentBuilderFactory; 012 import javax.xml.transform.Transformer; 013 import javax.xml.transform.TransformerFactory; 014 import javax.xml.transform.dom.DOMSource; 015 import javax.xml.transform.stream.StreamResult; 016 import org.hackystat.dailyprojectdata.resource.dailyprojectdata.DailyProjectDataResource; 017 import org.hackystat.dailyprojectdata.resource.issue.IssueDataParser; 018 import org.hackystat.dailyprojectdata.resource.issue.jaxb.IssueData; 019 import org.hackystat.dailyprojectdata.resource.issuechange.jaxb.ChangedItem; 020 import org.hackystat.dailyprojectdata.resource.issuechange.jaxb.IssueChangeDailyProjectData; 021 import org.hackystat.dailyprojectdata.resource.issuechange.jaxb.IssueChangeData; 022 import org.hackystat.sensorbase.client.SensorBaseClient; 023 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex; 024 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef; 025 import org.hackystat.utilities.tstamp.Tstamp; 026 import org.restlet.Context; 027 import org.restlet.data.MediaType; 028 import org.restlet.data.Request; 029 import org.restlet.data.Response; 030 import org.restlet.resource.Representation; 031 import org.restlet.resource.Variant; 032 import org.w3c.dom.Document; 033 034 /** 035 * Implements the Resource for processing GET {host}/issue/{user}/{project}/{starttime} requests. 036 * Requires the authenticated user to be {user} or else the Admin user for the sensorbase 037 * connected to this service. 038 * @author Shaoxuan Zhang 039 */ 040 public class IssueChangeResource extends DailyProjectDataResource { 041 042 //private String status; 043 044 /** 045 * The standard constructor. 046 * @param context The context. 047 * @param request The request object. 048 * @param response The response object. 049 */ 050 public IssueChangeResource(Context context, Request request, Response response) { 051 super(context, request, response); 052 //this.status = (String) request.getAttributes().get("Status"); 053 } 054 055 /** 056 * Returns an IssueDailyProjectData instance representing the Issue associated with the 057 * Project data, or null if not authorized. 058 * Authenticated user must be the uriUser, or Admin, or project member. 059 * @param variant The representational variant requested. 060 * @return The representation. 061 */ 062 @Override 063 public Representation represent(Variant variant) { 064 Logger logger = this.server.getLogger(); 065 logger.fine("Issue DPD: Starting"); 066 if (variant.getMediaType().equals(MediaType.TEXT_XML)) { 067 try { 068 // [1] get the SensorBaseClient for the user making this request. 069 SensorBaseClient client = super.getSensorBaseClient(); 070 // [2] Check the front side cache and return if the DPD is found and is OK to access. 071 String cachedDpd = this.server.getFrontSideCache().get(uriUser, project, uriString); 072 if ((cachedDpd != null) && client.inProject(uriUser, project)) { 073 return super.getStringRepresentation(cachedDpd); 074 } 075 // [2] get a SensorDataIndex of all Issue data for this Project on the requested day. 076 XMLGregorianCalendar startTime = Tstamp.makeTimestamp(this.timestamp); 077 XMLGregorianCalendar endTime = Tstamp.incrementDays(startTime, 1); 078 logger.fine("Issue DPD: Requesting index: " + uriUser + " " + project); 079 080 XMLGregorianCalendar projectStartTime = client.getProject(uriUser, project).getStartTime(); 081 082 SensorDataIndex index = client.getProjectSensorData(uriUser, project, projectStartTime, 083 endTime, "Issue"); 084 logger.fine("Issue DPD: Got index: " + index.getSensorDataRef().size() + " instances"); 085 // [3] prepare the IssueDailyProjectData 086 IssueChangeDailyProjectData issueDpd = new IssueChangeDailyProjectData(); 087 issueDpd.setOwner(uriUser); 088 issueDpd.setProject(project); 089 issueDpd.setStartTime(startTime); 090 // [4] parse Issue SensorData. 091 int openedIssue = 0; 092 int reopenedIssue = 0; 093 int closedIssue = 0; 094 IssueDataParser parser = new IssueDataParser(this.server.getLogger()); 095 for (SensorDataRef ref : index.getSensorDataRef()) { 096 IssueData issueDataStart = parser.getIssueDpd(client.getSensorData(ref), startTime); 097 IssueData issueDataEnd = parser.getIssueDpd(client.getSensorData(ref), endTime); 098 IssueChangeData changeData = this.generateChangeData(issueDataStart, issueDataEnd); 099 if (changeData == null) { 100 continue; 101 } 102 if (issueDataEnd.getStatus() == null) { 103 issueDpd.getIssueChangeData().add(changeData); 104 continue; 105 } 106 boolean isOpenEnd = parser.isOpenStatus(issueDataEnd.getStatus()); 107 if (issueDataStart.getStatus() == null) { 108 openedIssue++; 109 if (!isOpenEnd) { 110 closedIssue++; 111 } 112 } 113 else { 114 boolean isOpenStart = parser.isOpenStatus(issueDataStart.getStatus()); 115 if (isOpenStart && !isOpenEnd) { 116 closedIssue++; 117 } 118 else if (!isOpenStart && isOpenEnd) { 119 reopenedIssue++; 120 } 121 } 122 issueDpd.getIssueChangeData().add(changeData); 123 } 124 // [5] finish the IssueDailyProjectData and send. 125 issueDpd.setOpened(openedIssue); 126 issueDpd.setReopened(reopenedIssue); 127 issueDpd.setClosed(closedIssue); 128 String xmlData = makeIssues(issueDpd); 129 if (!Tstamp.isTodayOrLater(startTime)) { 130 this.server.getFrontSideCache().put(uriUser, project, uriString, xmlData); 131 } 132 logRequest("Issue"); 133 return super.getStringRepresentation(xmlData); 134 } 135 catch (Exception e) { 136 setStatusError("Error creating Issue DPD.", e); 137 return null; 138 } 139 } 140 return null; 141 } 142 143 /** 144 * Generate the issue change data according to the different between the two give issue data. 145 * @param issueDataStart initial issue data. 146 * @param issueDataEnd after issue data. 147 * @return the issue change data. 148 */ 149 private IssueChangeData generateChangeData(IssueData issueDataStart, IssueData issueDataEnd) { 150 IssueChangeData changeData = null; 151 String[] keys = new String[]{"Milestone", "Owner", "Priority", "Status", "Type"}; 152 for (String key : keys) { 153 try { 154 Method getMethod = IssueData.class.getMethod("get" + key, (Class<?>[])null); 155 String startValue = (String)getMethod.invoke(issueDataStart, (Object[])null); 156 String endValue = (String)getMethod.invoke(issueDataEnd, (Object[])null); 157 if (!equalString(startValue, endValue)) { 158 if (changeData == null) { 159 changeData = new IssueChangeData(); 160 } 161 ChangedItem changedItem = new ChangedItem(); 162 changedItem.setKey(key); 163 changedItem.setFromValue(startValue); 164 changedItem.setToValue(endValue); 165 changeData.getChangedItem().add(changedItem); 166 } 167 } 168 catch (SecurityException e) { 169 System.err.println(e.getMessage()); 170 } 171 catch (NoSuchMethodException e) { 172 System.err.println(e.getMessage()); 173 } 174 catch (IllegalArgumentException e) { 175 System.err.println(e.getMessage()); 176 } 177 catch (IllegalAccessException e) { 178 System.err.println(e.getMessage()); 179 } 180 catch (InvocationTargetException e) { 181 System.err.println(e.getMessage()); 182 } 183 } 184 return changeData; 185 } 186 187 /** 188 * Compare the string. 189 * @param s1 one string. 190 * @param s2 the other string. 191 * @return if two objects are null, true is returned. otherwise, it is the same as String.equals. 192 */ 193 private boolean equalString(String s1, String s2) { 194 if (s1 != null) { 195 return s1.equals(s2); 196 } 197 if (s2 == null) { 198 return true; 199 } 200 return false; 201 } 202 203 /** 204 * Returns the passed SensorData instance as a String encoding of its XML representation. 205 * @param data The SensorData instance. 206 * @return The XML String representation. 207 * @throws Exception If problems occur during translation. 208 */ 209 private String makeIssues (IssueChangeDailyProjectData data) throws Exception { 210 JAXBContext devTimeJAXB = 211 (JAXBContext)this.server.getContext().getAttributes().get("IssueJAXB"); 212 Marshaller marshaller = devTimeJAXB.createMarshaller(); 213 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 214 dbf.setNamespaceAware(true); 215 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 216 Document doc = documentBuilder.newDocument(); 217 marshaller.marshal(data, doc); 218 DOMSource domSource = new DOMSource(doc); 219 StringWriter writer = new StringWriter(); 220 StreamResult result = new StreamResult(writer); 221 TransformerFactory tf = TransformerFactory.newInstance(); 222 Transformer transformer = tf.newTransformer(); 223 transformer.transform(domSource, result); 224 return writer.toString(); 225 } 226 }