001    package org.hackystat.dailyprojectdata.resource.coverage;
002    
003    import java.io.StringWriter;
004    import java.util.List;
005    import java.util.logging.Logger;
006    
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    
017    import org.hackystat.dailyprojectdata.resource.coverage.jaxb.ConstructData;
018    import org.hackystat.dailyprojectdata.resource.coverage.jaxb.CoverageDailyProjectData;
019    import org.hackystat.dailyprojectdata.resource.dailyprojectdata.DailyProjectDataResource;
020    import org.hackystat.sensorbase.client.SensorBaseClient;
021    import org.hackystat.sensorbase.resource.sensordata.jaxb.Property;
022    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
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
036     * {host}/coverage/{user}/{project}/{timestamp}/{type} requests. 
037     * 
038     * Authenticated user must be the uriUser, or Admin, or project member. 
039     * 
040     * @author jsakuda
041     * @author austen
042     */
043    public class CoverageResource extends DailyProjectDataResource {
044      private final String granularity;
045    
046      /**
047       * The standard constructor.
048       * 
049       * @param context The context.
050       * @param request The request object.
051       * @param response The response object.
052       */
053      public CoverageResource(Context context, Request request, Response response) {
054        super(context, request, response);
055        this.granularity = (String) request.getAttributes().get("granularity");
056      }
057    
058      /**
059       * Returns an CoverageDailyProjectData instance representing the Coverage
060       * associated with the Project data for the given day and granularity.
061       * @param variant The representational variant requested.
062       * @return The representation.
063       */
064      @Override
065      public Representation represent(Variant variant) {
066        Logger logger = this.server.getLogger();
067        logger.fine("Coverage DPD: Starting");
068        if (variant.getMediaType().equals(MediaType.TEXT_XML)) {
069          try {
070            // [1] get the SensorBaseClient for the user making this request.
071            SensorBaseClient client = super.getSensorBaseClient();
072            // [2] Check the front side cache and return if the DPD is found and is OK to access.
073            String cachedDpd = this.server.getFrontSideCache().get(uriUser, project, uriString);
074            if ((cachedDpd != null) && client.inProject(uriUser, project)) {
075              return super.getStringRepresentation(cachedDpd);
076            }
077            // [2] Get the latest snapshot of Coverage data for this Project on the requested day.
078            XMLGregorianCalendar startTime = Tstamp.makeTimestamp(this.timestamp);
079            XMLGregorianCalendar endTime = Tstamp.incrementDays(startTime, 1);
080            logger.fine("Coverage DPD: Requesting index: " + uriUser + " " + project);
081            SensorDataIndex snapshot = 
082              client.getProjectSensorDataSnapshot(this.uriUser, this.project, startTime, endTime, 
083                  "Coverage");
084            logger.fine("Coverage DPD: Got index: " + snapshot.getSensorDataRef().size() 
085                + " instances");
086            // [3] Create the Coverage DPD.
087            CoverageDailyProjectData coverageData = new CoverageDailyProjectData();
088            coverageData.setProject(this.project);
089            coverageData.setStartTime(startTime);
090            coverageData.setGranularity(this.granularity);
091            
092            // [4] If data, then add ConstructData instances for required granularity.
093            if (!snapshot.getSensorDataRef().isEmpty()) {
094              logger.fine("There is data to process");
095              // Add a ConstructData instance if this sensor data contains the appropriate granularity.
096              for (SensorDataRef ref : snapshot.getSensorDataRef()) {
097                SensorData data = client.getSensorData(ref);
098                logger.fine ("Sensor Data: " + data);
099                coverageData.setOwner(data.getOwner()); 
100                coverageData.setTool(data.getTool()); 
101                String coveredKey = this.granularity.toLowerCase() + "_Covered";
102                String uncoveredKey = this.granularity.toLowerCase() + "_Uncovered";
103                logger.fine("Covered: " + coveredKey + " " + this.getProperty(data, coveredKey));
104                logger.fine("Uncovered: " + uncoveredKey + " " + this.getProperty(data, coveredKey));
105                Integer covered = this.getCoverageValue(data, coveredKey);
106                Integer uncovered = this.getCoverageValue(data, uncoveredKey);
107                logger.fine("Covered num: " + covered);
108                logger.fine("Uncovered num: " + uncovered);
109                if ((covered != null) && (uncovered != null)) { //NOPMD
110                  ConstructData construct = new ConstructData();
111                  construct.setName(data.getResource());
112                  construct.setNumCovered(covered);
113                  construct.setNumUncovered(uncovered);
114                  coverageData.getConstructData().add(construct);
115                }
116              }
117            }
118            logger.fine("Coverage DPD: Finished processing instances.");
119            // Now return the CoverageDPD instance. 
120            String xmlData = this.makeCoverage(coverageData);
121            if (!Tstamp.isTodayOrLater(startTime)) {
122              this.server.getFrontSideCache().put(uriUser, project, uriString, xmlData);
123            }
124            logRequest("Coverage", this.granularity);
125            return super.getStringRepresentation(xmlData);
126          }
127          catch (Exception e) {
128            setStatusError("Error creating Coverage DPD.", e);
129            return null;
130          }
131        }
132        return null;
133      }
134    
135      /**
136       * Returns the passed SensorData instance as a String encoding of its XML
137       * representation.
138       * 
139       * @param data The SensorData instance.
140       * @return The XML String representation.
141       * @throws Exception If problems occur during translation.
142       */
143      private String makeCoverage(CoverageDailyProjectData data) throws Exception {
144        JAXBContext codeIssueJAXB = (JAXBContext) this.server.getContext().getAttributes().get(
145            "CoverageJAXB");
146        Marshaller marshaller = codeIssueJAXB.createMarshaller();
147        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
148        dbf.setNamespaceAware(true);
149        DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
150        Document doc = documentBuilder.newDocument();
151        marshaller.marshal(data, doc);
152        DOMSource domSource = new DOMSource(doc);
153        StringWriter writer = new StringWriter();
154        StreamResult result = new StreamResult(writer);
155        TransformerFactory tf = TransformerFactory.newInstance();
156        Transformer transformer = tf.newTransformer();
157        transformer.transform(domSource, result);
158        return writer.toString();
159      }
160      
161      /**
162       * Returns the string value associated with the specified property key. If no
163       * property with that key exists, null is returned.
164       * @param data The sensor data instance whose property list will be searched.
165       * @param key the property key to search for.
166       * @return The property value with the specified key or null.
167       */
168      private String getProperty(SensorData data, String key) {
169        List<Property> propertyList = data.getProperties().getProperty();
170        for (Property property : propertyList) {
171          if (key.equals(property.getKey())) {
172            return property.getValue();
173          }
174        }
175        return null;
176      }
177      
178      /**
179       * Returns the Coverage value at the given Granularity, or null if not present or not 
180       * parsable to an Integer. 
181       * @param data The SensorData instance to inspect for the given granularity.
182       * @param granularityKey One of line_Covered, line_Uncovered, method_Covered, etc.
183       * @return The coverage integer, or null if not found.
184       */
185      private Integer getCoverageValue(SensorData data, String granularityKey) {
186        String prop = getProperty(data, granularityKey);
187        Double coverageNum = null;
188        try {
189          coverageNum = Double.parseDouble(prop);
190        }
191        catch (Exception e) { //NOPMD
192          // Don't do anything, ok to drop through.
193        }
194        return ((coverageNum == null) ? null : coverageNum.intValue());
195      }
196    }