| 1 16 17 package org.apache.cocoon.acting.modular; 18 19 import java.io.IOException ; 20 import java.sql.Connection ; 21 import java.sql.PreparedStatement ; 22 import java.sql.SQLException ; 23 import java.util.Map ; 24 25 import org.apache.avalon.excalibur.datasource.DataSourceComponent; 26 import org.apache.avalon.framework.activity.Disposable; 27 import org.apache.avalon.framework.configuration.Configurable; 28 import org.apache.avalon.framework.configuration.Configuration; 29 import org.apache.avalon.framework.configuration.ConfigurationException; 30 import org.apache.avalon.framework.parameters.Parameters; 31 import org.apache.avalon.framework.service.ServiceException; 32 import org.apache.avalon.framework.service.ServiceManager; 33 import org.apache.avalon.framework.service.ServiceSelector; 34 import org.apache.avalon.framework.thread.ThreadSafe; 35 36 import org.apache.cocoon.Constants; 37 import org.apache.cocoon.ProcessingException; 38 import org.apache.cocoon.acting.AbstractComplementaryConfigurableAction; 39 import org.apache.cocoon.components.modules.database.AutoIncrementModule; 40 import org.apache.cocoon.components.modules.input.InputModule; 41 import org.apache.cocoon.components.modules.output.OutputModule; 42 import org.apache.cocoon.environment.Redirector; 43 import org.apache.cocoon.environment.SourceResolver; 44 import org.apache.cocoon.util.HashMap; 45 import org.apache.cocoon.util.JDBCTypeConversions; 46 import org.apache.commons.lang.BooleanUtils; 47 48 96 public abstract class DatabaseAction extends AbstractComplementaryConfigurableAction implements Configurable, Disposable, ThreadSafe { 97 98 102 static final Integer MODE_AUTOINCR = new Integer (0); 103 static final Integer MODE_OTHERS = new Integer (1); 104 static final Integer MODE_OUTPUT = new Integer (2); 105 106 static final String ATTRIBUTE_KEY = "org.apache.cocoon.action.modular.DatabaseAction.outputModeName"; 107 108 static final String inputHint = "request-param"; static final String outputHint = "request-attr"; static final String databaseHint = "manual"; 113 static final String INPUT_MODULE_SELECTOR = InputModule.ROLE + "Selector"; 114 static final String OUTPUT_MODULE_SELECTOR = OutputModule.ROLE + "Selector"; 115 static final String DATABASE_MODULE_SELECTOR = AutoIncrementModule.ROLE + "Selector"; 116 117 118 122 protected ServiceSelector dbselector; 123 protected Map defaultModeNames = new HashMap( 3 ); 124 protected final HashMap cachedQueryData = new HashMap(); 125 protected String pathSeparator = "."; 126 protected int firstRow = 0; 127 protected boolean failOnEmpty = true; 128 129 133 136 protected static class Column { 137 boolean isKey = false; 138 boolean isSet = false; 139 boolean isAutoIncrement = false; 140 String mode = null; 141 Configuration modeConf = null; 142 Configuration columnConf = null; 143 } 144 145 149 protected static class CacheHelper { 150 153 public String queryString = null; 154 158 public int setMaster = -1; 159 public boolean isSet = false; 160 public int noOfKeys = 0; 161 public Column[] columns = null; 162 163 public CacheHelper( int cols ) { 164 this(0,cols); 165 } 166 167 public CacheHelper( int keys, int cols ) { 168 noOfKeys = keys; 169 columns = new Column[cols]; 170 for ( int i=0; i<cols; i++ ) { 171 columns[i] = new Column(); 172 } 173 } 174 } 175 176 181 protected static class LookUpKey { 182 public Configuration tableConf = null; 183 public Map modeTypes = null; 184 185 public LookUpKey( Configuration tableConf, Map modeTypes ) { 186 this.tableConf = tableConf; 187 this.modeTypes = modeTypes; 188 } 189 190 193 public boolean equals(Object obj) { 194 boolean result = false; 195 if (obj != null && obj instanceof LookUpKey) { 196 LookUpKey luk = (LookUpKey) obj; 197 result = true; 198 result = result && (luk.tableConf == null ? 199 this.tableConf == null : luk.tableConf.equals(this.tableConf)); 200 result = result && (luk.modeTypes == null ? 201 this.modeTypes == null : luk.modeTypes.equals(this.modeTypes)); 202 } 203 204 return result; 205 } 206 207 210 public int hashCode() { 211 return (this.tableConf != null ? 212 this.tableConf.hashCode() : 213 (this.modeTypes != null ? this.modeTypes.hashCode() : super.hashCode())); 214 } 215 } 216 217 public void configure(Configuration conf) throws ConfigurationException { 225 super.configure(conf); 226 if (this.settings != null) { 227 this.defaultModeNames.put(MODE_OTHERS, this.settings.get("input", inputHint)); 228 this.defaultModeNames.put(MODE_OUTPUT, this.settings.get("output", outputHint)); 229 this.defaultModeNames.put(MODE_AUTOINCR, this.settings.get("autoincrement", databaseHint)); 230 this.pathSeparator = (String )this.settings.get("path-separator", this.pathSeparator); 231 String tmp = (String )this.settings.get("first-row",null); 232 if (tmp != null) { 233 try { 234 this.firstRow = Integer.parseInt(tmp); 235 } catch (NumberFormatException nfe) { 236 if (getLogger().isWarnEnabled()) 237 getLogger().warn("problem parsing first row option "+tmp+" using default instead."); 238 } 239 } 240 tmp = (String ) this.settings.get("fail-on-empty",String.valueOf(this.failOnEmpty)); 241 this.failOnEmpty = BooleanUtils.toBoolean(tmp); 242 } 243 } 244 245 249 252 public void service(ServiceManager manager) throws ServiceException { 253 super.service(manager); 254 this.dbselector = (ServiceSelector) manager.lookup(DataSourceComponent.ROLE + "Selector"); 255 } 256 257 260 public void dispose() { 261 this.manager.release(dbselector); 262 } 263 264 268 271 protected DataSourceComponent getDataSource( Configuration conf, Parameters parameters ) 272 throws ServiceException { 273 274 String sourceName = parameters.getParameter( "connection", (String ) settings.get( "connection" ) ); 275 if ( sourceName == null ) { 276 Configuration dsn = conf.getChild("connection"); 277 return (DataSourceComponent) this.dbselector.select(dsn.getValue("")); 278 } else { 279 if (getLogger().isDebugEnabled()) 280 getLogger().debug("Using datasource: "+sourceName); 281 return (DataSourceComponent) this.dbselector.select(sourceName); 282 } 283 } 284 285 288 protected final boolean isLargeObject (String type) { 289 if ("ascii".equals(type)) return true; 290 if ("binary".equals(type)) return true; 291 if ("image".equals(type)) return true; 292 return false; 293 } 294 295 299 protected void setOutputAttribute(Map objectModel, String outputMode, String key, Object value) { 300 301 ServiceSelector outputSelector = null; 302 OutputModule output = null; 303 try { 304 outputSelector = (ServiceSelector) this.manager.lookup(OUTPUT_MODULE_SELECTOR); 305 if (outputMode != null && outputSelector != null && outputSelector.isSelectable(outputMode)) { 306 output = (OutputModule) outputSelector.select(outputMode); 307 } 308 if (output != null) { 309 output.setAttribute(null, objectModel, key, value); 310 } else if (getLogger().isWarnEnabled()) { 311 getLogger().warn("Could not select output mode " + outputMode); 312 } 313 } catch (Exception e) { 314 if (getLogger().isWarnEnabled()) { 315 getLogger().warn("Could not select output mode " + outputMode + ":" + e.getMessage()); 316 } 317 } finally { 318 if (outputSelector != null) { 319 if (output != null) 320 outputSelector.release(output); 321 this.manager.release(outputSelector); 322 } 323 } 324 } 325 326 334 protected int processTable( Configuration table, Connection conn, Map objectModel, 335 Map results, Map modeTypes ) 336 throws SQLException , ConfigurationException, Exception { 337 338 PreparedStatement statement = null; 339 int rows = 0; 340 try { 341 LookUpKey luk = new LookUpKey(table, modeTypes); 342 CacheHelper queryData = null; 343 344 if (getLogger().isDebugEnabled()) 345 getLogger().debug("modeTypes : "+ modeTypes); 346 347 synchronized (this.cachedQueryData) { 352 queryData = (CacheHelper) this.cachedQueryData.get(luk,null); 353 if (queryData == null) { 354 queryData = this.getQuery( table, modeTypes, defaultModeNames ); 355 this.cachedQueryData.put(luk,queryData); 356 } 357 } 358 359 if (getLogger().isDebugEnabled()) 360 getLogger().debug("query: "+queryData.queryString); 361 statement = conn.prepareStatement(queryData.queryString); 362 363 Object [][] columnValues = this.getColumnValues( table, queryData, objectModel ); 364 365 int setLength = 1; 366 if ( queryData.isSet ) { 367 if ( columnValues[ queryData.setMaster ] != null ) { 368 setLength = columnValues[ queryData.setMaster ].length; 369 } else { 370 setLength = 0; 371 } 372 } 373 374 for ( int rowIndex = 0; rowIndex < setLength; rowIndex++ ) { 375 if (getLogger().isDebugEnabled()) { 376 getLogger().debug( "====> row no. " + rowIndex ); 377 } 378 rows += processRow( objectModel, conn, statement, (String ) modeTypes.get(MODE_OUTPUT), table, queryData, columnValues, rowIndex, results ); 379 } 380 } finally { 381 try { 382 if (statement != null) { 383 statement.close(); 384 } 385 } catch (SQLException e) {} 386 } 387 return rows; 388 } 389 390 399 protected Configuration getMode( Configuration conf, String type ) 400 throws ConfigurationException { 401 402 String modeAll = "all"; 403 Configuration[] modes = conf.getChildren("mode"); 404 Configuration modeConfig = null; 405 406 for ( int i=0; i<modes.length; i++ ) { 407 String modeType = modes[i].getAttribute("type", "others"); 408 if ( modeType.equals(type) || modeType.equals(modeAll)) { 409 if (getLogger().isDebugEnabled()) 410 getLogger().debug("requested mode was \""+type+"\" returning \""+modeType+"\""); 411 modeConfig = modes[i]; 412 break; 413 } 414 } 415 return modeConfig; 416 } 417 418 421 protected String getOutputName ( Configuration tableConf, Configuration columnConf ) { 422 423 return getOutputName( tableConf, columnConf, -1 ); 424 } 425 426 436 protected String getOutputName ( Configuration tableConf, Configuration columnConf, int rowIndex ) { 437 438 if ( rowIndex != -1 && this.settings.containsKey("append-row") && 439 (this.settings.get("append-row").toString().equalsIgnoreCase("false") || 440 this.settings.get("append-row").toString().equalsIgnoreCase("0")) ) { 441 rowIndex = -1; 442 } else { 443 rowIndex = rowIndex + this.firstRow; 444 } 445 if ( this.settings.containsKey("append-table-name") && 446 (this.settings.get("append-table-name").toString().equalsIgnoreCase("false") || 447 this.settings.get("append-table-name").toString().equalsIgnoreCase("0")) ) 448 { 449 return ( columnConf.getAttribute("name",null) 450 + ( rowIndex == -1 ? "" : "[" + rowIndex + "]" ) ); 451 } else { 452 return ( tableConf.getAttribute("alias", tableConf.getAttribute("name", null) ) 453 + this.pathSeparator + columnConf.getAttribute("name",null) 454 + ( rowIndex == -1 ? "" : "[" + rowIndex + "]" ) ); 455 } 456 } 457 458 467 protected Object [] getColumnValue(Configuration tableConf, Column column, Map objectModel) 468 throws ConfigurationException, ServiceException { 469 470 if (column.isAutoIncrement) { 471 return new Object [1]; 472 } else { 473 Object [] values; 474 String cname = getOutputName( tableConf, column.columnConf ); 475 476 ServiceSelector inputSelector = null; 478 InputModule input = null; 479 try { 480 inputSelector = (ServiceSelector) this.manager.lookup(INPUT_MODULE_SELECTOR); 481 if (column.mode != null && inputSelector != null && inputSelector.isSelectable(column.mode)){ 482 input = (InputModule) inputSelector.select(column.mode); 483 } 484 485 if (column.isSet) { 486 if (getLogger().isDebugEnabled()) { 487 getLogger().debug( "Trying to set column " + cname + " from " + column.mode + " using getAttributeValues method"); 488 } 489 values = input.getAttributeValues( cname, column.modeConf, objectModel ); 490 } else { 491 if (getLogger().isDebugEnabled()) { 492 getLogger().debug( "Trying to set column " + cname + " from " + column.mode + " using getAttribute method"); 493 } 494 values = new Object [1]; 495 values[0] = input.getAttribute( cname, column.modeConf, objectModel ); 496 } 497 498 if (values != null) { 499 for ( int i = 0; i < values.length; i++ ) { 500 if (getLogger().isDebugEnabled()) { 501 getLogger().debug( "Setting column " + cname + " [" + i + "] " + values[i] ); 502 } 503 } 504 } 505 } finally { 506 if (inputSelector != null) { 507 if (input != null) { 508 inputSelector.release(input); 509 } 510 this.manager.release(inputSelector); 511 } 512 } 513 return values; 514 } 515 } 516 517 520 protected void fillModes ( Configuration[] conf, boolean isKey, Map defaultModeNames, 521 Map modeTypes, CacheHelper set ) 522 throws ConfigurationException { 523 524 String setMode = null; 525 int offset = (isKey ? 0: set.noOfKeys); 526 527 for (int i = offset; i < conf.length + offset; i++) { 528 if (getLogger().isDebugEnabled()) { 529 getLogger().debug("i=" + i); 530 } 531 set.columns[i].columnConf = conf[ i - offset ]; 532 set.columns[i].isSet = false; 533 set.columns[i].isKey = isKey; 534 set.columns[i].isAutoIncrement = false; 535 if (isKey & this.honourAutoIncrement()) { 536 set.columns[i].isAutoIncrement = set.columns[i].columnConf.getAttributeAsBoolean("autoincrement",false); 537 } 538 set.columns[i].modeConf = getMode(set.columns[i].columnConf, 539 selectMode(set.columns[i].isAutoIncrement, modeTypes)); 540 set.columns[i].mode = (set.columns[i].modeConf != null ? 541 set.columns[i].modeConf.getAttribute("name", selectMode(isKey, defaultModeNames)) : 542 selectMode(isKey, defaultModeNames)); 543 setMode = set.columns[i].columnConf.getAttribute("set", null); if (setMode == null && set.columns[i].modeConf != null) { 546 setMode = set.columns[i].modeConf.getAttribute("set", null); 548 } 549 if (setMode != null) { 550 set.columns[i].isSet = true; 551 set.isSet = true; 552 if (setMode.equals("master")) { 553 set.setMaster = i; 554 } 555 } 556 } 557 } 558 559 564 protected void setOutput( Map objectModel, String outputMode, Map results, 565 Configuration table, Configuration column, int rowIndex, Object value ) { 566 567 String param = this.getOutputName( table, column, rowIndex ); 568 if (getLogger().isDebugEnabled()) { 569 getLogger().debug( "Setting column " + param + " to " + value ); 570 } 571 this.setOutputAttribute(objectModel, outputMode, param, value); 572 if (results != null) { 573 results.put( param, String.valueOf( value ) ); 574 } 575 } 576 577 581 protected void setColumn (PreparedStatement statement, int position, Configuration entry, Object value) throws Exception { 582 JDBCTypeConversions.setColumn(statement, position, value, 583 (Integer )JDBCTypeConversions.typeConstants.get(entry.getAttribute("type"))); 584 } 585 586 593 protected void setColumn (Map objectModel, String outputMode, Map results, 594 Configuration table, Configuration column, int rowIndex, 595 Object value, PreparedStatement statement, int position) throws Exception { 596 597 if (results != null) { 598 this.setOutput(objectModel, outputMode, results, table, column, rowIndex, value); 599 } 600 this.setColumn( statement, position, column, value ); 601 } 602 603 607 612 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, 613 String source, Parameters param) throws Exception { 614 615 DataSourceComponent datasource = null; 616 Connection conn = null; 617 Map results = new HashMap(); 618 int rows = 0; 619 boolean failed = false; 620 621 boolean reloadable = Constants.DESCRIPTOR_RELOADABLE_DEFAULT; 623 624 String outputMode = param.getParameter("output", (String ) defaultModeNames.get(MODE_OUTPUT)); 627 628 if (this.settings.containsKey("reloadable")) { 629 reloadable = Boolean.valueOf((String ) this.settings.get("reloadable")).booleanValue(); 630 } 631 try { 633 Configuration conf = 634 this.getConfiguration(param.getParameter("descriptor", (String ) this.settings.get("descriptor")), 635 resolver, 636 param.getParameterAsBoolean("reloadable",reloadable)); 637 638 datasource = this.getDataSource(conf, param); 640 conn = datasource.getConnection(); 641 if (conn.getAutoCommit() == true) { 642 try { 643 conn.setAutoCommit(false); 644 } catch (Exception ex) { 645 String tmp = param.getParameter("use-transactions",(String ) this.settings.get("use-transactions",null)); 646 if (tmp != null && (tmp.equalsIgnoreCase("no") || tmp.equalsIgnoreCase("false") || tmp.equalsIgnoreCase("0"))) { 647 if (getLogger().isErrorEnabled()) 648 getLogger().error("This DB connection does not support transactions. If you want to risk your data's integrity by continuing nonetheless set parameter \"use-transactions\" to \"no\"."); 649 throw ex; 650 } 651 } 652 } 653 654 Configuration[] tables = conf.getChildren("table"); 656 String tablesetname = param.getParameter("table-set", (String ) this.settings.get("table-set")); 657 658 Map modeTypes = null; 659 660 if (tablesetname == null) { 661 modeTypes = new HashMap(6); 662 modeTypes.put( MODE_AUTOINCR, "autoincr" ); 663 modeTypes.put( MODE_OTHERS, "others" ); 664 modeTypes.put( MODE_OUTPUT, outputMode ); 665 for (int i = 0; i < tables.length; i++) { 666 rows += processTable(tables[i], conn, objectModel, results, modeTypes); 667 } 668 } else { 669 671 Map tableIndex = new HashMap(2*tables.length); 673 String tableName = null; 674 Object result = null; 675 for (int i=0; i<tables.length; i++) { 676 tableName = tables[i].getAttribute("alias",tables[i].getAttribute("name","")); 677 result = tableIndex.put(tableName,new Integer (i)); 678 if (result != null) { 679 throw new IOException ("Duplicate table entry for "+tableName+ |