001    package org.hackystat.dailyprojectdata.resource.build;
002    
003    import static org.hackystat.dailyprojectdata.server.ServerProperties.SENSORBASE_FULLHOST_KEY;
004    
005    import java.io.StringWriter;
006    import java.util.List;
007    import java.util.Map;
008    import java.util.Set;
009    import java.util.logging.Logger;
010    
011    import javax.xml.bind.JAXBContext;
012    import javax.xml.bind.Marshaller;
013    import javax.xml.datatype.XMLGregorianCalendar;
014    import javax.xml.parsers.DocumentBuilder;
015    import javax.xml.parsers.DocumentBuilderFactory;
016    import javax.xml.transform.Transformer;
017    import javax.xml.transform.TransformerFactory;
018    import javax.xml.transform.dom.DOMSource;
019    import javax.xml.transform.stream.StreamResult;
020    
021    import org.hackystat.dailyprojectdata.resource.build.jaxb.BuildDailyProjectData;
022    import org.hackystat.dailyprojectdata.resource.build.jaxb.MemberData;
023    import org.hackystat.dailyprojectdata.resource.dailyprojectdata.DailyProjectDataResource;
024    import org.hackystat.sensorbase.client.SensorBaseClient;
025    import org.hackystat.sensorbase.resource.sensordata.jaxb.Properties;
026    import org.hackystat.sensorbase.resource.sensordata.jaxb.Property;
027    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
028    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
029    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
030    import org.hackystat.utilities.tstamp.Tstamp;
031    import org.restlet.Context;
032    import org.restlet.data.MediaType;
033    import org.restlet.data.Request;
034    import org.restlet.data.Response;
035    import org.restlet.resource.Representation;
036    import org.restlet.resource.Variant;
037    import org.w3c.dom.Document;
038    
039    /**
040     * Implements the Resource for processing GET {host}/build/{user}/{project}/{timestamp}
041     * requests. 
042     * Authenticated user must be the uriUser, or Admin, or project member. 
043     * 
044     * @author Philip Johnson
045     */
046    public class BuildResource extends DailyProjectDataResource {
047    
048      /** The optional type. */
049      private String type;
050    
051      /**
052       * The standard constructor.
053       * 
054       * @param context The context.
055       * @param request The request object.
056       * @param response The response object.
057       */
058      public BuildResource(Context context, Request request, Response response) {
059        super(context, request, response);
060        this.type = (String) request.getAttributes().get("Type");
061      }
062    
063      /**
064       * Returns a BuildDailyProjectData instance representing the build totals associated with the
065       * Project data, or null if not authorized.
066       * 
067       * @param variant The representational variant requested.
068       * @return The representation.
069       */
070      @Override
071      public Representation represent(Variant variant) {
072        Logger logger = this.server.getLogger();
073        logger.fine("Build DPD: Starting");
074        if (variant.getMediaType().equals(MediaType.TEXT_XML)) {
075          try {
076            // [1] get the SensorBaseClient for the user making this request.
077            SensorBaseClient client = super.getSensorBaseClient();
078            // [2] Check the front side cache and return if the DPD is found and is OK to access.
079            String cachedDpd = this.server.getFrontSideCache().get(uriUser, project, uriString);
080            if ((cachedDpd != null) && client.inProject(uriUser, project)) {
081              return super.getStringRepresentation(cachedDpd);
082            }
083            // [2] get a SensorDataIndex of all Build data for this Project on the requested day.
084            XMLGregorianCalendar startTime = Tstamp.makeTimestamp(this.timestamp);
085            XMLGregorianCalendar endTime = Tstamp.incrementDays(startTime, 1);
086            logger.fine("Build DPD: Requesting index: " + uriUser + " " + project);
087            SensorDataIndex index = null;
088            index = client.getProjectSensorData(uriUser, project, startTime,
089                endTime, "Build");
090            logger.fine("Build DPD: Got index: " + index.getSensorDataRef().size() + " instances");
091            // [3] update the build data counter
092            MemberBuildCounter counter = new MemberBuildCounter();
093            List<SensorDataRef> sensorDataRefList = index.getSensorDataRef();
094            for (SensorDataRef sensorDataRef : sensorDataRefList) {
095              SensorData data = client.getSensorData(sensorDataRef);
096              String result = this.getPropertyValue(data, "Result");
097              boolean valid = this.isValidData(data);
098              if (valid && "Success".equals(result)) {
099                counter.addSuccessfulBuild(data.getOwner());
100              }
101              else if (valid && "Failure".equals(result)) {
102                counter.addFailedBuild(data.getOwner());
103              }
104            }
105            logger.fine("Build DPD: retrieved all instances, now building the DPD.");
106            // [4] create and return the BuildDailyProjectData
107            BuildDailyProjectData build = new BuildDailyProjectData();
108            String sensorBaseHost = this.server.getServerProperties().get(SENSORBASE_FULLHOST_KEY);
109            Map<String, Integer> successfulBuilds = counter.getSuccessfulBuilds();
110            Map<String, Integer> failedBuilds = counter.getFailedBuilds();
111    
112            Set<String> members = counter.getMembers();
113            for (String member : members) {
114              MemberData memberData = new MemberData();
115              memberData.setMemberUri(sensorBaseHost + "users/" + member);
116              Integer failures = failedBuilds.get(member);
117              if (failures == null) {
118                // no mapping for member in failed builds, means user had no failed builds
119                failures = 0;
120              }
121              memberData.setFailure(failures);
122    
123              Integer successes = successfulBuilds.get(member);
124              if (successes == null) {
125                // no mapping for member in successful builds, means user had no successful builds
126                successes = 0;
127              }
128              memberData.setSuccess(successes);
129    
130              build.getMemberData().add(memberData);
131            }
132            
133            build.setOwner(uriUser);
134            build.setProject(project);
135            build.setStartTime(startTime);
136            if (this.type == null) {
137              build.setType("*");
138            }
139            else {
140              build.setType(this.type);
141            }
142            
143            String xmlData = this.makeBuild(build);
144            if (!Tstamp.isTodayOrLater(startTime)) {
145              this.server.getFrontSideCache().put(uriUser, project, uriString, xmlData);
146            }
147            logRequest("Build", this.type);
148            return super.getStringRepresentation(xmlData);
149          }
150          catch (Exception e) {
151            setStatusError("Error creating Build DPD", e);
152            return null;
153          }
154        }
155        return null;
156      }
157    
158      /**
159       * Determines if the sensor data matches the specified Type.
160       * 
161       * @param data The sensor data to check for a valid Type.
162       * @return Returns true if the data should be processed or false if it does not match the
163       *         Type.
164       */
165      private boolean isValidData(SensorData data) {
166        // no type, or Type=* are wildcards for all data
167        if (this.type == null || "*".equals(this.type)) {
168          return true;
169        }
170    
171        String typeValue = getPropertyValue(data, "Type");
172        return this.type.equals(typeValue);
173      }
174    
175      /**
176       * Gets the value for the given property name from the <code>Properties</code> object
177       * contained in the given sensor data instance.
178       * 
179       * @param data The sensor data instance to get the property from.
180       * @param propertyName The name of the property to get the value for.
181       * @return Returns the value of the property or null if no matching property was found.
182       */
183      private String getPropertyValue(SensorData data, String propertyName) {
184        Properties properties = data.getProperties();
185        if (properties != null) {
186          List<Property> propertyList = properties.getProperty();
187          for (Property property : propertyList) {
188            if (property.getKey().equals(propertyName)) {
189              return property.getValue();
190            }
191          }
192        }
193        return null;
194      }
195      
196      /**
197       * Returns the passed SensorData instance as a String encoding of its XML representation.
198       * 
199       * @param data The SensorData instance.
200       * @return The XML String representation.
201       * @throws Exception If problems occur during translation.
202       */
203      private String makeBuild(BuildDailyProjectData data) throws Exception {
204        JAXBContext buildJAXB = (JAXBContext) this.server.getContext().getAttributes().get(
205            "BuildJAXB");
206        Marshaller marshaller = buildJAXB.createMarshaller();
207        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
208        dbf.setNamespaceAware(true);
209        DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
210        Document doc = documentBuilder.newDocument();
211        marshaller.marshal(data, doc);
212        DOMSource domSource = new DOMSource(doc);
213        StringWriter writer = new StringWriter();
214        StreamResult result = new StreamResult(writer);
215        TransformerFactory tf = TransformerFactory.newInstance();
216        Transformer transformer = tf.newTransformer();
217        transformer.transform(domSource, result);
218        return writer.toString();
219      }
220    }