Author: echatellier Date: 2013-05-20 15:06:14 +0200 (Mon, 20 May 2013) New Revision: 32 Url: http://chorem.org/projects/incubator/repository/revisions/32 Log: Add real database service Added: jtimerhtml5/src/main/java/org/chorem/jtimer/entities/ jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerAlert.java jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerProject.java jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerTask.java jtimerhtml5/src/main/java/org/chorem/jtimer/storage/ jtimerhtml5/src/main/java/org/chorem/jtimer/storage/Storage.java jtimerhtml5/src/main/java/org/chorem/jtimer/storage/StorageException.java jtimerhtml5/src/main/java/org/chorem/jtimer/utils/ jtimerhtml5/src/main/java/org/chorem/jtimer/utils/DailySortedMap.java Modified: jtimerhtml5/pom.xml Modified: jtimerhtml5/pom.xml =================================================================== --- jtimerhtml5/pom.xml 2013-05-20 12:43:52 UTC (rev 31) +++ jtimerhtml5/pom.xml 2013-05-20 13:06:14 UTC (rev 32) @@ -116,7 +116,7 @@ <artifactId>nuiton-i18n</artifactId> <version>2.4.1</version> </dependency> - + <dependency> <groupId>com.oracle</groupId> <artifactId>javafx</artifactId> @@ -124,5 +124,12 @@ <scope>system</scope> <systemPath>${project.basedir}/lib/jfxrt.jar</systemPath> </dependency> + + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <version>1.3.170</version> + <scope>runtime</scope> + </dependency> </dependencies> </project> Added: jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerAlert.java =================================================================== --- jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerAlert.java (rev 0) +++ jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerAlert.java 2013-05-20 13:06:14 UTC (rev 32) @@ -0,0 +1,158 @@ +/* + * #%L + * jTimer + * + * $Id: TimerAlert.java 2848 2012-03-28 16:37:31Z echatellier $ + * $HeadURL$ + * %% + * Copyright (C) 2009 - 2012 CodeLutin, Chatellier Eric + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +package org.chorem.jtimer.entities; + +import java.io.Serializable; + +/** + * Alert. + * + * @author chatellier + * @version $Revision: 2848 $ + * + * Last update : $Date: 2012-03-28 18:37:31 +0200 (mer., 28 mars 2012) $ + * By : $Author: echatellier $ + */ +public class TimerAlert implements Serializable, Cloneable { + + /** serialVersionUID. */ + private static final long serialVersionUID = 584921087501157113L; + + /** + * Alert type. + */ + public enum Type { + REACH_DAILY_TIME, + REACH_TOTAL_TIME + } + + /** Alert type. */ + protected Type type; + + /** Alert duration. (in ms) */ + protected long duration; + + /** + * Constructor. + */ + public TimerAlert() { + + } + + /** + * Constructor. + * + * @param type type + * @param duration duration (ms) + */ + public TimerAlert(Type type, long duration) { + this(); + this.type = type; + this.duration = duration; + } + + /** + * Get alert type. + * + * @return the type + */ + public Type getType() { + return type; + } + + /** + * Set alert type. + * + * @param type the type to set + */ + public void setType(Type type) { + this.type = type; + } + + /** + * Get duration. + * + * @return the duration in seconds + */ + public long getDuration() { + return duration; + } + + /** + * Set duration. + * + * @param duration the duration to set (in seconds) + */ + public void setDuration(long duration) { + this.duration = duration; + } + + /* + * @see java.lang.Object#clone() + */ + @Override + public TimerAlert clone() { + TimerAlert clone = null; + try { + clone = (TimerAlert)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Can't clone", e); + } + return clone; + } + + /* + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int result = 1; + result = 31 * result + (int) (duration ^ (duration >>> 32)); + result = 31 * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + /* + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + boolean result = false; + + if (obj instanceof TimerAlert) { + TimerAlert other = (TimerAlert) obj; + + result = duration == other.duration; + if (type != null) { + result &= type.equals(other.type); + } + else { + result &= other.type == null; + } + } + return result; + } +} Added: jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerProject.java =================================================================== --- jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerProject.java (rev 0) +++ jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerProject.java 2013-05-20 13:06:14 UTC (rev 32) @@ -0,0 +1,90 @@ +/* + * #%L + * jTimer + * + * $Id: TimerProject.java 2778 2011-09-05 08:12:54Z echatellier $ + * $HeadURL$ + * %% + * Copyright (C) 2007 - 2011 CodeLutin, Chatellier Eric + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +package org.chorem.jtimer.entities; + +/** + * Represent a project. + * + * @author chatellier + * @version $Revision: 2778 $ + * + * Last update : $Date: 2011-09-05 10:12:54 +0200 (lun. 05 sept. 2011) $ + * By : $Author: echatellier $ + */ +public class TimerProject extends TimerTask { + + /** serialVersionUID. */ + private static final long serialVersionUID = -8953488997256237790L; + + /** Synchronized project name prefix. */ + public static final String SYNCHRONIZED_PROJECT_NAME_PREFIX = "#"; + + /** + * Constructor. + */ + public TimerProject() { + } + + /** + * Constructor with name. + * + * @param name project name + */ + public TimerProject(String name) { + super(name); + } + + /** + * Is synchronized. + * + * @return the synchronize + */ + public boolean isSynchronized() { + + boolean sync = false; + + if (name != null && name.startsWith(SYNCHRONIZED_PROJECT_NAME_PREFIX)) { + sync = true; + } + + return sync; + } + + /** + * Clone project. + * + * @return project copy + */ + @Override + public TimerProject clone() { + + // Can't use clone() from super class + // Result object have to be + TimerProject newProject = (TimerProject) super.clone(); + + return newProject; + } +} Added: jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerTask.java =================================================================== --- jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerTask.java (rev 0) +++ jtimerhtml5/src/main/java/org/chorem/jtimer/entities/TimerTask.java 2013-05-20 13:06:14 UTC (rev 32) @@ -0,0 +1,395 @@ +/* + * #%L + * jTimer + * + * $Id: TimerTask.java 2885 2012-08-22 09:46:46Z echatellier $ + * $HeadURL$ + * %% + * Copyright (C) 2007 - 2012 CodeLutin, Chatellier Eric + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +package org.chorem.jtimer.entities; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.chorem.jtimer.utils.DailySortedMap; + +/** + * Represents a task. + * + * @author chatellier + * @version $Revision: 2885 $ + * + * Last update : $Date: 2012-08-22 11:46:46 +0200 (mer., 22 août 2012) $ + * By : $Author: echatellier $ + */ +public class TimerTask implements Cloneable, + Comparable<TimerTask>, Serializable { + + /** serialVersionUID */ + private static final long serialVersionUID = -7590755569706702695L; + + /** Task number. */ + protected int number; + + /** Task name. */ + protected String name; + + /** Creation date. */ + protected Date creationDate; + + /** Closed task. */ + protected boolean closed; + + /** + * Parent Task. + * More convenient + */ + protected TimerTask parent; + + /** + * Map calendar of day -> time (ms). (ordered on keys) + */ + protected SortedMap<Date, Long> allDaysTimes; + + /** + * Map date -> annotation text. + */ + protected SortedMap<Date, String> allDaysAnnotations; + + /** + * Sub tasks. + */ + protected List<TimerTask> subTasks; + + /** + * Task alerts. + */ + protected List<TimerAlert> alerts; + + /** + * Constructor. + */ + public TimerTask() { + allDaysTimes = new DailySortedMap<Long>(); + // les annoation sont à la seconde pres + allDaysAnnotations = new TreeMap<Date, String>(); + subTasks = new ArrayList<TimerTask>(); + alerts = new ArrayList<TimerAlert>(); + + // wrong value to detect bug + number = -1; + } + + /** + * Constructor with name. + * + * @param name task name + */ + public TimerTask(String name) { + this(); + this.name = name; + } + + /** + * Get task number. + * + * @return the number + */ + public int getNumber() { + return number; + } + + /** + * Set task number. + * + * @param number the number to set + */ + public void setNumber(int number) { + this.number = number; + } + + /** + * Get task name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Set task name. + * + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * Get task creation date. + * + * @return task creation date + */ + public Date getCreationDate() { + return creationDate; + } + + /** + * Set task creation date. + * + * @param creationDate creation date + */ + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + /** + * Get closed task state. + * + * @return <tt>true</tt> if task is closed + */ + public boolean isClosed() { + return closed; + } + + /** + * Set closed. + * + * @param closed closed + */ + public void setClosed(boolean closed) { + this.closed = closed; + } + + /** + * Get parent. + * + * Can be null if there is no parent. + * + * @return the parent + */ + public TimerTask getParent() { + return parent; + } + + /** + * Set parent. + * + * @param parent the parent to set + */ + protected void setParent(TimerTask parent) { + // will cause an infinite loop + if (parent == this) { + throw new IllegalArgumentException("Parent can't be current task"); + } + this.parent = parent; + + } + + /** + * Get task's subtasks. + * + * @return the subTasks + */ + public List<TimerTask> getSubTasks() { + return subTasks; + } + + /** + * Add task's subtask. + * + * Also add parent reference. + * + * @param t the task to add + * @return success flag + */ + public boolean addTask(TimerTask t) { + + // set parent + t.setParent(this); + boolean result = subTasks.add(t); + return result; + } + + /** + * Add time. + * + * @param date date + * @param time time in ms + */ + public void setTime(Date date, Long time) { + allDaysTimes.put(date, time); + } + + /** + * Get time at date. + * + * @param date date + * @return time at specified date in ms + */ + public long getTime(Date date) { + long result = 0; + + Long t = allDaysTimes.get(date); + if (t != null) { + result = t.longValue(); + } + + return result; + } + + /** + * Return all data. Sorted on date. + * + * @return total duration of all projects + */ + public SortedMap<Date, Long> getAllDaysAndTimes() { + return allDaysTimes; + } + + /** + * Add annotation. + * + * @param date date + * @param note note text + */ + public void addAnnotation(Date date, String note) { + allDaysAnnotations.put(date, note); + } + + /** + * Return all annotation, sorted on date. + * + * @return annotations + */ + public SortedMap<Date, String> getAllDaysAnnotations() { + return allDaysAnnotations; + } + + /** + * Add alert. + * + * @param alert + */ + public void addAlert(TimerAlert alert) { + alerts.add(alert); + } + + /** + * Get alert list. + * + * @return alerts + */ + public List<TimerAlert> getAlerts() { + return alerts; + } + + /** + * Set alert. + * + * @param alerts new alerts list + */ + public void setAlert(List<TimerAlert> alerts) { + this.alerts = alerts; + } + + /* + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return name + subTasks.toString(); + } + + /* + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int result = number % 31; + return result; + } + + /* + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + + if (!(o instanceof TimerTask)) { + return false; + } + + return number == ((TimerTask)o).getNumber(); + } + + /** + * Clone task. + * + * @return task copy + */ + @Override + public TimerTask clone() { + + TimerTask task; + + try { + task = (TimerTask) super.clone(); + + // copy date + task.creationDate = creationDate == null ? null + : (Date) creationDate.clone(); + + // make new list instance + task.allDaysTimes = new DailySortedMap<Long>(allDaysTimes); + task.allDaysAnnotations = new TreeMap<Date, String>(allDaysAnnotations); + task.subTasks = new ArrayList<TimerTask>(subTasks); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Can't clone", e); + } + + return task; + } + + /* + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(TimerTask o) { + + int result; + + if (getName() == null) { + result = -1; + } else if (o.getName() == null) { + result = 1; + } else { + // sort on name + result = getName().compareTo(o.getName()); + } + + return result; + } +} Added: jtimerhtml5/src/main/java/org/chorem/jtimer/storage/Storage.java =================================================================== --- jtimerhtml5/src/main/java/org/chorem/jtimer/storage/Storage.java (rev 0) +++ jtimerhtml5/src/main/java/org/chorem/jtimer/storage/Storage.java 2013-05-20 13:06:14 UTC (rev 32) @@ -0,0 +1,498 @@ +/* + * #%L + * jTimer + * + * $Id: DailySortedMap.java 2848 2012-03-28 16:37:31Z echatellier $ + * $HeadURL$ + * %% + * Copyright (C) 2013 CodeLutin, Chatellier Eric + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.storage; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.chorem.jtimer.entities.TimerProject; +import org.chorem.jtimer.entities.TimerTask; + +/** + * Implementation du stockage des taches en base de données basée sur h2. + * + * Le schema est composé des tables: + * <ul> + * <li>Task + * <li>TaskTime + * <li>Version (contenant la version du schema en cas de migration)</li> + * </ul> + * + * @author echatellier + */ +public class Storage { + + private static final Log log = LogFactory.getLog(Storage.class); + + protected static final String TABLE_TASK = "task"; + protected static final String TABLE_TIME = "tasktime"; + protected static final String TABLE_VERSION = "version"; + + protected Connection connection; + + public Storage() { + try { + if (log.isDebugEnabled()) { + log.debug("Registering jdbc driver"); + } + Class.forName("org.h2.Driver"); + } catch (ClassNotFoundException e) { + if (log.isErrorEnabled()) { + log.fatal("Can't find h2 driver"); + } + } + + try { + connection = getConnection(); + } catch (SQLException ex) { + throw new StorageException("Can't open database", ex); + } + + } + + @Override + protected void finalize() throws Throwable { + connection.close(); + } + + protected Connection getConnection() throws SQLException { + + String url = "/tmp/jtimer"; + if (log.isInfoEnabled()) { + log.info("Opening connection to database : " + url); + } + Connection conn = DriverManager.getConnection("jdbc:h2:" + url, "sa", + ""); + conn.setAutoCommit(true); + + // test to create schema if it not already exists + boolean schemaExists = schemaExists(conn); + if (!schemaExists) { + if (log.isInfoEnabled()) { + log.info("Creating new database schema"); + } + createSchema(conn); + + // set new version in schema + Statement statement = conn.createStatement(); + statement.executeUpdate("INSERT INTO VERSION VALUES('2.0')"); + } + + return conn; + } + + protected void closeConnection(Connection conn) throws SQLException { + if (conn != null) { + conn.close(); + } + } + + protected void closeStatement(Statement stmt) { + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException ex) { + throw new StorageException("Can't close statement", ex); + } + } + } + + /** + * Test if table "version" exists in database list. + * + * @param conn sql connection + * @return schema exists + */ + protected boolean schemaExists(Connection conn) { + boolean found = false; + Statement statement = null; + try { + statement = conn.createStatement(); + ResultSet rs = statement.executeQuery("SELECT table_name FROM information_schema.tables;"); + while (rs.next()) { + String name = rs.getString(1); + if (TABLE_VERSION.equalsIgnoreCase(name)) { + found = true; + } + } + } catch (SQLException ex) { + throw new StorageException("Can't test schema", ex); + } finally { + closeStatement(statement); + } + + return found; + } + + protected void createSchema(Connection conn) { + Statement statement = null; + try { + statement = conn.createStatement(); + statement.executeUpdate("CREATE TABLE " + TABLE_VERSION + + "(version VARCHAR(10))"); + statement.executeUpdate("CREATE TABLE " + TABLE_TASK + + "(id LONG NOT NULL AUTO_INCREMENT PRIMARY KEY," + + " name VARCHAR(255) NOT NULL," + + " parent LONG DEFAULT 0," + + " hidden BOOLEAN," + + " note TEXT)"); + statement.executeUpdate("CREATE TABLE " + TABLE_TIME + + "(taskid LONG NOT NULL REFERENCES " + TABLE_TASK + "(id)," + + " date DATE," + + " duration LONG," + + " PRIMARY KEY (taskid, date))"); + } catch (SQLException ex) { + throw new StorageException("Can't create schema", ex); + } finally { + closeStatement(statement); + } + + } + + /** + * Find all project. (task with no parent, parent = 0). + * + * @return all projects + */ + public int getProjectsCount() { + int result = 0; + Statement statement = null; + try { + statement = connection.createStatement(); + ResultSet rs = statement.executeQuery("SELECT count(*) FROM " + TABLE_TASK + + " WHERE parent = 0"); + if (rs.next()) { + result = rs.getInt(1); + } + } catch (SQLException ex) { + throw new StorageException("Can't get project count", ex); + } finally { + closeStatement(statement); + } + return result; + } + + /** + * Find all project. (task with no parent, parent = 0). + * + * @return all projects + */ + public List<TimerProject> getProjects() { + List<TimerProject> projects = new ArrayList<TimerProject>(); + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("SELECT * FROM " + TABLE_TASK + + " WHERE parent = 0 ORDER BY id"); + ResultSet rs = statement.executeQuery(); + while (rs.next()) { + TimerProject project = new TimerProject(); + project.setNumber(rs.getInt("id")); + project.setName(rs.getString("name")); + projects.add(project); + } + } catch (SQLException ex) { + throw new StorageException("Can't get project", ex); + } finally { + closeStatement(statement); + } + return projects; + } + + /** + * Find all project. (task with no parent, parent = 0). + * + * @return all projects + */ + public TimerProject getProject(int offset) { + TimerProject project = null; + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("SELECT * FROM " + TABLE_TASK + + " WHERE parent = 0 ORDER BY id LIMIT 1 OFFSET ?"); + statement.setInt(1, offset); + ResultSet rs = statement.executeQuery(); + if (rs.next()) { + project = new TimerProject(); + project.setNumber(rs.getInt("id")); + project.setName(rs.getString("name")); + } + } catch (SQLException ex) { + throw new StorageException("Can't get project", ex); + } finally { + closeStatement(statement); + } + return project; + } + + /** + * Find all project. (task with no parent, parent = 0). + * + * @return all projects + */ + public int getTasksCount(TimerTask parent) { + int result = 0; + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("SELECT count(*) FROM " + TABLE_TASK + + " WHERE parent = ?"); + statement.setInt(1, parent.getNumber()); + ResultSet rs = statement.executeQuery(); + if (rs.next()) { + result = rs.getInt(1); + } + } catch (SQLException ex) { + throw new StorageException("Can't get task count", ex); + } finally { + closeStatement(statement); + } + return result; + } + + public TimerTask getTasks(TimerTask parent, int offset) { + TimerTask task = null; + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("SELECT * FROM " + TABLE_TASK + + " WHERE parent = ? ORDER BY id LIMIT 1 OFFSET ?"); + statement.setLong(1, parent.getNumber()); + statement.setLong(2, offset); + ResultSet rs = statement.executeQuery(); + while (rs.next()) { + task = new TimerTask(); + task.setNumber(rs.getInt("id")); + task.setName(rs.getString("name")); + } + } catch (SQLException ex) { + throw new StorageException("Can't get task", ex); + } finally { + closeStatement(statement); + } + return task; + } + + /** + * Find sub tasks.. + * + * @param parent parent task + * @return all sub tasks + */ + public List<TimerTask> getTasks(TimerTask parent) { + List<TimerTask> tasks = new ArrayList<TimerTask>(); + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("SELECT * FROM " + TABLE_TASK + + " WHERE parent = ? ORDER BY id"); + statement.setLong(1, parent.getNumber()); + ResultSet rs = statement.executeQuery(); + while (rs.next()) { + TimerTask task = new TimerTask(); + task.setNumber(rs.getInt("id")); + task.setName(rs.getString("name")); + tasks.add(task); + } + } catch (SQLException ex) { + throw new StorageException("Can't get task", ex); + } finally { + closeStatement(statement); + } + return tasks; + } + + public void addProject(TimerProject project) { + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("INSERT INTO " + + TABLE_TASK + "(name, parent, hidden, note)" + + " VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + statement.setString(1, project.getName()); + statement.setLong(2, 0); + statement.setBoolean(3, project.isClosed()); + statement.setString(4, null /*project.getNote()*/); + statement.executeUpdate(); + + // get generated id + ResultSet rs = statement.getGeneratedKeys(); + if (rs.next()) { + project.setNumber(rs.getInt(1)); + } + } catch (SQLException ex) { + throw new StorageException("Can't add project", ex); + } finally { + closeStatement(statement); + } + } + + public void addTask(TimerTask task) { + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("INSERT INTO " + + TABLE_TASK + "(name, parent, hidden, note)" + + " VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + statement.setString(1, task.getName()); + statement.setLong(2, task.getParent().getNumber()); + statement.setBoolean(3, task.isClosed()); + statement.setString(4, null /*project.getNote()*/); + statement.executeUpdate(); + + // get generated id + ResultSet rs = statement.getGeneratedKeys(); + if (rs.next()) { + task.setNumber(rs.getInt(1)); + } + } catch (SQLException ex) { + throw new StorageException("Can't add project", ex); + } finally { + closeStatement(statement); + } + } + + public void modifyProject(TimerProject project) { + modifyTask(project); + } + + public void modifyTask(TimerTask task) { + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("UPDATE " + + TABLE_TASK + " SET name=?, parent=?, hidden=?, note=?" + + " WHERE id = ?"); + statement.setString(1, task.getName()); + if (task.getParent() == null) { + statement.setLong(2, 0); + } else { + statement.setLong(2, task.getParent().getNumber()); + } + statement.setBoolean(3, task.isClosed()); + statement.setString(4, null /*project.getNote()*/); + statement.setLong(5, task.getNumber()); + statement.executeUpdate(); + } catch (SQLException ex) { + throw new StorageException("Can't modify task", ex); + } finally { + closeStatement(statement); + } + } + + protected void saveTaskTimes(TimerTask task) throws SQLException { + + // delete all + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("DELETE " + TABLE_TIME + + " WHERE TASKID = ?"); + statement.setInt(1, task.getNumber()); + statement.executeUpdate(); + } finally { + closeStatement(statement); + } + + // add new + try { + statement = connection.prepareStatement("INSERT INTO " + TABLE_TIME + + "(taskid, date, duration)" + + " VALUES(?, ?, ?)"); + SortedMap<Date, Long> taskTimes = task.getAllDaysAndTimes(); + for (Map.Entry<Date, Long> taskTime : taskTimes.entrySet()) { + statement.setLong(1, task.getNumber()); + statement.setDate(2, new java.sql.Date(taskTime.getKey().getTime())); + statement.setLong(3, taskTime.getValue()); + + statement.executeUpdate(); + } + } finally { + closeStatement(statement); + } + } + + public void deleteProject(TimerProject project) { + deleteTask(project); + } + + public void deleteTask(TimerTask task) { + PreparedStatement statement = null; + try { + statement = connection.prepareStatement("DELETE FROM " + + TABLE_TASK + " WHERE id = ?"); + statement.setLong(1, task.getNumber()); + statement.executeUpdate(); + } catch (SQLException ex) { + throw new StorageException("Can't delete project", ex); + } finally { + closeStatement(statement); + } + } + + public void setAnnotation(TimerTask task, Date date, String annotation) { + + } + + public void setTaskTime(TimerTask task, Date date, Long time) { + try { + saveTaskTimes(task); + } catch (SQLException ex) { + throw new StorageException("Can't set task time", ex); + } + } + + public void changeClosedState(TimerTask task) { + modifyTask(task); + } + + public void moveTask(TimerTask task) { + modifyTask(task); + } + + public void mergeTasks(TimerTask destinationTask, + List<TimerTask> otherTasks) { + modifyTask(destinationTask); + for (TimerTask task : otherTasks) { + deleteTask(task); + } + } + + public void startTask(TimerTask task) { + + } + + public void stopTask(TimerTask task) { + + } + + public void dataLoaded(Collection<TimerProject> projects) { + + } +} Added: jtimerhtml5/src/main/java/org/chorem/jtimer/storage/StorageException.java =================================================================== --- jtimerhtml5/src/main/java/org/chorem/jtimer/storage/StorageException.java (rev 0) +++ jtimerhtml5/src/main/java/org/chorem/jtimer/storage/StorageException.java 2013-05-20 13:06:14 UTC (rev 32) @@ -0,0 +1,39 @@ +/* + * #%L + * jTimer + * + * $Id: DailySortedMap.java 2848 2012-03-28 16:37:31Z echatellier $ + * $HeadURL$ + * %% + * Copyright (C) 2013 CodeLutin, Chatellier Eric + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.storage; + +public class StorageException extends RuntimeException { + + /** serialVersionUID. */ + private static final long serialVersionUID = -6544441950983775434L; + + public StorageException(String message, Throwable cause) { + super(message, cause); + } + + public StorageException(String message) { + super(message); + } +} Added: jtimerhtml5/src/main/java/org/chorem/jtimer/utils/DailySortedMap.java =================================================================== --- jtimerhtml5/src/main/java/org/chorem/jtimer/utils/DailySortedMap.java (rev 0) +++ jtimerhtml5/src/main/java/org/chorem/jtimer/utils/DailySortedMap.java 2013-05-20 13:06:14 UTC (rev 32) @@ -0,0 +1,308 @@ +/* + * #%L + * jTimer + * + * $Id: DailySortedMap.java 2848 2012-03-28 16:37:31Z echatellier $ + * $HeadURL$ + * %% + * Copyright (C) 2008 - 2011 CodeLutin, Chatellier Eric + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +package org.chorem.jtimer.utils; + +import java.util.Calendar; +import java.util.Comparator; +import java.util.Date; +import java.util.Map; +import java.util.NavigableMap; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * SortedMap that return a unique value for all date for a day. + * + * @see java.util.SortedMap + * @param <T> values type + * + * @author chatellier + * @version $Revision: 2848 $ + * + * Last update : $Date: 2012-03-28 18:37:31 +0200 (mer., 28 mars 2012) $ + * By : $Author: echatellier $ + */ +public class DailySortedMap<T> extends TreeMap<Date, T> { + + /** serialVersionUID */ + private static final long serialVersionUID = 5736472379626976185L; + + /** + * Constructs a new, empty tree map, using the natural ordering + * of its keys. + */ + public DailySortedMap() { + } + + /** + * Constructs a new, empty tree map, ordered according to + * the given comparator. + * + * @param comparator comparator + */ + public DailySortedMap(Comparator<? super Date> comparator) { + super(comparator); + } + + /** + * Constructs a new tree map containing the same mappings + * as the given map, ordered according to the natural ordering of its keys. + * + * @param m init map + */ + public DailySortedMap(Map<? extends Date, ? extends T> m) { + super(m); + } + + /** + * Constructs a new tree map containing the same mappings and + * using the same ordering as the specified sorted map. + * + * @param m init map + */ + public DailySortedMap(SortedMap<Date, ? extends T> m) { + super(m); + } + + /** + * {@inheritDoc} + */ + @Override + public Map.Entry<Date, T> ceilingEntry(Date key) { + Date dayKey = getDayDate(key); + Map.Entry<Date, T> result = super.ceilingEntry(dayKey); + return result; + + } + + /** + * {@inheritDoc} + */ + @Override + public Date ceilingKey(Date key) { + Date dayKey = getDayDate(key); + Date result = super.ceilingKey(dayKey); + return result; + + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsKey(Object key) { + boolean result = false; + + if (key instanceof Date) { + Date lDate = getDayDate((Date) key); + result = super.containsKey(lDate); + } + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public Map.Entry<Date, T> floorEntry(Date key) { + Date dayKey = getDayDate(key); + Map.Entry<Date, T> result = super.floorEntry(dayKey); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public Date floorKey(Date key) { + Date dayKey = getDayDate(key); + Date result = super.floorKey(dayKey); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public T get(Object key) { + T result = null; + + if (key instanceof Date) { + Date lDate = getDayDate((Date) key); + result = super.get(lDate); + } + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public NavigableMap<Date, T> headMap(Date toKey, boolean inclusive) { + Date dayKey = getDayDate(toKey); + NavigableMap<Date, T> result = super.headMap(dayKey, inclusive); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public SortedMap<Date, T> headMap(Date toKey) { + Date dayKey = getDayDate(toKey); + SortedMap<Date, T> result = super.headMap(dayKey); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public Map.Entry<Date, T> higherEntry(Date key) { + Date dayKey = getDayDate(key); + Map.Entry<Date, T> result = super.higherEntry(dayKey); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public Date higherKey(Date key) { + Date dayKey = getDayDate(key); + Date result = super.higherKey(dayKey); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public Map.Entry<Date, T> lowerEntry(Date key) { + Date dayKey = getDayDate(key); + Map.Entry<Date, T> result = super.lowerEntry(dayKey); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public Date lowerKey(Date key) { + Date dayKey = getDayDate(key); + Date result = super.lowerKey(dayKey); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public T put(Date key, T value) { + Date lDate = getDayDate(key); + T result = super.put(lDate, value); + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void putAll(Map<? extends Date, ? extends T> map) { + for (Map.Entry<? extends Date, ? extends T> entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public NavigableMap<Date, T> subMap(Date fromKey, boolean fromInclusive, + Date toKey, boolean toInclusive) { + Date fromDayKey = getDayDate(fromKey); + Date toDayKey = getDayDate(toKey); + NavigableMap<Date, T> result = super.subMap(fromDayKey, fromInclusive, + toDayKey, toInclusive); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public SortedMap<Date, T> subMap(Date fromKey, Date toKey) { + Date fromDayKey = getDayDate(fromKey); + Date toDayKey = getDayDate(toKey); + SortedMap<Date, T> result = super.subMap(fromDayKey, toDayKey); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public NavigableMap<Date, T> tailMap(Date fromKey, boolean inclusive) { + Date fromDayKey = getDayDate(fromKey); + NavigableMap<Date, T> result = super.tailMap(fromDayKey, inclusive); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public SortedMap<Date, T> tailMap(Date fromKey) { + Date fromDayKey = getDayDate(fromKey); + SortedMap<Date, T> result = super.tailMap(fromDayKey); + return result; + } + + /** + * Get date at 0h00:00.000. + * + * @param date date to convert + * @return day date + */ + protected Date getDayDate(Date date) { + Date ldate = (Date) date.clone(); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(ldate); + + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + Date result = calendar.getTime(); + + return result; + } +}