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    }