001    package org.hackystat.dailyprojectdata.resource.codeissue;
002    
003    import java.io.StringWriter;
004    import java.util.Set;
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.codeissue.jaxb.CodeIssueDailyProjectData;
018    import org.hackystat.dailyprojectdata.resource.codeissue.jaxb.CodeIssueData;
019    import org.hackystat.dailyprojectdata.resource.dailyprojectdata.DailyProjectDataResource;
020    import org.hackystat.sensorbase.client.SensorBaseClient;
021    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
022    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
023    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
024    import org.hackystat.utilities.tstamp.Tstamp;
025    import org.restlet.Context;
026    import org.restlet.data.MediaType;
027    import org.restlet.data.Request;
028    import org.restlet.data.Response;
029    import org.restlet.resource.Representation;
030    import org.restlet.resource.Variant;
031    import org.w3c.dom.Document;
032    
033    /**
034     * Implements the Resource for processing GET {host}/codeissue/{user}/{project}/{timestamp}
035     * requests. 
036     * 
037     * Authenticated user must be the uriUser, or Admin, or project member. 
038     * 
039     * @author Philip Johnson, Julie Sakuda.
040     */
041    public class CodeIssueResource extends DailyProjectDataResource {
042    
043      /** The optional code issue tool. */
044      private String tool;
045    
046      /** The optional type. */
047      private String type;
048    
049      /**
050       * The standard constructor.
051       * 
052       * @param context The context.
053       * @param request The request object.
054       * @param response The response object.
055       */
056      public CodeIssueResource(Context context, Request request, Response response) {
057        super(context, request, response);
058        this.tool = (String) request.getAttributes().get("Tool");
059        // get the type and remove any spaces
060        this.type = (String) request.getAttributes().get("Type");
061        if (this.type != null) {
062          this.type = type.replaceAll(" ", "");
063        }
064      }
065    
066      /**
067       * Returns a CodeIssueDailyProjectData instance representing the CodeIssues associated with
068       * the Project data, or null if not authorized.
069       * 
070       * @param variant The representational variant requested.
071       * @return The representation.
072       */
073      @Override
074      public Representation represent(Variant variant) {
075        Logger logger = this.server.getLogger();
076        logger.fine("CodeIssue DPD: Starting");
077        if (variant.getMediaType().equals(MediaType.TEXT_XML)) {
078          try {
079            // [1] get the SensorBaseClient for the user making this request.
080            SensorBaseClient client = super.getSensorBaseClient();
081            // [2] Check the front side cache and return if the DPD is found and is OK to access.
082            String cachedDpd = this.server.getFrontSideCache().get(uriUser, project, uriString);
083            if ((cachedDpd != null) && client.inProject(uriUser, project)) {
084              return super.getStringRepresentation(cachedDpd);
085            }
086            // [2] get a SensorDataIndex of all CodeIssue data for this Project on the requested day.
087            XMLGregorianCalendar startTime = Tstamp.makeTimestamp(this.timestamp);
088            XMLGregorianCalendar endTime = Tstamp.incrementDays(startTime, 1);
089            logger.fine("CodeIssue DPD: Requesting index: " + uriUser + " " + project);
090            SensorDataIndex index = client.getProjectSensorData(uriUser, project, startTime,
091                endTime, "CodeIssue");
092            logger.fine("CodeIssue DPD: Got index: " + index.getSensorDataRef().size() + " instances");
093    
094            // [3] Create a MultiToolSnapshot generated from all CodeIssue sensor data for this day.
095            MultiToolSnapshot snapshot = new MultiToolSnapshot();
096            for (SensorDataRef ref : index.getSensorDataRef()) {
097              SensorData data = client.getSensorData(ref);
098              snapshot.add(data);
099            }
100            logger.fine("CodeIssue DPD: retrieved all instances. Now building DPD.");
101            // [4] Create the codeIssue DPD. 
102            CodeIssueDailyProjectData codeIssue = new CodeIssueDailyProjectData();
103            
104            // [4.1] Case 1: tool and type are null. Add an entry for all CodeIssueTypes in all tools.
105            if ((this.tool == null ) && (this.type == null)) {
106              for (String tool : snapshot.getTools()) {
107                Set<SensorData> toolSnapshot = snapshot.getSensorData(tool);
108                IssueTypeCounter counter = new IssueTypeCounter(toolSnapshot, logger);
109                for (String issueType : counter.getTypes()) {
110                  codeIssue.getCodeIssueData().add(makeCodeIssueData(tool, issueType, counter));
111                }
112                // Add a zero entry if necessary.
113                if (counter.getTypes().isEmpty()) { //NOPMD
114                  codeIssue.getCodeIssueData().add(makeZeroCodeIssueData(tool));
115                }
116              }
117            }
118            
119            // [4.2] Case 2: tool is specified, type is null. Add entry for all types for this tool.
120            if ((this.tool != null ) && (this.type == null)) {
121              Set<SensorData> toolSnapshot = snapshot.getSensorData(this.tool);
122              IssueTypeCounter counter = new IssueTypeCounter(toolSnapshot, this.getLogger());
123              for (String issueType : counter.getTypes()) {
124                codeIssue.getCodeIssueData().add(makeCodeIssueData(this.tool, issueType, counter));
125              }
126              // Add a zero entry if we have data for this tool, but no issues.
127              if (!toolSnapshot.isEmpty() && counter.getTypes().isEmpty()) { //NOPMD
128                codeIssue.getCodeIssueData().add(makeZeroCodeIssueData(tool));
129              }
130            }
131            
132            // [4.3] Case 3: type is specified, tool is null. Add entry for all occurrences of this type
133            if ((this.tool == null ) && (this.type != null)) {
134              for (String tool : snapshot.getTools()) {
135                Set<SensorData> toolSnapshot = snapshot.getSensorData(tool);
136                IssueTypeCounter counter = new IssueTypeCounter(toolSnapshot, this.getLogger());
137                for (String issueType : counter.getTypes()) {
138                  if (issueType.equals(this.type)) { //NOPMD
139                    codeIssue.getCodeIssueData().add(makeCodeIssueData(tool, issueType, counter));
140                  }
141                }
142              }
143              // Not sure how to indicate 'zero' in this case.  So don't try.
144            }
145            
146            // [4.4] Case 4: tool and type are specified.  Add entry for this tool and this type.
147            if ((this.tool != null ) && (this.type != null)) {
148              Set<SensorData> toolSnapshot = snapshot.getSensorData(this.tool);
149              IssueTypeCounter counter = new IssueTypeCounter(toolSnapshot, this.getLogger());
150              for (String issueType : counter.getTypes()) {
151                if (this.type.equals(issueType)) { //NOPMD
152                  codeIssue.getCodeIssueData().add(makeCodeIssueData(this.tool, issueType, counter));
153                }
154              }
155              // Add a zero entry if we have data for this tool, but no issues of that type
156              if (!toolSnapshot.isEmpty() && !counter.getTypes().contains(this.type)) { //NOPMD
157                codeIssue.getCodeIssueData().add(makeZeroCodeIssueData(tool));
158              }
159            }
160            
161            // Now finish building the structure
162            codeIssue.setStartTime(startTime);
163            codeIssue.setOwner(uriUser);
164            codeIssue.setProject(project);
165            codeIssue.setUriPattern("**"); // we don't support UriPatterns yet.
166    
167            String xmlData = this.makeCodeIssue(codeIssue);
168            if (!Tstamp.isTodayOrLater(startTime)) {
169              this.server.getFrontSideCache().put(uriUser, project, uriString, xmlData);
170            }
171            logRequest("CodeIssue", this.tool, this.type);
172            return super.getStringRepresentation(xmlData);
173          }
174          catch (Exception e) {
175            setStatusError("Error creating CodeIssue DPD.", e);
176            return null;
177          }
178        }
179        return null;
180      }
181    
182      
183      /**
184       * Creates a returns a CodeIssueData instance.
185       * @param tool The Tool. 
186       * @param issueType The Issue Type.
187       * @param counter The CodeIssueCounter.
188       * @return The CodeIssueData instance. 
189       */
190      private CodeIssueData makeCodeIssueData(String tool, String issueType, IssueTypeCounter counter) {
191        CodeIssueData issueData = new CodeIssueData();
192        issueData.setTool(tool);
193        issueData.setIssueType(issueType);
194        issueData.setNumIssues(counter.getCount(issueType));
195        return issueData;
196      }
197      
198      /**
199       * Creates a zero CodeIssueData entry for the specified tool.
200       * @param tool The tool with zero issues. 
201       * @return The "zero" CodeIssueData entry for this tool.
202       */
203      private CodeIssueData makeZeroCodeIssueData(String tool) {
204        CodeIssueData issueData = new CodeIssueData();
205        issueData.setTool(tool);
206        issueData.setNumIssues(0);
207        return issueData;
208      }
209    
210      /**
211       * Returns the passed SensorData instance as a String encoding of its XML representation.
212       * 
213       * @param data The SensorData instance.
214       * @return The XML String representation.
215       * @throws Exception If problems occur during translation.
216       */
217      private String makeCodeIssue(CodeIssueDailyProjectData data) throws Exception {
218        JAXBContext codeIssueJAXB = (JAXBContext) this.server.getContext().getAttributes().get(
219            "CodeIssueJAXB");
220        Marshaller marshaller = codeIssueJAXB.createMarshaller();
221        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
222        dbf.setNamespaceAware(true);
223        DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
224        Document doc = documentBuilder.newDocument();
225        marshaller.marshal(data, doc);
226        DOMSource domSource = new DOMSource(doc);
227        StringWriter writer = new StringWriter();
228        StreamResult result = new StreamResult(writer);
229        TransformerFactory tf = TransformerFactory.newInstance();
230        Transformer transformer = tf.newTransformer();
231        transformer.transform(domSource, result);
232        return writer.toString();
233      }
234    }