001/* 002 * Copyright 2016 Anyware Services 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 org.ametys.plugins.workflow.store; 017 018import java.sql.Connection; 019import java.sql.PreparedStatement; 020import java.sql.ResultSet; 021import java.sql.SQLException; 022import java.sql.Statement; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.avalon.framework.activity.Disposable; 029import org.apache.avalon.framework.activity.Initializable; 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.configuration.Configurable; 032import org.apache.avalon.framework.configuration.Configuration; 033import org.apache.avalon.framework.configuration.ConfigurationException; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.avalon.framework.service.Serviceable; 037import org.apache.commons.lang.StringUtils; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041import org.ametys.core.datasource.ConnectionHelper; 042import org.ametys.core.datasource.SQLDataSourceManager; 043import org.ametys.plugins.workflow.JDBCPropertySet; 044import org.ametys.plugins.workflow.PropertySetExpression; 045import org.ametys.runtime.config.Config; 046 047import com.opensymphony.module.propertyset.PropertySet; 048import com.opensymphony.workflow.StoreException; 049import com.opensymphony.workflow.query.Expression; 050import com.opensymphony.workflow.query.NestedExpression; 051import com.opensymphony.workflow.query.WorkflowExpressionQuery; 052import com.opensymphony.workflow.spi.jdbc.JDBCWorkflowStore; 053 054/** 055 * Ametys JDBC workflow store 056 */ 057public class JdbcWorkflowStore extends JDBCWorkflowStore implements AmetysWorkflowStore, Component, Serviceable, Configurable, Initializable, Disposable 058{ 059 /** Avalon role. */ 060 public static final String ROLE = JdbcWorkflowStore.class.getName(); 061 062 /** Logger */ 063 protected Logger _logger = LoggerFactory.getLogger(JdbcWorkflowStore.class); 064 065 /** The manager for SQL data source */ 066 protected SQLDataSourceManager _sqlDataSourceManager; 067 068 /** Configured datasource id */ 069 protected String _dataSourceId; 070 071 /** Configured data source type */ 072 protected String _databaseType; 073 074 /** from com.opensymphony.workflow.spi.jdbc.MySQLWorkflowStore */ 075 protected String _entrySequenceIncrement; 076 /** from com.opensymphony.workflow.spi.jdbc.MySQLWorkflowStore */ 077 protected String _entrySequenceRetrieve; 078 /** from com.opensymphony.workflow.spi.jdbc.MySQLWorkflowStore */ 079 protected String _stepSequenceIncrement; 080 /** from com.opensymphony.workflow.spi.jdbc.MySQLWorkflowStore */ 081 protected String _stepSequenceRetrieve; 082 083 public void service(ServiceManager manager) throws ServiceException 084 { 085 _sqlDataSourceManager = (SQLDataSourceManager) manager.lookup(SQLDataSourceManager.ROLE); 086 } 087 088 public void configure(Configuration configuration) throws ConfigurationException 089 { 090 Configuration dataSourceConf = configuration.getChild("datasource", true); 091 String dataSourceConfParam = dataSourceConf.getValue(); 092 String dataSourceConfType = dataSourceConf.getAttribute("type", "config"); 093 094 if (StringUtils.equals(dataSourceConfType, "config")) 095 { 096 _dataSourceId = Config.getInstance().getValueAsString(dataSourceConfParam); 097 } 098 else // expecting type="id" 099 { 100 _dataSourceId = dataSourceConfParam; 101 } 102 } 103 104 public void initialize() throws Exception 105 { 106 ds = _sqlDataSourceManager.getSQLDataSource(_dataSourceId); 107 108 if (ds == null) 109 { 110 throw new ConfigurationException("Invalid datasource id: " + _dataSourceId); 111 } 112 113 this.init(Collections.EMPTY_MAP); 114 } 115 116 public void dispose() 117 { 118 _dataSourceId = null; 119 ds = null; 120 } 121 122 @Override 123 public void init(Map props) throws StoreException 124 { 125 // Copier les bons paramètres pour JDBCWorkflowStore 126 entryTable = "OS_WFENTRY"; 127 entryId = "ID"; 128 entryName = "NAME"; 129 entryState = "STATE"; 130 historyTable = "OS_HISTORYSTEP"; 131 historyPrevTable = "OS_HISTORYSTEP_PREV"; 132 currentTable = "OS_CURRENTSTEP"; 133 currentPrevTable = "OS_CURRENTSTEP_PREV"; 134 stepId = "ID"; 135 stepEntryId = "ENTRY_ID"; 136 stepStepId = "STEP_ID"; 137 stepActionId = "ACTION_ID"; 138 stepOwner = "OWNER"; 139 stepCaller = "CALLER"; 140 stepStartDate = "START_DATE"; 141 stepFinishDate = "FINISH_DATE"; 142 stepDueDate = "DUE_DATE"; 143 stepStatus = "STATUS"; 144 stepPreviousId = "PREVIOUS_ID"; 145 146 String databaseType; 147 try 148 { 149 databaseType = getDatabaseType(); 150 } 151 catch (SQLException e) 152 { 153 throw new StoreException("Unable to retrieve database type", e); 154 } 155 156 // DATABASE_MYSQL 157 if (ConnectionHelper.DATABASE_MYSQL.equals(databaseType)) 158 { 159 _entrySequenceIncrement = "INSERT INTO OS_ENTRYIDS (ID) values (null)"; 160 _entrySequenceRetrieve = "SELECT LAST_INSERT_ID()"; 161 _stepSequenceIncrement = "INSERT INTO OS_STEPIDS (ID) values (null)"; 162 _stepSequenceRetrieve = "SELECT LAST_INSERT_ID()"; 163 } 164 165 // DATABASE_DERBY 166 else if (ConnectionHelper.DATABASE_DERBY.equals(databaseType)) 167 { 168 _entrySequenceIncrement = "INSERT INTO OS_ENTRYIDS values (DEFAULT)"; 169 _entrySequenceRetrieve = "VALUES IDENTITY_VAL_LOCAL()"; 170 _stepSequenceIncrement = "INSERT INTO OS_STEPIDS values (DEFAULT)"; 171 _stepSequenceRetrieve = "VALUES IDENTITY_VAL_LOCAL()"; 172 } 173 174 // DATABASE_POSTGRES 175 else if (ConnectionHelper.DATABASE_POSTGRES.equals(databaseType)) 176 { 177 entrySequence = "SELECT nextval('seq_os_wfentry')"; 178 stepSequence = "SELECT nextval('seq_os_currentsteps')"; 179 } 180 181 // DATABASE_ORACLE 182 else if (ConnectionHelper.DATABASE_ORACLE.equals(databaseType)) 183 { 184 entrySequence = "SELECT seq_os_wfentry.nextval from dual"; 185 stepSequence = "SELECT seq_os_currentsteps.nextval from dual"; 186 } 187 188 // DATABASE_HSQLDB 189 else if (ConnectionHelper.DATABASE_HSQLDB.equals(databaseType)) 190 { 191 entrySequence = "call next value for seq_os_wfentry"; 192 stepSequence = "call next value for seq_os_currentsteps"; 193 } 194 195 else 196 { 197 throw new IllegalArgumentException(String.format("Unsupported database type '%s'", databaseType)); 198 } 199 } 200 201 /** 202 * Database type getter 203 * @return The database type 204 * @throws SQLException on error 205 */ 206 public String getDatabaseType() throws SQLException 207 { 208 if (_databaseType == null) 209 { 210 try (Connection connection = getConnection();) 211 { 212 _databaseType = ConnectionHelper.getDatabaseType(connection); 213 } 214 } 215 216 return _databaseType; 217 } 218 219 @Override 220 public PropertySet getPropertySet(long id) 221 { 222 // Utiliser notre propre property set (cocoon connection pool) 223 Map<String, String> args = new HashMap<>(1); 224 args.put("globalKey", JDBCPropertySet.OSWF_PREFIX + id); 225 226 Map<String, Object> config = new HashMap<>(1); 227 config.put("datasource", ds); 228 229 PropertySet ps = new JDBCPropertySet(); 230 ps.init(config, args); 231 return ps; 232 } 233 234 @Override 235 public boolean shouldClearHistory() 236 { 237 return true; 238 } 239 240 @Override 241 protected void cleanup(Connection connection, Statement statement, ResultSet result) 242 { 243 ConnectionHelper.cleanup(result); 244 ConnectionHelper.cleanup(statement); 245 if (closeConnWhenDone) 246 { 247 ConnectionHelper.cleanup(connection); 248 } 249 } 250 251 @Override 252 public void deleteInstance(long idWorkflow) 253 { 254 Connection c = null; 255 PreparedStatement stmt = null; 256 257 try 258 { 259 // Récupérer la connexion du pool 260 c = getConnection(); 261 // Supprimer les entrées récursivement grâce aux "ON DELETE CASCADE" 262 // Supprime dans l'ordre les lignes correspondantes des tables 263 // OS_HISTORYSTEP_PREV, OS_CURRENTSTEP_PREV, OS_HISTORYSTEP, 264 // OS_CURRENTSTEP 265 String sql = "DELETE FROM " + entryTable + " WHERE " + entryId + " = ?"; 266 // Exécuter la requête 267 stmt = c.prepareStatement(sql); 268 // Positionner l'identifiant de l'instance du workflow 269 stmt.setLong(1, idWorkflow); 270 271 // Logger la requête 272 _logger.debug(sql); 273 274 // Vérifier si une ligne a bien été supprimée 275 if (stmt.executeUpdate() != 1) 276 { 277 _logger.error("Unable to remove a workflow instance in database"); 278 } 279 } 280 catch (SQLException ex) 281 { 282 _logger.error("Error while removing a workflow instance", ex); 283 } 284 finally 285 { 286 cleanup(c, stmt, null); 287 } 288 } 289 290 public void clearHistory(long idWorkflow) 291 { 292 Connection c = null; 293 PreparedStatement stmt = null; 294 295 try 296 { 297 // Récupérer la connexion du pool 298 c = getConnection(); 299 // Supprimer les entrées récursivement grâce aux "ON DELETE CASCADE" 300 // Supprime dans l'ordre les lignes correspondantes des tables 301 // OS_HISTORYSTEP_PREV, OS_CURRENTSTEP_PREV, OS_HISTORYSTEP 302 String sql = "DELETE FROM " + historyTable + " WHERE " + stepEntryId + " = ?"; 303 // Exécuter la requête 304 stmt = c.prepareStatement(sql); 305 // Positionner l'identifiant de l'instance du workflow 306 stmt.setLong(1, idWorkflow); 307 308 // Logger la requête 309 _logger.debug(sql); 310 311 // Supprimer les éventuelles lignes 312 stmt.executeUpdate(); 313 } 314 catch (SQLException e) 315 { 316 _logger.error("Error while clearing history steps from an workflow instance", e); 317 } 318 finally 319 { 320 cleanup(c, stmt, null); 321 } 322 } 323 324 @Override 325 public List query(WorkflowExpressionQuery e) throws StoreException 326 { 327 Expression expression = e.getExpression(); 328 329 if (expression instanceof PropertySetExpression) 330 { 331 return JDBCPropertySet.query(ds, expression); 332 } 333 else if (expression instanceof NestedExpression) 334 { 335 NestedExpression expr = (NestedExpression) expression; 336 337 // Vérifier qu'il n'y ai pas autre chose que du PropertySet 338 // imbriqué avant de l'envoyer au JDBCPropertySet 339 if (JDBCPropertySet.isPropertySetExpressionsNested(expr)) 340 { 341 return JDBCPropertySet.query(ds, expression); 342 } 343 344 return super.query(e); 345 } 346 else 347 { 348 return super.query(e); 349 } 350 } 351 352 @Override 353 protected long getNextEntrySequence(Connection c) throws SQLException 354 { 355 // DATABASE_MYSQL - DATABASE_DERBY 356 String databaseType = getDatabaseType(); 357 if (ConnectionHelper.DATABASE_MYSQL.equals(databaseType) || ConnectionHelper.DATABASE_DERBY.equals(databaseType)) 358 { 359 PreparedStatement stmt = null; 360 ResultSet rset = null; 361 362 try 363 { 364 stmt = c.prepareStatement(_entrySequenceIncrement); 365 stmt.executeUpdate(); 366 rset = c.createStatement().executeQuery(_entrySequenceRetrieve); 367 368 rset.next(); 369 370 long id = rset.getLong(1); 371 372 return id; 373 } 374 finally 375 { 376 cleanup(null, stmt, rset); 377 } 378 } 379 else 380 { 381 return super.getNextEntrySequence(c); 382 } 383 } 384 385 @Override 386 protected long getNextStepSequence(Connection c) throws SQLException 387 { 388 // DATABASE_MYSQL - DATABASE_DERBY 389 String databaseType = getDatabaseType(); 390 if (ConnectionHelper.DATABASE_MYSQL.equals(databaseType) || ConnectionHelper.DATABASE_DERBY.equals(databaseType)) 391 { 392 PreparedStatement stmt = null; 393 ResultSet rset = null; 394 395 try 396 { 397 stmt = c.prepareStatement(_stepSequenceIncrement); 398 stmt.executeUpdate(); 399 rset = c.createStatement().executeQuery(_stepSequenceRetrieve); 400 401 rset.next(); 402 403 long id = rset.getLong(1); 404 405 return id; 406 } 407 finally 408 { 409 cleanup(null, stmt, rset); 410 } 411 } 412 else 413 { 414 return super.getNextEntrySequence(c); 415 } 416 } 417}