public enum SensorSampleType {

    /**
     * Raw sample from sensor input.
     */
    RAW,
    
    /**
     * Sample compacted from all samples from the same minute.
     */
    MINUTE,
    
    /**
     * Sample compacted from all samples from the same hour.
     */
    HOUR,
    
    /**
     * Sample compacted from all samples from the same day.
     */
    DAY
} public class SensorDao {
    /**
     * Creates a new sensor.
     * 
     * @param sensor Sensor to create
     * @return Sensor ID
     */
    public String create(Sensor sensor) {
        // Init sensor data
        sensor.setId(UUID.randomUUID().toString());
        sensor.setCreateDate(new Date());

        // Create sensor
        final Handle handle = ThreadLocalContext.get().getHandle();
        handle.createStatement("insert into " +
                " T_SENSOR(SEN_ID_C, SEN_NAME_C, SEN_TYPE_C, SEN_CREATEDATE_D)" +
                " values(:id, :name, :type, :createDate)")
                .bind("id", sensor.getId())
                .bind("name", sensor.getName())
                .bind("type", sensor.getType().name())
                .bind("createDate", sensor.getCreateDate())
                .execute();

        return sensor.getId();
    }
    
    /**
     * Updates a sensor.
     * 
     * @param sensor Sensor to update
     * @return Updated sensor
     */
    public Sensor update(Sensor sensor) {
        final Handle handle = ThreadLocalContext.get().getHandle();
        handle.createStatement("update T_SENSOR e set " +
                " e.SEN_NAME_C = :name, " +
                " e.SEN_TYPE_C = :type " +
                " where e.SEN_ID_C = :id and e.SEN_DELETEDATE_D is null")
                .bind("id", sensor.getId())
                .bind("name", sensor.getName())
                .bind("type", sensor.getType().name())
                .execute();

        return sensor;
    }
    
    /**
     * Gets a sensor by its ID.
     * 
     * @param id Sensor ID
     * @return Sensor
     */
    public Sensor getActiveById(String id) {
        final Handle handle = ThreadLocalContext.get().getHandle();
        Query<Sensor> q = handle.createQuery("select " + new SensorMapper().getJoinedColumns("e") +
                "  from T_SENSOR e" +
                "  where e.SEN_ID_C = :id and e.SEN_DELETEDATE_D is null")
                .bind("id", id)
                .mapTo(Sensor.class);
        return q.first();
    }
    
    /**
     * Deletes a sensor.
     * 
     * @param id Sensor's ID
     */
    public void delete(String id) {
        final Handle handle = ThreadLocalContext.get().getHandle();
        handle.createStatement("update T_SENSOR e" +
                "  set e.SEN_DELETEDATE_D = :deleteDate" +
                "  where e.SEN_ID_C = :id and e.SEN_DELETEDATE_D is null")
                .bind("id", id)
                .bind("deleteDate", new Date())
                .execute();
        
        // TODO Delete samples
    }

    /**
     * Returns the list of all sensors.
     *
     * @return List of sensors
     */
    public List<Sensor> findAll() {
        Handle handle = ThreadLocalContext.get().getHandle();
        Query<Sensor> q = handle.createQuery("select " + new SensorMapper().getJoinedColumns("e") +
                "  from T_SENSOR e " +
                "  where e.SEN_DELETEDATE_D is null" +
                "  order by e.SEN_NAME_C")
                .mapTo(Sensor.class);
        return q.list();
    }
} public class SensorSampleDao {
    /**
     * Creates a new sensor sample.
     * 
     * @param sample Sensor sample to create
     */
    public String create(SensorSample sample) {
        // Init sensor sample data
        sample.setId(UUID.randomUUID().toString());

        // Create sensor sample
        final Handle handle = ThreadLocalContext.get().getHandle();
        handle.createStatement("insert into " +
                " T_SENSOR_SAMPLE(SES_ID_C, SES_IDSEN_C, SES_VALUE_N, SES_CREATEDATE_D, SES_TYPE_C)" +
                " values(:id, :sensorId, :value, :createDate, :type)")
                .bind("id", sample.getId())
                .bind("sensorId", sample.getSensorId())
                .bind("value", sample.getValue())
                .bind("type", sample.getType().name())
                .bind("createDate", new Timestamp(sample.getCreateDate().getTime()))
                .execute();

        return sample.getId();
    }
    
    /**
     * Returns samples before a specified date.
     * 
     * @param id Sensor ID
     * @param date Date
     * @param type Sample type
     */
    public List<SensorSample> findAllBefore(String id, Date date, SensorSampleType type) {
        Handle handle = ThreadLocalContext.get().getHandle();
        Query<SensorSample> q = handle.createQuery("select " + new SensorSampleMapper().getJoinedColumns("e") +
                "  from T_SENSOR_SAMPLE e " +
                "  where e.SES_TYPE_C = :type " +
                " and e.SES_IDSEN_C = :id " +
                " and e.SES_CREATEDATE_D < :date " +
                "  order by e.SES_CREATEDATE_D")
                .bind("id", id)
                .bind("date", new Timestamp(date.getTime()))
                .bind("type", type.name())
                .mapTo(SensorSample.class);
        return q.list();
    }

    /**
     * Delete a list of samples.
     * 
     * @param idList List of samples IDs
     */
    public void deleteList(List<String> idList) {
        final Handle handle = ThreadLocalContext.get().getHandle();
        Map<String, Object> args = bind(idList);
        handle.createStatement("delete from T_SENSOR_SAMPLE e" +
                "  where e.SES_ID_C in (" + join(args.keySet()) + ")")
                .bindFromMap(args)
                .execute();
    }
    
    // TODO Refactor this :(
    private String join(Set<String> args) {
        StringBuilder sb = new StringBuilder();
        for (Iterator<String> it = args.iterator(); it.hasNext();) {
            sb.append(":").append(it.next());
            if (it.hasNext()) {
                sb.append(",");
            }
        }
        return sb.toString();
    }
    
    // TODO And this!
    private Map<String, Object> bind(List<? extends Object> list) {
        Map<String, Object> params = Maps.newHashMap();
        int i = 0;
        for (Object obj : list) {
            params.put("bind_" + i++, obj);
        }
        return params;
    }

    /**
     * Find samples by criteria.
     * 
     * @param criteria Criteria
     * @return Sample list
     */
    public List<SensorSample> findByCriteria(SensorSampleCriteria criteria) {
        Handle handle = ThreadLocalContext.get().getHandle();
        Query<SensorSample> q = handle.createQuery("select " + new SensorSampleMapper().getJoinedColumns("e") +
                " from T_SENSOR_SAMPLE e " +
                " where e.SES_IDSEN_C = :id" +
                " and e.SES_TYPE_C = :type " +
                " order by e.SES_CREATEDATE_D")
                .bind("id", criteria.getSensorId())
                .bind("type", criteria.getType().name())
                .mapTo(SensorSample.class);
        return q.list();
    }
} public class SensorSampleCriteria {
    /**
     * Sensor ID.
     */
    private String sensorId;
    
    /**
     * Sensor sample type.
     */
    private SensorSampleType type;

    /**
     * Getter of sensorId.
     *
     * @return the sensorId
     */
    public String getSensorId() {
        return sensorId;
    }

    /**
     * Setter of sensorId.
     *
     * @param sensorId sensorId
     */
    public SensorSampleCriteria setSensorId(String sensorId) {
        this.sensorId = sensorId;
        return this;
    }

    /**
     * Getter of type.
     *
     * @return the type
     */
    public SensorSampleType getType() {
        return type;
    }

    /**
     * Setter of type.
     *
     * @param type type
     */
    public SensorSampleCriteria setType(SensorSampleType type) {
        this.type = type;
        return this;
    }
} public class Sensor {
    /**
     * Sensor type.
     * 
     * @author bgamard
     */
    public enum Type {
        ELECTRICITY,
        
        TEMPERATURE,
        
        HUMIDITY,
        
        SOUND,
        
        LIGHT,
        
        MOVEMENT
    }
    
    /**
     * ID.
     */
    private String id;
    
    /**
     * Name.
     */
    private String name;
    
    /**
     * Type.
     */
    private Type type;
    
    /**
     * Creation date.
     */
    private Date createDate;
    
    /**
     * Deletion date.
     */
    private Date deleteDate;

    public Sensor() {
    }

    public Sensor(String id, String name, Date createDate, Date deleteDate, Type type) {
        this.id = id;
        this.name = name;
        this.createDate = createDate;
        this.deleteDate = deleteDate;
        this.type = type;
    }

    /**
     * Getter of id.
     *
     * @return id
     */
    public String getId() {
        return id;
    }

    /**
     * Setter of id.
     *
     * @param id id
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * Getter of name.
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * Setter of name.
     *
     * @param name name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Getter of createDate.
     *
     * @return createDate
     */
    public Date getCreateDate() {
        return createDate;
    }

    /**
     * Setter of createDate.
     *
     * @param createDate createDate
     */
    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    /**
     * Getter of deleteDate.
     *
     * @return deleteDate
     */
    public Date getDeleteDate() {
        return deleteDate;
    }

    /**
     * Setter of deleteDate.
     *
     * @param deleteDate deleteDate
     */
    public void setDeleteDate(Date deleteDate) {
        this.deleteDate = deleteDate;
    }
    
    /**
     * Getter of type.
     * @return type
     */
    public Type getType() {
        return type;
    }

    /**
     * Setter of type.
     * @param type type
     */
    public void setType(Type type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add("id", id)
                .add("name", name)
                .add("type", type)
                .toString();
    }
} public enum Type {
    ELECTRICITY,
        
    TEMPERATURE,
        
    HUMIDITY,
        
    SOUND,
        
    LIGHT,
        
    MOVEMENT
} public class SensorSample {
    /**
     * ID.
     */
    private String id;
    
    /**
     * Sensor ID.
     */
    private String sensorId;
    
    /**
     * Value.
     */
    private float value;
    
    /**
     * Creation date.
     */
    private Date createDate;
    
    /**
     * Type.
     */
    private SensorSampleType type;
    
    public SensorSample() {
    }

    public SensorSample(String id, String sensorId, Date createDate, float value, SensorSampleType type) {
        this.id = id;
        this.sensorId = sensorId;
        this.createDate = createDate;
        this.value = value;
        this.type = type;
    }

    /**
     * Getter of id.
     *
     * @return id
     */
    public String getId() {
        return id;
    }

    /**
     * Setter of id.
     *
     * @param id id
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * Getter of createDate.
     *
     * @return createDate
     */
    public Date getCreateDate() {
        return createDate;
    }

    /**
     * Setter of createDate.
     *
     * @param createDate createDate
     */
    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
    
    /**
     * Getter of sensorId.
     *
     * @return the sensorId
     */
    public String getSensorId() {
        return sensorId;
    }

    /**
     * Setter of sensorId.
     *
     * @param sensorId sensorId
     */
    public void setSensorId(String sensorId) {
        this.sensorId = sensorId;
    }

    /**
     * Getter of value.
     *
     * @return the value
     */
    public float getValue() {
        return value;
    }

    /**
     * Setter of value.
     *
     * @param value value
     */
    public void setValue(float value) {
        this.value = value;
    }

    /**
     * Getter of type.
     *
     * @return the type
     */
    public SensorSampleType getType() {
        return type;
    }

    /**
     * Setter of type.
     *
     * @param type type
     */
    public void setType(SensorSampleType type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add("id", id)
                .add("value", value)
                .add("createDate", createDate)
                .add("type", type)
                .toString();
    }
} public enum BaseFunction {

    /**
     * Allows the user to use the admin fonctions.
     */
    ADMIN,

    /**
     * Allows the user to change his password. 
     */
    PASSWORD,
} public class ClientException extends WebApplicationException {
    /**
     * Serial UID.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Logger.
     */
    private static final Logger log = LoggerFactory.getLogger(ClientException.class);

    /**
     * Constructor of ClientException.
     * 
     * @param type Error type (e.g. AlreadyExistingEmail, ValidationError)
     * @param message Human readable error message
     * @param e Readable error message
     */
    public ClientException(String type, String message, Exception e) {
        this(type, message);
        log.error(type + ": " + message, e);
    }

    /**
     * Constructor of ClientException.
     * 
     * @param type Error type (e.g. AlreadyExistingEmail, ValidationError)
     * @param message Human readable error message
     */
    public ClientException(String type, String message) {
        super(Response.status(Status.BAD_REQUEST).entity(Json.createObjectBuilder()
            .add("type", type)
            .add("message", message).build()).build());
    }
} public class ValidationUtil {
    private static Pattern EMAIL_PATTERN = Pattern.compile(".+@.+\\..+");
    
    private static Pattern HTTP_URL_PATTERN = Pattern.compile("https?://.+");
    
    private static Pattern ALPHANUMERIC_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
    
    /**
     * Checks that the argument is not null.
     * 
     * @param s Object tu validate
     * @param name Name of the parameter
     * @throws ClientException
     */
    public static void validateRequired(Object s, String name) throws ClientException {
        if (s == null) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
        }
    }
    
    /**
     * Validate a string length.
     * 
     * @param s String to validate
     * @param name Name of the parameter
     * @param lengthMin Minimum length (or null)
     * @param lengthMax Maximum length (or null)
     * @param nullable True if the string can be empty or null
     * @return String without white spaces
     * @throws ClientException
     */
    public static String validateLength(String s, String name, Integer lengthMin, Integer lengthMax, boolean nullable) throws ClientException {
        s = StringUtils.strip(s);
        if (nullable && StringUtils.isEmpty(s)) {
            return s;
        }
        if (s == null) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
        }
        if (lengthMin != null && s.length() < lengthMin) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must be more than {1} characters", name, lengthMin));
        }
        if (lengthMax != null && s.length() > lengthMax) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must be more than {1} characters", name, lengthMax));
        }
        return s;
    }
    
    /**
     * Validate a string length. The string mustn't be empty.
     * 
     * @param s String to validate
     * @param name Name of the parameter
     * @param lengthMin Minimum length (or null)
     * @param lengthMax Maximum length (or null)
     * @return String without white spaces
     * @throws ClientException
     */
    public static String validateLength(String s, String name, Integer lengthMin, Integer lengthMax) throws ClientException {
        return validateLength(s, name, lengthMin, lengthMax, false);
    }
    
    /**
     * Checks if the string is not null and is not only whitespaces.
     * 
     * @param s String to validate
     * @param name Name of the parameter
     * @return String without white spaces
     * @throws ClientException
     */
    public static String validateStringNotBlank(String s, String name) throws ClientException {
        return validateLength(s, name, 1, null, false);
    }
    
    /**
     * Checks if the string is an email.
     * 
     * @param s String to validate
     * @param name Name of the parameter
     * @throws ClientException
     */
    public static void validateEmail(String s, String name) throws ClientException {
        if (!EMAIL_PATTERN.matcher(s).matches()) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must be an email", name));
        }
    }
    
    /**
     * Validates that the provided string matches an URL with HTTP or HTTPS scheme.
     * 
     * @param s String to validate
     * @param name Name of the parameter
     * @return Stripped URL
     * @throws ClientException
     */
    public static String validateHttpUrl(String s, String name) throws ClientException {
        s = StringUtils.strip(s);
        if (!HTTP_URL_PATTERN.matcher(s).matches()) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must be an HTTP(s) URL", name));
        }
        return s;
    }
    
    /**
     * Checks if the string uses only alphanumerical or underscore characters.
     * 
     * @param s String to validate
     * @param name Name of the parameter
     * @throws ClientException
     */
    public static void validateAlphanumeric(String s, String name) throws ClientException {
        if (!ALPHANUMERIC_PATTERN.matcher(s).matches()) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must have only alphanumeric or underscore characters", name));
        }
    }
    
    /**
     * Checks if the string is a number.
     * 
     * @param s String to validate
     * @param name Name of the parameter
     * @return Parsed number
     * @throws ClientException
     */
    public static Integer validateInteger(String s, String name) throws ClientException {
        try {
            return Integer.valueOf(s);
        } catch (NumberFormatException e) {
            throw new ClientException("Validation Error", MessageFormat.format("{0} is not a number", name));
        }
    }
    
    /**
     * Validates and parses a date.
     * 
     * @param s String to validate
     * @param name Name of the parameter
     * @param nullable True if the string can be empty or null
     * @return Parsed date
     * @throws ClientException
     */
    public static Date validateDate(String s, String name, boolean nullable) throws ClientException {
        if (Strings.isNullOrEmpty(s)) {
            if (!nullable) {
                throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
            } else {
                return null;
            }
        }
        try {
            return new DateTime(Long.parseLong(s)).toDate();
        } catch (NumberFormatException e) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must be a date", name));
        }
    }
    
    /**
     * Validates a directory.
     * 
     * @param s Path to the directory
     * @param name Name of the parameter
     * @param nullable True if the path can be empty or null
     * @return Directory
     * @throws ClientException
     */
    public static File validateDirectory(String s, String name, boolean nullable) throws ClientException {
        if (Strings.isNullOrEmpty(s)) {
            if (!nullable) {
                throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
            } else {
                return null;
            }
        }
        
        File directory = new File(s);
        if (!directory.exists() || directory.isFile()) {
            throw new ClientException("ValidationError", MessageFormat.format("{0} must be a valid directory", name));
        }
        
        return directory;
    }
}
import java.util.Date;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.sismics.home.core.constant.SensorSampleType;
import com.sismics.home.core.dao.dbi.SensorDao;
import com.sismics.home.core.dao.dbi.SensorSampleDao;
import com.sismics.home.core.dao.dbi.criteria.SensorSampleCriteria;
import com.sismics.home.core.model.dbi.Sensor;
import com.sismics.home.core.model.dbi.Sensor.Type;
import com.sismics.home.core.model.dbi.SensorSample;
import com.sismics.home.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.ValidationUtil;
package com.sismics.home.rest.resource;




/**
 * Sensor REST resources.
 * 
 * @author bgamard
 */
@Path("/sensor")
public class SensorResource extends BaseResource {
    /**
     * Creates a new sensor.
     *
     * @param name Name
     * @return Response
     */
    @PUT
    @Produces(MediaType.APPLICATION_JSON)
    public Response create(
            @FormParam("name") String name,
            @FormParam("type") String typeStr) {

        if (!authenticate()) {
            throw new ForbiddenClientException();
        }
        checkBaseFunction(BaseFunction.ADMIN);

        // Validate the input data
        name = ValidationUtil.validateLength(name, "name", 1, 100);
        ValidationUtil.validateRequired(typeStr, "type");
        Type type = Type.valueOf(typeStr);

        // Create the sensor
        Sensor sensor = new Sensor();
        sensor.setName(name);
        sensor.setType(type);

        SensorDao sensorDao = new SensorDao();
        String sensorId = sensorDao.create(sensor);

        // Always return OK
        return Response.ok()
                .entity(Json.createObjectBuilder()
                        .add("status", "ok")
                        .add("id", sensorId)
                        .build())
                .build();
    }

    /**
     * Creates a new sensor sample.
     *
     * @param id Sensor ID
     * @param date Sample date
     * @param value Sample value
     * @return Response
     */
    @PUT
    @Path("sample")
    @Produces(MediaType.APPLICATION_JSON)
    public Response createSample(
            @FormParam("id") List<String> idList,
            @FormParam("date") List<String> dateStrList,
            @FormParam("value") List<Float> valueList) {
        // Pre-check data
        if (idList.size() != dateStrList.size() || dateStrList.size() != valueList.size()) {
            throw new ClientException("DataError", "Data length mismatch");
        }
        
        int index = 0;
        for (String id : idList) {
            Float value = valueList.get(index);
            String dateStr = dateStrList.get(index);
            
            // Check if the sensor exists
            SensorDao sensorDao = new SensorDao();
            SensorSampleDao sensorSampleDao = new SensorSampleDao();