private List<Range> getUnsharedRanges(String streamOwner, String streamName)
      throws ClassNotFoundException, SQLException, IOException, NamingException,
          NoSuchAlgorithmException {
    /*double start = 1392883208801.0;
    double end = 1392936762729.0;
    double d = (end - start) / 24;
    resultRanges.add(new Range(start, start + d));
    resultRanges.add(new Range(start + 2*d, start + 3*d));
    resultRanges.add(new Range(start + 4*d, start + 5*d));
    resultRanges.add(new Range(start + 6*d, start + 7*d));
    resultRanges.add(new Range(start + 8*d, start + 9*d));*/

    prepareDummyStream(streamOwner, streamName);

    int[] shareflags = queryUsingDummyStreamAsOtherUser(streamOwner, streamName);

    // dump share flags
    StringBuilder sb = new StringBuilder();
    sb.append("flags = [");
    for (int f : shareflags) {
      sb.append(f);
      sb.append(",");
    }
    sb.deleteCharAt(sb.length() - 1);
    sb.append("]");
    Log.info(sb.toString());

    // Generate unshared ranges
    List<Range> resultRanges = new ArrayList<Range>();
    int prevflag = shareflags[0];
    double curStartTime = -1;
    if (shareflags[0] == 0) {
      curStartTime = startDate.getMillis();
    }
    for (int i = 1; i < shareflags.length; i++) {
      int curflag = shareflags[i];
      if (prevflag == curflag) {
        continue;
      } else if (prevflag == 0 && curflag == 1) {
        if (curStartTime < 0) {
          Log.error("invalid curStartTime at " + i);
        }
        double endTime = startDate.getMillis() + i * DUMMY_STREAM_INTERVAL * 1000;
        resultRanges.add(new Range(curStartTime, endTime));
        curStartTime = -1;
      } else if (prevflag == 1 && curflag == 0) {
        curStartTime = startDate.getMillis() + i * DUMMY_STREAM_INTERVAL * 1000;
      }

      prevflag = shareflags[i];
    }
    if (curStartTime > 0) {
      double endTime = startDate.getMillis() + shareflags.length * DUMMY_STREAM_INTERVAL * 1000;
      resultRanges.add(new Range(curStartTime, endTime));
    }

    return resultRanges;
  }
  private void sendEmailToOwner(String username, String reportIdxFileName) {
    String link = basePath + REPORTS_URL + "/" + reportIdxFileName;
    link = "<a href=\"" + link + "\">" + link + "</a>";

    String title =
        EMAIL_TITLE + " (" + titleFmt.print(startDate) + " ~ " + titleFmt.print(endDate) + ")";
    String body =
        "Your data reports are ready for your review at the following address:<br/><br/>" + link;
    body += "<br/><br/>Please access the above link and provide us your feedback!<br/><br/>";
    body += "Regards,<br/>SensorPrivacy Research Team";

    // Get email address
    UserDatabaseDriver db = null;
    String email = null;
    try {
      db = DatabaseConnector.getUserDatabase();
      email = db.getUserEmail(username);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (SQLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (NamingException e) {
      e.printStackTrace();
    } finally {
      if (db != null) {
        try {
          db.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }

    if (email == null) {
      Log.error("No email address for " + username);
      return;
    }

    try {
      MailSender.send(email, title, body);
    } catch (MessagingException e) {
      e.printStackTrace();
      Log.error("Error while sending email..");
      return;
    }

    Log.info("Done sending email!");
  }
  @Override
  public void run() {
    try {
      if (!checkLock()) {
        return;
      }

      Log.info("Report thread running..");

      // Fetch GPS data
      Log.info("Fetching GPS data..");

      boolean isGPS = generateGPSInitScript();
      if (!isGPS) {
        Log.error("Generating GPS samples failed..");
      } else {
        Log.info("Fetching GPS data done");
      }

      // Read report template
      String reportTemplate = FileUtils.readFileToString(new File(REPORT_TEMPLATE_PATH));

      if (reportTemplate == null) {
        Log.error("Error reading report template.");
        return;
      }

      for (String curSensor : REPORT_SENSORS) {
        generateReportForSensor(curSensor, reportTemplate);
      }

      String reportIdxFileName = generateReportIndexPage();

      if (reportIdxFileName == null) {
        Log.error("Error generating report index.");
        return;
      }

      sendEmailToOwner(owner, reportIdxFileName);

      Log.info("Report thread done!.");

    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      releaseLock();
    }
  }
  private void prepareDummyStream(String streamOwner, String streamName)
      throws ClassNotFoundException, SQLException, IOException, NamingException,
          NoSuchAlgorithmException {

    // Check if dummy stream exists
    List<Stream> streams = null;
    StreamDatabaseDriver db = null;
    try {
      db = DatabaseConnector.getStreamDatabase();
      streams = db.getStreamList(streamOwner);
    } finally {
      if (db != null) {
        try {
          db.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }

    boolean isFound = false;
    for (Stream stream : streams) {
      if (stream.name.equals(DUMMY_STREAM_NAME)) {
        isFound = true;
      }
    }

    if (!isFound) {
      createDummyStream(streamOwner);
    }

    // Check if dummy stream has desired amount of data on today.
    DateTime day = simpleFmt.parseDateTime(startDate.toString(simpleFmt));
    int targetNumSamples = MAX_SEC_A_DAY / DUMMY_STREAM_INTERVAL;

    while (day.isBefore(endDate)) {
      Log.info("current day: " + fmt.print(day));

      String startTime = fmt.print(day);
      String endTime = fmt.print(day.plusDays(1).minusMillis(1));
      try {
        db = DatabaseConnector.getStreamDatabase();

        boolean isData =
            db.prepareQuery(
                streamOwner,
                streamOwner,
                DUMMY_STREAM_NAME,
                startTime,
                endTime,
                null,
                null,
                0,
                0,
                0,
                true,
                null);
        Stream stream = db.getStoredStreamInfo();

        if (!isData) {
          Log.error("isData false");
          throw new UnsupportedOperationException("idData false.");
        }

        if (stream.num_samples != targetNumSamples) {
          Log.info("dummy stream is no good. Bulkloading dummy stream..");
          bulkloadDummyStreamData(streamOwner, day);
        } else {
          Log.info("dummy stream is good.");
        }
      } finally {
        if (db != null) {
          try {
            db.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
        }
      }

      day = day.plusDays(1);
    }
  }
 private int[] queryUsingDummyStreamAsOtherUser(String streamOwner, String streamName)
     throws ClassNotFoundException, SQLException, IOException, NamingException {
   StreamDatabaseDriver db = null;
   String startTime = fmt.print(startDate);
   String endTime = fmt.print(endDate.minusMillis(1));
   Log.info("startTime: " + startTime + ", endTime: " + endTime);
   long durationInSecs = (endDate.getMillis() / 1000) - (startDate.getMillis() / 1000);
   int size = (int) (durationInSecs / DUMMY_STREAM_INTERVAL);
   if (size <= 0) {
     size = 1;
   }
   int[] shareflags = new int[size];
   Log.info("size: " + (int) (durationInSecs / DUMMY_STREAM_INTERVAL));
   try {
     db = DatabaseConnector.getStreamDatabase();
     boolean isData =
         db.prepareQuery(
             OTHER_USER_NAME,
             streamOwner,
             DUMMY_STREAM_NAME,
             startTime,
             endTime,
             null,
             null,
             0,
             0,
             0,
             false,
             streamName);
     if (!isData) {
       Log.info("No data shared with " + OTHER_USER_NAME + " for " + streamName);
       return shareflags;
     }
     Object[] tuple = new Object[db.getStoredStreamInfo().channels.size() + 1];
     long todayMillis = startDate.getMillis();
     while (db.getNextTuple(tuple)) {
       long timestamp = (Long) tuple[0];
       // Log.info("timestamp: " + timestamp);
       int idx = (int) ((timestamp - todayMillis) / 1000 / DUMMY_STREAM_INTERVAL);
       if (idx >= size) {
         Log.error(
             "idx >= size, timestamp = "
                 + fmt.print(new DateTime(timestamp))
                 + ", idx = "
                 + idx
                 + ", size = "
                 + size);
       } else {
         shareflags[idx] = 1;
       }
     }
     return shareflags;
   } finally {
     if (db != null) {
       try {
         db.close();
       } catch (SQLException e) {
         e.printStackTrace();
       }
     }
   }
 }
  private File createPNG(
      String streamName,
      int width,
      int height,
      String outputPath,
      boolean isLine,
      boolean isShape) {

    StreamDatabaseDriver db = null;
    String requestingUser = owner;
    String streamOwner = owner;

    String startTime = fmt.print(startDate);
    String endTime = fmt.print(endDate);

    try {
      db = DatabaseConnector.getStreamDatabase();

      boolean isData =
          db.prepareQuery(
              requestingUser,
              streamOwner,
              streamName,
              startTime,
              endTime,
              null,
              null,
              0,
              0,
              0,
              true,
              null);
      Stream stream = db.getStoredStreamInfo();

      if (!isData) {
        Log.error("isData null");
        return null;
      }

      if (stream.num_samples > width) {
        db.close();
        db = DatabaseConnector.getStreamDatabase();
        int skipEveryNth = (int) (stream.num_samples / width);
        isData =
            db.prepareQuery(
                requestingUser,
                streamOwner,
                streamName,
                startTime,
                endTime,
                null,
                null,
                0,
                0,
                skipEveryNth,
                false,
                null);
        stream = db.getStoredStreamInfo();

        if (!isData) {
          Log.error("isData null");
          return null;
        }
      }

      // Prepare data
      XYSeries[] series = null;
      long minTsInterval = Long.MAX_VALUE; // to determine whether to use marker on the plot.
      long prevTimestamp = -1;
      Object[] tuple = new Object[db.getStoredStreamInfo().channels.size() + 1];
      while (db.getNextTuple(tuple)) {
        // Init XYSeries array
        if (series == null) {
          series = new XYSeries[tuple.length - 1];
          for (int i = 0; i < series.length; i++) {
            series[i] = new XYSeries(stream.channels.get(i).name);
          }
        }

        long timestamp = ((Long) tuple[0]).longValue();
        for (int i = 1; i < tuple.length; i++) {
          try {
            series[i - 1].add(timestamp, (Number) tuple[i]);
          } catch (ClassCastException e) {
            continue;
          }
        }

        long diff = timestamp - prevTimestamp;
        if (diff > 0 && diff < minTsInterval) {
          minTsInterval = diff;
        }

        prevTimestamp = timestamp;
      }

      db.close();
      db = null;

      if (series == null) {
        throw new UnsupportedOperationException("No data for " + streamName);
      }

      XYSeriesCollection xyDataset = new XYSeriesCollection();
      for (XYSeries s : series) {
        xyDataset.addSeries(s);
      }

      // Generate title string
      long start = (long) series[0].getMinX();
      long end = (long) series[0].getMaxX();
      Timestamp startTimestamp = new Timestamp(start);
      Timestamp endTimestamp = new Timestamp(end);
      String title =
          stream.owner
              + ": "
              + stream.name
              + "\n"
              + startTimestamp.toString()
              + " ~ "
              + endTimestamp.toString();

      //  Create the chart object
      DateAxis xAxis = new DateAxis("Time");
      xAxis.setDateFormatOverride(new SimpleDateFormat("hh:mm aa"));
      // NumberAxis xAxis = new NumberAxis("");
      long margin = (endDate.getMillis() - startDate.getMillis()) / 24;
      xAxis.setRange(
          new Date(startDate.getMillis() - margin), new Date(endDate.getMillis() + margin));

      NumberAxis yAxis = new NumberAxis("Value");
      yAxis.setAutoRangeIncludesZero(false); // override default

      if (streamName.equals(ACTIVITY_SENSOR)) {
        yAxis.setTickUnit(new NumberTickUnit(1.0));
        yAxis.setRange(0.0, 4.0);
      } else if (streamName.equals(STRESS_SENSOR)) {
        yAxis.setTickUnit(new NumberTickUnit(1.0));
        yAxis.setRange(0.0, 1.0);
      } else if (streamName.equals(CONVERSATION_SENSOR)) {
        yAxis.setTickUnit(new NumberTickUnit(1.0));
        yAxis.setRange(0.0, 2.0);
      }

      StandardXYItemRenderer renderer;
      // long dataCount = (end - start) / minTsInterval;
      // if (dataCount <= width) {
      //	renderer = new StandardXYItemRenderer(StandardXYItemRenderer.LINES +
      // StandardXYItemRenderer.SHAPES);
      // } else {
      //	renderer = new StandardXYItemRenderer(StandardXYItemRenderer.LINES);
      // }
      if (isLine && isShape) {
        renderer =
            new StandardXYItemRenderer(
                StandardXYItemRenderer.LINES + StandardXYItemRenderer.SHAPES);
      } else if (isLine && !isShape) {
        renderer = new StandardXYItemRenderer(StandardXYItemRenderer.LINES);
      } else if (!isLine && isShape) {
        renderer = new StandardXYItemRenderer(StandardXYItemRenderer.SHAPES);
      } else {
        renderer = new StandardXYItemRenderer(StandardXYItemRenderer.LINES);
      }
      // renderer.setShapesFilled(true);

      XYPlot plot = new XYPlot(xyDataset, xAxis, yAxis, renderer);
      JFreeChart chart =
          new JFreeChart(title, new Font(Font.SANS_SERIF, Font.BOLD, 12), plot, true);
      // JFreeChart chart = new JFreeChart(title, plot);
      chart.setBackgroundPaint(java.awt.Color.WHITE);
      chart.removeLegend();

      // Marker
      final Color c = new Color(255, 60, 24, 63);
      List<Range> markerRanges = getUnsharedRanges(streamOwner, streamName);

      for (Range range : markerRanges) {
        Marker marker =
            new IntervalMarker(
                range.startTimeInMillis,
                range.endTimeInMillis,
                c,
                new BasicStroke(2.0f),
                null,
                null,
                1.0f);
        plot.addDomainMarker(marker, Layer.BACKGROUND);
      }

      ChartRenderingInfo info = new ChartRenderingInfo(new StandardEntityCollection());
      String filename = ServletUtilities.saveChartAsPNG(chart, width, height, info, null);

      File imageFile = new File("/tmp/" + filename);
      File toFile =
          new File(outputPath + "/" + streamName + "_" + fileFmt.print(startDate) + ".png");
      imageFile.renameTo(toFile);

      return toFile;

    } catch (ClassNotFoundException
        | IOException
        | NamingException
        | SQLException
        | UnsupportedOperationException e) {
      e.printStackTrace();
    } catch (IllegalArgumentException e) {
      e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } finally {
      if (db != null) {
        try {
          db.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
    return null;
  }
  private boolean generateGPSInitScript() {

    StreamDatabaseDriver db = null;
    String requestingUser = owner;
    String streamOwner = owner;
    String startTime = fmt.print(startDate);
    String endTime = fmt.print(endDate);

    long startTimestamp = startDate.getMillis();

    try {
      db = DatabaseConnector.getStreamDatabase();

      boolean isData =
          db.prepareQuery(
              requestingUser,
              streamOwner,
              GPS_STREAM_NAME,
              startTime,
              endTime,
              null,
              null,
              0,
              0,
              0,
              true,
              null);

      if (!isData) {
        Log.error("No GPS data..");
        return false;
      }

      Stream stream = db.getStoredStreamInfo();
      long numSamples = stream.num_samples;

      if (numSamples <= 0) {
        Log.error("numSamples <= 0 on " + GPS_STREAM_NAME);
        return false;
      }

      Object[] tuple = new Object[db.getStoredStreamInfo().channels.size() + 1];
      int timestamp;
      Coord coords[] = new Coord[(int) numSamples];
      int idx = 0;
      while (db.getNextTuple(tuple)) {
        timestamp = (int) (((Long) tuple[0] - startTimestamp) / 1000);
        coords[idx] = new Coord(timestamp, (Double) tuple[1], (Double) tuple[2]);
        idx++;
      }

      List<Coord> clusteredCoords = gpsClustering(coords);

      // Generate all shared gps poitns.
      StringBuilder sb = new StringBuilder();
      for (Coord coord : clusteredCoords) {
        sb.append("gps[");
        sb.append(coord.timestampInSecs);
        sb.append("] = new google.maps.Marker({ position: new google.maps.LatLng(");
        sb.append(coord.lat);
        sb.append(", ");
        sb.append(coord.lon);
        sb.append("), icon: sharedCircle, draggable: false, map: null });\n");
      }

      mGPSSamplesAllShared = sb.toString();

      // Generate based on rule processing.
      List<Range> unsharedRanges = getUnsharedRanges(streamOwner, GPS_STREAM_NAME);
      sb = new StringBuilder();
      for (Coord coord : clusteredCoords) {
        sb.append("gps[");
        sb.append(coord.timestampInSecs);
        sb.append("] = new google.maps.Marker({ position: new google.maps.LatLng(");
        sb.append(coord.lat);
        sb.append(", ");
        sb.append(coord.lon);
        sb.append("), icon: ");
        if (isCoordUnshared(coord, unsharedRanges)) {
          sb.append("nonSharedCircle");
        } else {
          sb.append("sharedCircle");
        }
        sb.append(", draggable: false, map: null });\n");
      }

      mGPSSamplesSharingFlagged = sb.toString();

      return true;
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (SQLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (NamingException e) {
      e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (IllegalArgumentException e) {
      e.printStackTrace();
    } finally {
      if (db != null) {
        try {
          db.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
    return false;
  }