001 package org.hackystat.telemetry.service.resource.chart; 002 003 import java.io.StringWriter; 004 import java.math.BigInteger; 005 006 import javax.xml.bind.JAXBContext; 007 import javax.xml.bind.Marshaller; 008 import javax.xml.datatype.XMLGregorianCalendar; 009 import javax.xml.parsers.DocumentBuilder; 010 import javax.xml.parsers.DocumentBuilderFactory; 011 import javax.xml.transform.Transformer; 012 import javax.xml.transform.TransformerFactory; 013 import javax.xml.transform.dom.DOMSource; 014 import javax.xml.transform.stream.StreamResult; 015 016 import org.hackystat.dailyprojectdata.client.DailyProjectDataClient; 017 import org.hackystat.sensorbase.client.SensorBaseClient; 018 import org.hackystat.sensorbase.resource.projects.ProjectUtils; 019 import org.hackystat.sensorbase.resource.projects.jaxb.Project; 020 import org.hackystat.sensorbase.resource.users.jaxb.User; 021 import org.hackystat.telemetry.analyzer.configuration.TelemetryChartDefinitionInfo; 022 import org.hackystat.telemetry.analyzer.configuration.TelemetryDefinitionManager; 023 import org.hackystat.telemetry.analyzer.configuration.TelemetryDefinitionManagerFactory; 024 import org.hackystat.telemetry.analyzer.configuration.TelemetryDefinitionType; 025 import org.hackystat.telemetry.analyzer.evaluator.TelemetryChartObject; 026 import org.hackystat.telemetry.analyzer.evaluator.TelemetryDefinitionResolver; 027 import org.hackystat.telemetry.analyzer.evaluator.TelemetryEvaluator; 028 import org.hackystat.telemetry.analyzer.evaluator.TelemetryStreamsObject; 029 import org.hackystat.telemetry.analyzer.evaluator.VariableResolver; 030 import org.hackystat.telemetry.analyzer.evaluator.TelemetryChartObject.SubChart; 031 import org.hackystat.telemetry.analyzer.evaluator.TelemetryStreamsObject.Stream; 032 import org.hackystat.telemetry.analyzer.language.ast.StringConstant; 033 import org.hackystat.telemetry.analyzer.language.ast.TelemetryChartDefinition; 034 import org.hackystat.telemetry.analyzer.language.ast.Variable; 035 import org.hackystat.telemetry.analyzer.model.TelemetryDataPoint; 036 import org.hackystat.telemetry.service.resource.chart.jaxb.Parameter; 037 import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryChartData; 038 import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryPoint; 039 import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryStream; 040 import org.hackystat.telemetry.service.resource.chart.jaxb.YAxis; 041 import org.hackystat.telemetry.service.resource.telemetry.TelemetryResource; 042 import org.hackystat.utilities.time.interval.DayInterval; 043 import org.hackystat.utilities.time.interval.Interval; 044 import org.hackystat.utilities.time.interval.MonthInterval; 045 import org.hackystat.utilities.time.interval.WeekInterval; 046 import org.hackystat.utilities.tstamp.Tstamp; 047 import org.restlet.Context; 048 import org.restlet.data.MediaType; 049 import org.restlet.data.Request; 050 import org.restlet.data.Response; 051 import org.restlet.resource.Representation; 052 import org.restlet.resource.Variant; 053 import org.w3c.dom.Document; 054 055 /** 056 * Processes GET {host}/chart/{chart}/{email}/{project}/{granularity}/{start}/{end} and 057 * GET {host}/chart/{chart}/{email}/{project}/{granularity}/{start}/{end}?params={params} requests. 058 * Requires the authenticated user to be {email} or else the Admin user for the sensorbase connected 059 * to this service. 060 * 061 * @author Philip Johnson 062 */ 063 public class ChartDataResource extends TelemetryResource { 064 065 private XMLGregorianCalendar startDay; 066 private XMLGregorianCalendar endDay; 067 068 /** 069 * The standard constructor. 070 * 071 * @param context The context. 072 * @param request The request object. 073 * @param response The response object. 074 */ 075 public ChartDataResource(Context context, Request request, Response response) { 076 super(context, request, response); 077 } 078 079 /** 080 * Returns an TelemetryChart instance representing the trend information associated with the 081 * Project data, or null if not authorized. 082 * 083 * @param variant The representational variant requested. 084 * @return The representation. 085 */ 086 @Override 087 public Representation represent(Variant variant) { 088 if (variant.getMediaType().equals(MediaType.TEXT_XML)) { 089 try { 090 091 // [1] Authenticate to DPD and SensorBase services; return with error if failure. 092 DailyProjectDataClient dpdClient = this.getDailyProjectDataClient(); 093 SensorBaseClient sensorBaseClient = this.getSensorBaseClient(); 094 try { 095 dpdClient.authenticate(); 096 sensorBaseClient.authenticate(); 097 } 098 catch (Exception e) { 099 setStatusError("Cannot authenticate this user", e); 100 return null; 101 } 102 103 // [2] Get the User representation, return with error if not defined. 104 User user; 105 try { 106 user = sensorBaseClient.getUser(this.uriUser); 107 } 108 catch (Exception e) { 109 setStatusError("Undefined user: " + this.uriUser, e); 110 return null; 111 } 112 113 // [3] get the Project representation; return with error if not defined. 114 Project project; 115 try { 116 project = sensorBaseClient.getProject(this.uriUser, this.projectName); 117 } 118 catch (Exception e) { 119 setStatusError(String.format("Undefined project %s for owner %s", 120 this.projectName, uriUser), e); 121 return null; 122 } 123 124 // [4] Get the chart representation, return with error if chart not defined. 125 TelemetryChartDefinition chartDef; 126 try { 127 TelemetryDefinitionManager manager = 128 TelemetryDefinitionManagerFactory.getGlobalPersistentInstance(); 129 TelemetryChartDefinitionInfo chartDefInfo = (TelemetryChartDefinitionInfo) manager.get( 130 user, this.chart, true, TelemetryDefinitionType.CHART); 131 chartDef = chartDefInfo.getChartDefinitionObject(); 132 } 133 catch (Exception e) { 134 setStatusError("Undefined chart " + this.chart, e); 135 return null; 136 } 137 138 // [5] Get the start and end days, return with error if cannot be parsed. 139 try { 140 this.startDay = Tstamp.makeTimestamp(this.start); 141 } 142 catch (Exception e) { 143 setStatusError("Bad start day: " + this.start, e); 144 return null; 145 } 146 try { 147 this.endDay = Tstamp.makeTimestamp(this.end); 148 } 149 catch (Exception e) { 150 setStatusError("Bad end day: " + this.end, e); 151 return null; 152 } 153 154 // [5.5] Make sure start and end days are OK w.r.t. project times. 155 if (!ProjectUtils.isValidStartTime(project, this.startDay)) { 156 String msg = this.startDay + " is before Project start day: " + project.getStartTime(); 157 setStatusError(msg); 158 return null; 159 } 160 if (!ProjectUtils.isValidEndTime(project, this.endDay)) { 161 String msg = this.endDay + " is after Project end day: " + project.getEndTime(); 162 setStatusError(msg); 163 return null; 164 } 165 if (Tstamp.lessThan(this.endDay, this.startDay)) { 166 String msg = this.startDay + " must be greater than: " + this.endDay; 167 setStatusError(msg); 168 return null; 169 } 170 171 // [5.6] Telemetry end date cannot be after tomorrow. 172 XMLGregorianCalendar tomorrow = Tstamp.incrementDays(Tstamp.makeTimestamp(), 1); 173 if (Tstamp.greaterThan(this.endDay, tomorrow)) { 174 String msg = this.endDay + " cannot be in the future. Change to today at the latest."; 175 setStatusError(msg); 176 return null; 177 } 178 179 // [6] Create the appropriate interval based upon granularity, or return error. 180 Interval interval; 181 try { 182 if ("day".equalsIgnoreCase(this.granularity)) { 183 interval = new DayInterval(startDay, endDay); 184 } 185 else if ("week".equalsIgnoreCase(this.granularity)) { 186 interval = new WeekInterval(startDay, endDay); 187 } 188 else if ("month".equalsIgnoreCase(this.granularity)) { 189 interval = new MonthInterval(startDay, endDay); 190 } 191 else { 192 String msg = this.granularity + " must be either 'day', 'week', or 'month'"; 193 setStatusError(msg); 194 return null; 195 } 196 } 197 catch (Exception e) { 198 String msg = this.start + " and " + this.end + " are illegal. Maybe out of order?"; 199 setStatusError(msg, e); 200 return null; 201 } 202 203 // [7] Get the parameters. 204 StringConstant[] varValues = parseParams(this.params); 205 206 // [8] Check that supplied parameters match required parameters. 207 Variable[] variables = chartDef.getVariables(); 208 if (varValues.length != variables.length) { 209 String msg = "Chart needs " + variables.length + " variables; got: " + varValues.length; 210 setStatusError(msg); 211 return null; 212 } 213 // Bind variables to values. 214 VariableResolver variableResolver = new VariableResolver(); 215 for (int i = 0; i < variables.length; i++) { 216 variableResolver.add(variables[i], varValues[i]); 217 } 218 219 // [9] Make a telemetry definition resolver and generate the TelemetryChartObject. 220 TelemetryDefinitionResolver telemetryDefinitionResolver = new TelemetryDefinitionResolver( 221 TelemetryDefinitionManagerFactory.getGlobalPersistentInstance(), user); 222 TelemetryChartObject chartObject = TelemetryEvaluator.evaluate(chartDef, 223 telemetryDefinitionResolver, variableResolver, project, dpdClient, interval); 224 225 // [10] Convert the TelemetryChartObject into it's "resource" representation. 226 TelemetryChartData chart = convertChartObjectToResource(chartObject); 227 228 // [11] Add information about the variables and parameters to the resource. 229 for (int i = 0; i < variables.length; i++) { 230 Parameter parameter = new Parameter(); 231 parameter.setName(variables[i].getName()); 232 parameter.setValue(varValues[i].getValue()); 233 chart.getParameter().add(parameter); 234 } 235 236 // [12] package the resulting data up in XML and return it. 237 logRequest(); 238 return this.getStringRepresentation(makeChartXml(chart)); 239 } 240 catch (Exception e) { 241 setStatusError("Error processing chart", e); 242 return null; 243 } 244 } 245 // Shouldn't ever get here. 246 return null; 247 } 248 249 /** 250 * Takes the TelemetryChartObject returned from the telemetry analyzer code and converts it 251 * into the resource representation. 252 * @param chartObject The TelemetryChartObject 253 * @return A TelemetryChart resource representation. 254 */ 255 private TelemetryChartData convertChartObjectToResource(TelemetryChartObject chartObject) { 256 // All of the JAXB instances will have variable names ending with "Resource" in order to 257 // distinguish them from their Telemetry Analyzer counterparts. 258 TelemetryChartData chartResource = new TelemetryChartData(); 259 chartResource.setURI(this.telemetryServer.getHostName() + "chart/" + this.chart + 260 "/" + this.uriUser + "/" + this.projectName + "/" + this.granularity + "/" + 261 this.start + "/" + this.end + "/"); 262 //Not dealing with <Parameter> yet. 263 // for each Analyzer subchart... 264 for (SubChart subChart : chartObject.getSubCharts()) { 265 // Get the Analyzer YAxis instance. 266 org.hackystat.telemetry.analyzer.evaluator.TelemetryChartObject.YAxis yaxis = 267 subChart.getYAxis(); 268 YAxis yAxisResource = new YAxis(); 269 yAxisResource.setName("Unknown"); 270 yAxisResource.setUnits(yaxis.getLabel()); 271 if (yaxis.getLowerBound() != null) { 272 yAxisResource.setLowerBound(BigInteger.valueOf(yaxis.getLowerBound().longValue())); 273 } 274 if (yaxis.getUpperBound() != null) { 275 yAxisResource.setUpperBound(BigInteger.valueOf(yaxis.getUpperBound().longValue())); 276 } 277 278 yAxisResource.setNumberType((yaxis.isIntegerAxis() ? "integer" : "double")); 279 280 // Get the StreamsObject inside the SubChart. 281 TelemetryStreamsObject streams = subChart.getTelemetryStreamsObject(); 282 // Get each Stream inside the StreamsObject... 283 for (Stream streamObject : streams.getStreams()) { 284 // Create a TelemetryStream Resource to associate with the Analyzer streamObject 285 TelemetryStream telemetryStreamResource = new TelemetryStream(); 286 telemetryStreamResource.setYAxis(yAxisResource); 287 chartResource.getTelemetryStream().add(telemetryStreamResource); 288 telemetryStreamResource.setName(streamObject.getName()); 289 // Now get the Analyzer TelemetryStream 'model' instance associated with the streamObject. 290 org.hackystat.telemetry.analyzer.model.TelemetryStream stream 291 = streamObject.getTelemetryStream(); 292 // Now iterate through the Analyzer TelemetryDataPoint instances. 293 for (TelemetryDataPoint dataPoint : stream.getDataPoints()) { 294 // Create a resource DataPoint. 295 TelemetryPoint pointResource = new TelemetryPoint(); 296 pointResource.setTime(Tstamp.makeTimestamp(dataPoint.getPeriod().getFirstDay())); 297 String val = (dataPoint.getValue() == null) ? null : dataPoint.getValue().toString(); 298 pointResource.setValue(val); 299 telemetryStreamResource.getTelemetryPoint().add(pointResource); 300 } 301 } 302 } 303 return chartResource; 304 } 305 306 /** 307 * Creates a fake TelemetryChart instance with reasonable looking internal data. 308 * 309 * @return A TelemetryChart instance. 310 * @throws Exception If problems occur in Tstamp. 311 */ 312 @SuppressWarnings("unused") 313 private TelemetryChartData makeSampleChart() throws Exception { 314 TelemetryChartData chart = new TelemetryChartData(); 315 // Set the attributes 316 chart.setURI(this.telemetryServer.getHostName() + "chart/" + this.chart); 317 // Set the parameter 318 Parameter parameter = new Parameter(); 319 parameter.setName("FilePattern"); 320 parameter.setValue("**"); 321 chart.getParameter().add(parameter); 322 // Create telemetry points. 323 TelemetryPoint point1 = new TelemetryPoint(); 324 point1.setTime(Tstamp.makeTimestamp("2007-08-01")); 325 TelemetryPoint point2 = new TelemetryPoint(); 326 point2.setTime(Tstamp.makeTimestamp("2007-08-02")); 327 TelemetryPoint point3 = new TelemetryPoint(); 328 point1.setTime(Tstamp.makeTimestamp("2007-08-03")); 329 // Create telemetry stream 330 TelemetryStream stream = new TelemetryStream(); 331 stream.getTelemetryPoint().add(point1); 332 stream.getTelemetryPoint().add(point2); 333 stream.getTelemetryPoint().add(point3); 334 // Set the stream. 335 chart.getTelemetryStream().add(stream); 336 return chart; 337 } 338 339 /** 340 * Returns the passed TelemetryChart instance as a String encoding of its XML representation. 341 * 342 * @param data The SensorData instance. 343 * @return The XML String representation. 344 * @throws Exception If problems occur during translation. 345 */ 346 private String makeChartXml(TelemetryChartData data) throws Exception { 347 JAXBContext chartJAXB = (JAXBContext) this.telemetryServer.getContext().getAttributes().get( 348 "ChartJAXB"); 349 Marshaller marshaller = chartJAXB.createMarshaller(); 350 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 351 dbf.setNamespaceAware(true); 352 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 353 Document doc = documentBuilder.newDocument(); 354 marshaller.marshal(data, doc); 355 DOMSource domSource = new DOMSource(doc); 356 StringWriter writer = new StringWriter(); 357 StreamResult result = new StreamResult(writer); 358 TransformerFactory tf = TransformerFactory.newInstance(); 359 Transformer transformer = tf.newTransformer(); 360 transformer.transform(domSource, result); 361 return writer.toString(); 362 } 363 364 /** 365 * Parses the params parameter and returns the comma-separated values as an array of 366 * StringConstant. 367 * White spaces at the beginning and end of those substrings will be trimmed. 368 * If a substring has single quotes or double quotes surrounding it, they are removed 369 * before returning. 370 * <p> 371 * If you really want to preserve white spaces at the beginning or end of the substring, 372 * use ' sub ' or " sub ". 373 * 374 * @param input A comma-separated strings. 375 * @return An array of StringConstant. 376 */ 377 private StringConstant[] parseParams(String input) { 378 String singleQuote = "'"; 379 String doubleQuote = "\""; 380 if (input == null || input.length() == 0) { 381 return new StringConstant[0]; 382 } 383 else { 384 String[] subs = input.split(","); 385 StringConstant[] constants = new StringConstant[subs.length]; 386 for (int i = 0; i < subs.length; i++) { 387 String str = subs[i].trim(); 388 if ((str.startsWith(singleQuote) && str.endsWith(singleQuote)) 389 || (str.startsWith(doubleQuote) && str.endsWith(doubleQuote))) { 390 constants[i] = new StringConstant(str.substring(1, str.length() - 1)); 391 } 392 else { 393 constants[i] = new StringConstant(str); 394 } 395 } 396 return constants; 397 } 398 } 399 400 }