001/**
002 * Copyright 2016 Tampere University of Technology, Pori Department
003 * 
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 *   http://www.apache.org/licenses/LICENSE-2.0
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package service.tut.pori.apilta.sensors;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Map.Entry;
023import java.util.UUID;
024
025import org.apache.log4j.Logger;
026import org.springframework.jdbc.core.JdbcTemplate;
027import org.springframework.transaction.TransactionStatus;
028import org.springframework.transaction.support.TransactionCallback;
029
030import core.tut.pori.dao.clause.SQLClause.SQLType;
031import core.tut.pori.http.parameters.DataGroups;
032import core.tut.pori.http.parameters.Limits;
033import service.tut.pori.apilta.sensors.datatypes.Condition;
034import service.tut.pori.apilta.sensors.datatypes.Output;
035import service.tut.pori.apilta.sensors.datatypes.SensorTask;
036import service.tut.pori.tasks.TaskDAO;
037import service.tut.pori.tasks.datatypes.Task;
038
039/**
040 * task dao for the sensors service
041 *
042 */
043public class SensorTaskDAO extends TaskDAO {
044  private static final Logger LOGGER = Logger.getLogger(SensorTaskDAO.class);
045  /* tables */
046  private static final String TABLE_MEASUREMENTS_TASKS_CONDITIONS = DATABASE+".measurements_tasks_conditions";
047  private static final String TABLE_MEASUREMENTS_TASKS_OUTPUTS = DATABASE+".measurements_tasks_outputs";
048  /* columns */
049  private static final String COLUMN_CONDITION_ID = "condition_id";
050  private static final String COLUMN_CONDITION_KEY = "condition_key";
051  private static final String COLUMN_CONDITION_VALUE = "condition_value";
052  private static final String COLUMN_FEATURE = "feature";
053  /* sql */
054  private static final String SQL_DELETE_CONDITIONS = "DELETE FROM "+TABLE_MEASUREMENTS_TASKS_CONDITIONS+" WHERE "+Definitions.COLUMN_TASK_ID+"=?";
055  private static final String SQL_DELETE_OUTPUT = "DELETE FROM "+TABLE_MEASUREMENTS_TASKS_OUTPUTS+" WHERE "+Definitions.COLUMN_TASK_ID+"=?";
056  private static final String SQL_INSERT_CONDITION = "INSERT INTO "+TABLE_MEASUREMENTS_TASKS_CONDITIONS+" ("+Definitions.COLUMN_TASK_ID+", "+COLUMN_CONDITION_ID+", "+COLUMN_CONDITION_KEY+", "+COLUMN_CONDITION_VALUE+", "+COLUMN_ROW_CREATED+") VALUES (?,?,?,?,NOW())";
057  private static final int[] SQL_INSERT_CONDITION_SQL_TYPES = {SQLType.STRING.toInt(), SQLType.STRING.toInt(), SQLType.STRING.toInt(), SQLType.STRING.toInt()};
058  private static final String SQL_INSERT_OUTPUT = "INSERT INTO "+TABLE_MEASUREMENTS_TASKS_OUTPUTS+" ("+Definitions.COLUMN_TASK_ID+", "+COLUMN_FEATURE+", "+COLUMN_ROW_CREATED+") VALUES (?,?,NOW())";
059  private static final int[] SQL_INSERT_OUTPUT_SQL_TYPES = {SQLType.STRING.toInt(), SQLType.STRING.toInt()};
060  private static final String SQL_GET_CONDITIONS = "SELECT "+COLUMN_CONDITION_ID+", "+COLUMN_CONDITION_KEY+", "+COLUMN_CONDITION_VALUE+" FROM "+TABLE_MEASUREMENTS_TASKS_CONDITIONS+" WHERE "+Definitions.COLUMN_TASK_ID+"=? ORDER BY "+COLUMN_CONDITION_ID;
061  private static final String SQL_GET_OUTPUT = "SELECT "+COLUMN_FEATURE+" FROM "+TABLE_MEASUREMENTS_TASKS_OUTPUTS+" WHERE "+Definitions.COLUMN_TASK_ID+"=?";
062  private static final int[] SQL_TYPE_TASK_ID = {SQLType.STRING.toInt()};
063  
064  @Override
065  public String createTask(Task task) {
066    if(task instanceof SensorTask){
067      return createTask((SensorTask) task);
068    }else{
069      LOGGER.debug("Using super class for task insertion...");
070      return super.createTask(task);
071    }
072  }
073  
074  /**
075   * 
076   * @param task
077   * @return identifier of the created task
078   */
079  public String createTask(SensorTask task) {
080    return getTransactionTemplate().execute(new TransactionCallback<String>() {
081      @Override
082      public String doInTransaction(TransactionStatus arg0) {
083        String taskId = SensorTaskDAO.super.createTask(task);
084        if(taskId == null){
085          LOGGER.warn("Failed to create base task.");
086          return null;
087        }
088        
089        addConditions(task.getConditions(), taskId);
090        addOutput(task.getOutput(), taskId);
091        
092        return taskId;
093      }
094    });
095  }
096
097  /**
098   * 
099   * @param output
100   * @param taskId
101   */
102  private void addOutput(List<Output> output, String taskId) {
103    if(output == null || output.isEmpty()){
104      LOGGER.warn("No output for task, id: "+taskId);
105      return;
106    }
107    
108    JdbcTemplate t = getJdbcTemplate();
109    Object[] ob = {taskId, null};
110    for(Output o : output) {
111      ob[1] = o.getFeature();
112      t.update(SQL_INSERT_OUTPUT, ob, SQL_INSERT_OUTPUT_SQL_TYPES);
113    }
114  }
115
116  /**
117   * 
118   * @param conditions
119   * @param taskId
120   */
121  private void addConditions(List<Condition> conditions, String taskId) {
122    if(conditions == null || conditions.isEmpty()){
123      LOGGER.warn("No conditions for task, id: "+taskId);
124      return;
125    }
126    
127    JdbcTemplate t = getJdbcTemplate();
128    Object[] ob = {taskId, null, null, null};
129    for(Condition condition : conditions){
130      ob[1] = UUID.randomUUID().toString(); // generate random identifier for this condition
131      for(Entry<String, String> entry : condition.getConditions().entrySet()){
132        ob[2] = entry.getKey();
133        ob[3] = entry.getValue();
134        t.update(SQL_INSERT_CONDITION, ob, SQL_INSERT_CONDITION_SQL_TYPES);
135      }
136    }
137  }
138
139  @Override
140  public boolean updateTask(Task task) throws IllegalArgumentException {
141    if(task instanceof SensorTask){
142      return updateTask((SensorTask) task);
143    }else{
144      LOGGER.debug("Using super class for task update...");
145      return super.updateTask(task);
146    }
147  }
148  
149  /**
150   * 
151   * @param task
152   * @return true on success
153   */
154  public boolean updateTask(SensorTask task) {
155    return getTransactionTemplate().execute(new TransactionCallback<Boolean>() {
156      @Override
157      public Boolean doInTransaction(TransactionStatus status) {
158        if(!SensorTaskDAO.super.updateTask(task)){
159          LOGGER.warn("Base task update failed, aborting update...");
160          return Boolean.FALSE;
161        }
162        
163        for(String taskId : task.getTaskIds()){
164          removeConditions(taskId);
165          addConditions(task.getConditions(), taskId);
166          removeOutput(taskId);
167          addOutput(task.getOutput(), taskId);
168        }
169        
170        return Boolean.TRUE;
171      }
172    });
173  }
174
175  /**
176   * 
177   * @param taskId
178   */
179  private void removeOutput(String taskId) {
180    getJdbcTemplate().update(SQL_DELETE_OUTPUT, new Object[]{taskId}, SQL_TYPE_TASK_ID);
181  }
182
183  /**
184   * 
185   * @param taskId
186   */
187  private void removeConditions(String taskId) {
188    getJdbcTemplate().update(SQL_DELETE_CONDITIONS, new Object[]{taskId}, SQL_TYPE_TASK_ID);
189  }
190
191  @Override
192  public SensorTask getTask(Long backendId, DataGroups dataGroups, Limits limits, String taskId) {
193    Task task = super.getTask(backendId, dataGroups, limits, taskId);
194    if(task == null) {
195      LOGGER.debug("Task not found, id: "+taskId);
196      return null;
197    }
198    
199    SensorTask sTask = new SensorTask(task);
200    sTask.setConditions(getConditions(taskId));
201    sTask.setOutput(getOuput(taskId));
202    return sTask;
203  }
204
205  /**
206   * 
207   * @param taskId
208   * @return list of output or null if none was found
209   */
210  private List<Output> getOuput(String taskId) {
211    List<String> features = getJdbcTemplate().queryForList(SQL_GET_OUTPUT, new Object[]{taskId}, SQL_TYPE_TASK_ID, String.class);
212    if(features.isEmpty()){
213      LOGGER.warn("No output for task, id: "+taskId);
214      return null;
215    }
216    
217    ArrayList<Output> output = new ArrayList<>(features.size());
218    for(String feature : features) {
219      Output o = new Output();
220      o.setFeature(feature);
221      output.add(o);
222    }
223    
224    return output;
225  }
226
227  /**
228   * 
229   * @param taskId
230   * @return list of conditions or null if none was found
231   */
232  private List<Condition> getConditions(String taskId) {
233    List<Map<String, Object>> rows = getJdbcTemplate().queryForList(SQL_GET_CONDITIONS, new Object[]{taskId}, SQL_TYPE_TASK_ID);
234    if(rows.isEmpty()){
235      LOGGER.warn("No conditions for task, id: "+taskId);
236      return null;
237    }
238    
239    ArrayList<Condition> conditions = new ArrayList<>();
240    String conditionId = null;
241    Condition condition = null;
242    for(Map<String, Object> row : rows){
243      Object tempId = row.get(COLUMN_CONDITION_ID);
244      if(!tempId.equals(conditionId)){ // the result list is sorted by condition id, so the condition collection changes when the id changes
245        condition = new Condition();
246        condition.setConditions(new HashMap<>());
247        conditions.add(condition);
248        conditionId = (String) tempId;
249      } 
250      condition.getConditions().put((String) row.get(COLUMN_CONDITION_KEY), (String) row.get(COLUMN_CONDITION_VALUE));    
251    } // for rows
252    return conditions;
253  }
254
255  @Override
256  public void removeTask(String taskId) {
257    getTransactionTemplate().execute(new TransactionCallback<Void>() {
258
259      @Override
260      public Void doInTransaction(TransactionStatus status) {
261        removeConditions(taskId);
262        removeOutput(taskId);
263        SensorTaskDAO.super.removeTask(taskId);
264        return null;
265      }
266    });
267  }
268}
269