001/** 002 * Copyright 2014 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.contentanalysis; 017 018import java.util.ArrayList; 019import java.util.EnumSet; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023import java.util.Map.Entry; 024import java.util.Set; 025 026import org.apache.commons.lang3.BooleanUtils; 027import org.apache.commons.lang3.StringUtils; 028import org.apache.log4j.Logger; 029import org.springframework.jdbc.core.JdbcTemplate; 030import org.springframework.transaction.TransactionStatus; 031import org.springframework.transaction.support.TransactionCallback; 032 033import service.tut.pori.contentanalysis.AnalysisBackend.Capability; 034import core.tut.pori.dao.clause.AndClause; 035import core.tut.pori.dao.clause.SQLClause.SQLType; 036import core.tut.pori.dao.SQLDAO; 037import core.tut.pori.dao.SQLSelectBuilder; 038import core.tut.pori.http.parameters.DataGroups; 039 040/** 041 * A DAO for inserting, removing and modifying the analysis back-ends known by the system. 042 * 043 */ 044public class BackendDAO extends SQLDAO{ 045 private static final Logger LOGGER = Logger.getLogger(BackendDAO.class); 046 /* table names */ 047 private static final String TABLE_BACKEND_CAPABILITIES = DATABASE+".ca_backend_capabilities"; 048 private static final String TABLE_BACKENDS = DATABASE+".ca_backends"; 049 /* columns */ 050 private static final String COLUMN_ANALYSIS_URI = "analysis_uri"; 051 private static final String COLUMN_CAPABILITY = "capability"; 052 private static final String COLUMN_ENABLED = "enabled"; 053 private static final String COLUMN_DEFAULT_TASK_DATAGROUPS = "default_task_datagroups"; 054 /* sql strings */ 055 private static final String SQL_DELETE_BACKEND = "DELETE FROM "+TABLE_BACKENDS+" WHERE "+Definitions.COLUMN_BACKEND_ID+"=?"; 056 private static final int[] SQL_DELETE_BACKEND_SQL_TYPES = new int[]{SQLType.STRING.toInt()}; 057 058 private static final String SQL_DELETE_CAPABILITIES = "DELETE FROM "+TABLE_BACKEND_CAPABILITIES+" WHERE "+Definitions.COLUMN_BACKEND_ID+"=?"; 059 private static final int[] SQL_DELETE_CAPABILITIES_SQL_TYPES = new int[]{SQLType.STRING.toInt()}; 060 061 private static final String SQL_INSERT_BACKEND = "INSERT INTO " + TABLE_BACKENDS + " ("+Definitions.COLUMN_BACKEND_ID+", "+Definitions.COLUMN_DESCRIPTION+", "+COLUMN_ENABLED+", "+COLUMN_ANALYSIS_URI+", "+COLUMN_DEFAULT_TASK_DATAGROUPS+", "+COLUMN_ROW_CREATED+") VALUES (?,?,?,?,?,NOW())"; 062 private static final int[] SQL_INSERT_BACKEND_SQL_TYPES = new int[]{SQLType.INTEGER.toInt(),SQLType.STRING.toInt(),SQLType.INTEGER.toInt(),SQLType.STRING.toInt(),SQLType.STRING.toInt()}; 063 064 private static final String SQL_INSERT_CAPABILITIES = "INSERT INTO "+TABLE_BACKEND_CAPABILITIES+" ("+Definitions.COLUMN_BACKEND_ID+","+COLUMN_CAPABILITY+","+COLUMN_ROW_CREATED+") VALUES (?,?,NOW()) ON DUPLICATE KEY UPDATE "+COLUMN_ROW_UPDATED+"=NOW()"; 065 private static final int[] SQL_INSERT_CAPABILITIES_SQL_TYPES = new int[]{SQLType.STRING.toInt(),SQLType.STRING.toInt()}; 066 067 private static final String SQL_SELECT_BACKENDS = "SELECT "+TABLE_BACKENDS+"."+Definitions.COLUMN_BACKEND_ID+","+TABLE_BACKENDS+"."+COLUMN_ANALYSIS_URI+","+TABLE_BACKENDS+"."+COLUMN_ENABLED+","+TABLE_BACKENDS+"."+Definitions.COLUMN_DESCRIPTION+","+TABLE_BACKENDS+"."+COLUMN_DEFAULT_TASK_DATAGROUPS+ 068 " FROM "+TABLE_BACKENDS+" INNER JOIN "+TABLE_BACKEND_CAPABILITIES+" ON "+TABLE_BACKEND_CAPABILITIES+"."+Definitions.COLUMN_BACKEND_ID+"="+TABLE_BACKENDS+"."+Definitions.COLUMN_BACKEND_ID+" WHERE "+TABLE_BACKEND_CAPABILITIES+"."+COLUMN_CAPABILITY+"=? AND "+TABLE_BACKENDS+"."+COLUMN_ENABLED+"="+BooleanUtils.toInteger(true); 069 private static final int[] SQL_SELECT_BACKENDS_SQL_TYPES = new int[]{SQLType.STRING.toInt()}; 070 071 private static final String SQL_UPDATE_BACKENDS = "UPDATE "+TABLE_BACKENDS+" SET "+COLUMN_ANALYSIS_URI+"=?,"+COLUMN_ENABLED+"=?,"+Definitions.COLUMN_DESCRIPTION+"=?,"+COLUMN_DEFAULT_TASK_DATAGROUPS+"=? WHERE "+Definitions.COLUMN_BACKEND_ID+"=?"; 072 private static final int[] SQL_UPDATE_BACKENDS_SQL_TYPES = new int[]{SQLType.STRING.toInt(),SQLType.STRING.toInt(),SQLType.STRING.toInt(),SQLType.STRING.toInt(),SQLType.STRING.toInt()}; 073 074 private static final String[] TABLE_BACKEND_CAPABILITIES_ALL_COLUMNS = new String[]{Definitions.COLUMN_BACKEND_ID,COLUMN_CAPABILITY}; 075 private static final String[] TABLE_BACKENDS_ALL_COLUMNS = new String[]{Definitions.COLUMN_BACKEND_ID,COLUMN_ANALYSIS_URI,COLUMN_ENABLED,Definitions.COLUMN_DESCRIPTION,COLUMN_DEFAULT_TASK_DATAGROUPS}; 076 077 private static final String SQL_GET_ENABLED_BACKENDS = "SELECT "+StringUtils.join(TABLE_BACKENDS_ALL_COLUMNS, ',')+" FROM "+TABLE_BACKENDS+" WHERE "+COLUMN_ENABLED+"="+BooleanUtils.toInteger(true); 078 079 private static final String SQL_SELECT_BACKEND_BY_BACKEND_ID = "SELECT "+StringUtils.join(TABLE_BACKENDS_ALL_COLUMNS, ',')+" FROM "+TABLE_BACKENDS+" WHERE "+Definitions.COLUMN_BACKEND_ID+"=? LIMIT 1"; 080 private static final int[] SQL_SELECT_BACKEND_BY_BACKEND_ID_TYPES = new int[]{SQLType.INTEGER.toInt()}; 081 082 083 /** 084 * 085 * @param end 086 * 087 */ 088 public void createBackend(final AnalysisBackend end){ 089 getTransactionTemplate().execute(new TransactionCallback<Void>() { 090 091 @Override 092 public Void doInTransaction(TransactionStatus status) { 093 JdbcTemplate t = getJdbcTemplate(); 094 t.update(SQL_INSERT_BACKEND, new Object[]{end.getBackendId(),end.getDescription(),BooleanUtils.toInteger(end.isEnabled()),end.getAnalysisUri(),end.getDefaultTaskDataGroups().toDataGroupString()}, SQL_INSERT_BACKEND_SQL_TYPES); 095 096 addCapabilities(t.queryForObject(SQL_SELECT_LAST_INSERT_ID, Integer.class), end.getCapabilities()); 097 return null; 098 } 099 }); 100 } 101 102 /** 103 * add the list of capabilities for the given backend 104 * 105 * @param backendId 106 * @param capabilities 107 */ 108 private void addCapabilities(Integer backendId, EnumSet<Capability> capabilities){ 109 if(capabilities == null){ 110 LOGGER.debug("No capabilities."); 111 return; 112 } 113 JdbcTemplate t = getJdbcTemplate(); 114 Object[] ob = new Object[]{backendId,null}; 115 for(Iterator<Capability> iter = capabilities.iterator();iter.hasNext();){ 116 ob[1] = iter.next().toInt(); 117 t.update(SQL_INSERT_CAPABILITIES, ob, SQL_INSERT_CAPABILITIES_SQL_TYPES); 118 } 119 } 120 121 /** 122 * updates the details for the given backend 123 * 124 * @param end 125 * 126 */ 127 public void updateBackend(AnalysisBackend end){ 128 JdbcTemplate t = getJdbcTemplate(); 129 Integer backendId = end.getBackendId(); 130 t.update(SQL_UPDATE_BACKENDS, new Object[]{end.getAnalysisUri(),BooleanUtils.toInteger(end.isEnabled()),end.getDescription(),end.getDefaultTaskDataGroups().toDataGroupString(),backendId}, SQL_UPDATE_BACKENDS_SQL_TYPES); 131 132 removeCapabilities(end.getBackendId()); // remove all previously set capabilities 133 134 addCapabilities(backendId, end.getCapabilities()); 135 } 136 137 /** 138 * 139 * @param backendId 140 */ 141 private void removeCapabilities(Integer backendId){ 142 getJdbcTemplate().update(SQL_DELETE_CAPABILITIES, new Object[]{backendId}, SQL_DELETE_CAPABILITIES_SQL_TYPES); 143 } 144 145 /** 146 * 147 * @param backendId 148 * 149 */ 150 public void removeBackend(Integer backendId){ 151 getJdbcTemplate().update(SQL_DELETE_BACKEND, new Object[]{backendId}, SQL_DELETE_BACKEND_SQL_TYPES); 152 removeCapabilities(backendId); 153 } 154 155 /** 156 * convenience method for retrieving all known backends 157 * @return list of backends 158 */ 159 public List<AnalysisBackend> getBackends(){ 160 return getBackends((List<Integer>)null); 161 } 162 163 /** 164 * Only enabled backends will be returned. 165 * 166 * @param capability 167 * @return the list of backends with the given capability or null if none available 168 */ 169 public List<AnalysisBackend> getBackends(Capability capability){ 170 List<AnalysisBackend> ends = extractBackends(getJdbcTemplate().queryForList(SQL_SELECT_BACKENDS, new Object[]{capability.toInt()}, SQL_SELECT_BACKENDS_SQL_TYPES)); 171 setCapabilities(ends); 172 return ends; 173 } 174 175 /** 176 * 177 * @param capabilities 178 * @return list of back-ends matching ANY of the given capabilities or null if none was found 179 */ 180 public List<AnalysisBackend> getBackends(Set<Capability> capabilities){ 181 if(capabilities == null || capabilities.isEmpty()){ 182 LOGGER.debug("Empty capability list."); 183 return null; 184 } 185 List<AnalysisBackend> ends = new ArrayList<>(); 186 for(Capability c : capabilities){ // loop all, we could also replace this with SQL join 187 List<AnalysisBackend> tempEnds = getBackends(c); 188 if(tempEnds != null){ 189 ends.addAll(tempEnds); 190 }else{ 191 LOGGER.debug("No back-ends with capability: "+c.name()); 192 } 193 } 194 return (ends.isEmpty() ? null : ends); 195 } 196 197 /** 198 * 199 * @param rows 200 * @return list of backends from the row map or null if none 201 */ 202 private List<AnalysisBackend> extractBackends(List<Map<String,Object>> rows){ 203 if(rows.isEmpty()){ 204 LOGGER.debug("No backends."); 205 return null; 206 } 207 List<AnalysisBackend> ends = new ArrayList<>(rows.size()); 208 for(Iterator<Map<String,Object>> rowIter = rows.iterator();rowIter.hasNext();){ 209 AnalysisBackend end = new AnalysisBackend(); 210 for(Entry<String,Object> rowEntry : rowIter.next().entrySet()){ 211 switch(rowEntry.getKey()){ 212 case Definitions.COLUMN_BACKEND_ID: 213 Integer id = (Integer) rowEntry.getValue(); 214 end.setBackendId(id); 215 break; 216 case COLUMN_ANALYSIS_URI: 217 end.setAnalysisUri((String) rowEntry.getValue()); 218 break; 219 case COLUMN_ENABLED: 220 end.setEnabled(BooleanUtils.toBoolean((Integer) rowEntry.getValue())); 221 break; 222 case Definitions.COLUMN_DESCRIPTION: 223 end.setDescription((String) rowEntry.getValue()); 224 break; 225 case COLUMN_DEFAULT_TASK_DATAGROUPS: 226 DataGroups dg = new DataGroups(); 227 dg.initialize((String)rowEntry.getValue()); 228 end.setDefaultTaskDataGroups(dg); 229 break; 230 default: 231 LOGGER.warn("Ignored unknown column: "+rowEntry.getKey()); 232 break; 233 } 234 } 235 ends.add(end); 236 } 237 return ends; 238 } 239 240 /** 241 * helper method for retrieving (and setting) the capabilities for the given list of backend 242 * 243 * @param ends 244 */ 245 private void setCapabilities(List<AnalysisBackend> ends){ 246 if(ends == null){ 247 LOGGER.debug("No backends in the list."); 248 return; 249 } 250 251 Integer[] ids = new Integer[ends.size()]; 252 int index = 0; 253 for(Iterator<AnalysisBackend> iter = ends.iterator();iter.hasNext();){ // get ids 254 ids[index++] = iter.next().getBackendId(); 255 } 256 257 SQLSelectBuilder sql = new SQLSelectBuilder(TABLE_BACKEND_CAPABILITIES); 258 sql.addSelectColumns(TABLE_BACKEND_CAPABILITIES_ALL_COLUMNS); 259 sql.addWhereClause(new AndClause(Definitions.COLUMN_BACKEND_ID, ids, SQLType.INTEGER)); 260 261 List<Map<String,Object>> rows = getJdbcTemplate().queryForList(sql.toSQLString(), sql.getValues(), sql.getValueTypes()); 262 for(Iterator<Map<String,Object>> rowIter = rows.iterator();rowIter.hasNext();){ 263 Integer backendId = null; 264 Capability capability = null; 265 for(Entry<String,Object> rowEntry : rowIter.next().entrySet()){ // go through the returned rows 266 switch(rowEntry.getKey()){ 267 case Definitions.COLUMN_BACKEND_ID: 268 backendId = (Integer) rowEntry.getValue(); 269 break; 270 case COLUMN_CAPABILITY: 271 capability = Capability.fromInt((Integer) rowEntry.getValue()); 272 break; 273 default: 274 LOGGER.warn("Ignored unknown column: "+rowEntry.getKey()); 275 break; 276 } 277 } // for row 278 279 for(Iterator<AnalysisBackend> bIter = ends.iterator();bIter.hasNext();){ // find the owner of this capability 280 AnalysisBackend end = bIter.next(); 281 if(end.getBackendId().equals(backendId)){ 282 end.addCapability(capability); 283 break; 284 } 285 } // for backends 286 } // for rows 287 } 288 289 /** 290 * 291 * @param backendIds if != null, only the requested backends will be returned 292 * @return list of backends or null if none available. 293 */ 294 public List<AnalysisBackend> getBackends(List<Integer> backendIds){ 295 SQLSelectBuilder sqlBuilder = new SQLSelectBuilder(TABLE_BACKENDS); 296 sqlBuilder.addSelectColumns(TABLE_BACKENDS_ALL_COLUMNS); 297 298 if(backendIds != null){ 299 sqlBuilder.addWhereClause(new AndClause(Definitions.COLUMN_BACKEND_ID, backendIds.toArray(), SQLType.INTEGER)); 300 } 301 302 List<AnalysisBackend> ends = extractBackends(getJdbcTemplate().queryForList(sqlBuilder.toSQLString(), sqlBuilder.getValues(), sqlBuilder.getValueTypes())); 303 setCapabilities(ends); 304 return ends; 305 } 306 307 /** 308 * 309 * @return list of back-ends or null if none was found 310 */ 311 public List<AnalysisBackend> getEnabledBackends(){ 312 return extractBackends(getJdbcTemplate().queryForList(SQL_GET_ENABLED_BACKENDS)); 313 } 314 315 /** 316 * 317 * @param backendId 318 * @return the back-end with the given is or null if not found 319 */ 320 public AnalysisBackend getBackend(Integer backendId){ 321 List<Map<String,Object>> rows = getJdbcTemplate().queryForList(SQL_SELECT_BACKEND_BY_BACKEND_ID, new Object[]{backendId}, SQL_SELECT_BACKEND_BY_BACKEND_ID_TYPES); 322 if(rows.isEmpty()){ 323 return null; 324 }else{ 325 List<AnalysisBackend> backends = extractBackends(rows); 326 setCapabilities(backends); 327 return backends.get(0); 328 } 329 } 330}