Save This Page
Home » cocoon-2.1.11-src » org.apache » cocoon » acting » modular » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    * 
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    * 
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   
   18   package org.apache.cocoon.acting.modular;
   19   
   20   import java.io.IOException;
   21   import java.sql.Connection;
   22   import java.sql.PreparedStatement;
   23   import java.sql.SQLException;
   24   import java.util.Map;
   25   
   26   import org.apache.avalon.excalibur.datasource.DataSourceComponent;
   27   import org.apache.avalon.framework.activity.Disposable;
   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   /**
   49    * Abstract action for common function needed by database actions.
   50    * The difference to the other Database*Actions is, that the actions
   51    * in this package use additional components ("modules") for reading
   52    * and writing parameters. In addition the descriptor format has
   53    * changed to accomodate the new features.
   54    *
   55    * <p>This action is heavily based upon the original DatabaseAddActions.</p>
   56    *
   57    * <p>Modes have to be configured in cocoon.xconf. Mode names from
   58    * descriptor.xml file are looked up in the component service. Default
   59    * mode names can only be set during action setup. </p>
   60    *
   61    * <p>The number of affected rows is returned to the sitemap with the
   62    * "row-count" parameter if at least one row was affected.</p>
   63    *
   64    * <p>All known column types can be found in 
   65    * {@link org.apache.cocoon.util.JDBCTypeConversions JDBCTypeConversions}.</p>
   66    *
   67    * <table>
   68    * <tr><td colspan="2">Configuration options (setup):</td></tr>
   69    * <tr><td>input            </td><td>default mode name for reading values (request-param)</td></tr>
   70    * <tr><td>autoincrement    </td><td>default mode name for obtaining values from autoincrement columns (auto)</td></tr>
   71    * <tr><td>append-row       </td><td>append row number in square brackets to column name for output (yes)</td></tr>
   72    * <tr><td>append-table-name</td><td>add table name to column name for both in- and output (yes)</td></tr>
   73    * <tr><td>first-row        </td><td>row index of first row (0)</td></tr>
   74    * <tr><td>path-separator   </td><td>string to separate table name from column name (.)</td></tr>
   75    * </table>
   76    *
   77    * <table>
   78    * <tr><td colspan="2">Configuration options (setup and per invocation):</td></tr>
   79    * <tr><td>throw-exception  </td><td>throw an exception when an error occurs (default: false)</td></tr>
   80    * <tr><td>descriptor       </td><td>file containing database description</td></tr>
   81    * <tr><td>table-set        </td><td>table-set name to work with         </td></tr>
   82    * <tr><td>output           </td><td>mode name for writing values (request-attr)</td></tr>
   83    * <tr><td>reloadable       </td><td>dynamically reload descriptor file if change is detected</td></tr>
   84    * <tr><td>use-transactions </td><td>defaults to yes</td></tr>
   85    * <tr><td>connection       </td><td>configured datasource connection to use (overrides value from descriptor file)</td></tr>
   86    * <tr><td>fail-on-empty    </td><td>(boolean) fail is statement affected zero rows (true)</td></tr>
   87    * </table>
   88    *
   89    * @author <a href="mailto:haul@apache.org">Christian Haul</a>
   90    * @version $Id: DatabaseAction.java 433543 2006-08-22 06:22:54Z crossley $
   91    * @see org.apache.cocoon.components.modules.input
   92    * @see org.apache.cocoon.components.modules.output
   93    * @see org.apache.cocoon.components.modules.database
   94    * @see org.apache.cocoon.util.JDBCTypeConversions
   95    */
   96   public abstract class DatabaseAction extends AbstractComplementaryConfigurableAction implements Disposable, ThreadSafe {
   97   
   98       // ========================================================================
   99       // constants
  100       // ========================================================================
  101   
  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       // These can be overidden from cocoon.xconf
  109       static final String inputHint = "request-param"; // default to request parameters
  110       static final String outputHint = "request-attr"; // default to request attributes
  111       static final String databaseHint = "manual"; // default to manual auto increments
  112   
  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       // ========================================================================
  119       // instance vars
  120       // ========================================================================
  121   
  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       // ========================================================================
  130       // inner helper classes
  131       // ========================================================================
  132   
  133       /**
  134        * Structure that takes all processed data for one column.
  135        */
  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       /**
  146        * Structure that takes all processed data for a table depending
  147        * on current default modes
  148        */
  149       protected static class CacheHelper {
  150           /**
  151            * Generated query string
  152            */
  153           public String queryString = null;
  154           /**
  155            * if a set is used, column number which is used to determine
  156            * the number of rows to insert.
  157            */
  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       /**
  177        * Structure that takes up both current mode types for database
  178        * operations and table configuration data. Used to access parsed
  179        * configuration data.
  180        */
  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   		/* (non-Javadoc)
  191   		 * @see java.lang.Object#equals(java.lang.Object)
  192   		 */
  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   		/* (non-Javadoc)
  208   		 * @see java.lang.Object#hashCode()
  209   		 */
  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       // set up default modes
  218       // <input/>
  219       // <output/>
  220       // <autoincrement/>
  221       //
  222       // all other modes need to be declared in cocoon.xconf
  223       // no need to declare them per action (anymore!)
  224       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       // ========================================================================
  246       // Avalon methods
  247       // ========================================================================
  248   
  249       /**
  250        * Compose the Actions so that we can select our databases.
  251        */
  252       public void service(ServiceManager manager) throws ServiceException {
  253           super.service(manager);
  254           this.dbselector = (ServiceSelector) manager.lookup(DataSourceComponent.ROLE + "Selector");
  255       }
  256   
  257       /**
  258        *  dispose
  259        */
  260       public void dispose() {
  261           this.manager.release(dbselector);
  262       }
  263   
  264       // ========================================================================
  265       // protected utility methods
  266       // ========================================================================
  267   
  268       /**
  269        * Get the Datasource we need.
  270        */
  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       /**
  286        * Return whether a type is a Large Object (BLOB/CLOB).
  287        */
  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       /**
  296        * Store a key/value pair in the output attributes. We prefix the key
  297        * with the name of this class to prevent potential name collisions.
  298        */
  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       /**
  327        * Inserts a row or a set of rows into the given table based on the
  328        * request parameters
  329        *
  330        * @param table the table's configuration
  331        * @param conn the database connection
  332        * @param objectModel the objectModel
  333        */
  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               // get cached data
  348               // synchronize complete block since we don't want 100s of threads
  349               // generating the same cached data set. In the long run all data
  350               // is cached anyways so this won't cost much.
  351               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       /**
  391        * Choose a mode configuration based on its name.
  392        * @param conf Configuration (i.e. a column's configuration) that might have
  393        * several children configurations named "mode".
  394        * @param type desired type (i.e. every mode has a type
  395        * attribute), find the first mode that has a compatible type.
  396        * Special mode "all" matches all queried types.
  397        * @return configuration that has desired type or type "all" or null.
  398        */
  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       /**
  419        * compose name for output a long the lines of "table.column"
  420        */
  421       protected String getOutputName ( Configuration tableConf, Configuration columnConf ) {
  422   
  423           return getOutputName( tableConf, columnConf, -1 );
  424       }
  425   
  426       /**
  427        * compose name for output a long the lines of "table.column[row]" or
  428        * "table.column" if rowIndex is -1.
  429        * If the section of the sitemap corresponding to the action contains
  430        * <append-table-name>false</append-table-name>
  431        * the name for output is "column[row]"
  432        * If the section of the sitemap corresponding to the action contains
  433        * <append-row>false</append-row>
  434        * the name for output is "column"
  435        */
  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       /*
  459        * Read all values for a column from an InputModule
  460        *
  461        * If the given column is an autoincrement column, an empty array
  462        * is returned, otherwise if it is part of a set, all available
  463        * values are fetched, or only the first one if it is not part of
  464        * a set.
  465        *
  466        */
  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               // obtain input module and read values
  477               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       /**
  518        * Setup parsed attribute configuration object
  519        */
  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               // Determine set mode for a whole column ...
  544               setMode = set.columns[i].columnConf.getAttribute("set", null);  // master vs slave vs null
  545               if (setMode == null && set.columns[i].modeConf != null) {
  546                   // ... or for each mode individually
  547                   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       /**
  560        * create a unique name using the getOutputName method and write
  561        * the value to the output module and the results map if present.
  562        *
  563        */
  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       /**
  578        * set a column in a statement using the appropriate JDBC setXXX method.
  579        *
  580        */
  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       /**
  587        * set a column in a statement using the appropriate JDBC setXXX
  588        * method and propagate the value to the output module and results
  589        * map if present. Effectively combines calls to setColumn and
  590        * setOutput.
  591        *
  592        */
  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       // ========================================================================
  604       // main method
  605       // ========================================================================
  606   
  607       /**
  608        * Add a record to the database.  This action assumes that
  609        * the file referenced by the "descriptor" parameter conforms
  610        * to the AbstractDatabaseAction specifications.
  611        */
  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           // read global parameter settings
  622           boolean reloadable = Constants.DESCRIPTOR_RELOADABLE_DEFAULT;
  623   
  624           // call specific default modes apart from output mode are not supported
  625           // set request attribute
  626           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           // read local parameter settings
  632           try {
  633               Configuration conf =
  634                   this.getConfiguration(param.getParameter("descriptor", (String) this.settings.get("descriptor")),
  635                                         resolver,
  636                                         param.getParameterAsBoolean("reloadable",reloadable));
  637   
  638               // get database connection and try to turn off autocommit
  639               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               // find tables to work with
  655               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                   // new set based behaviour
  670   
  671                   // create index for table names / aliases
  672                   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+" at positions "+result+" and "+i);
  680                       }
  681                   }
  682   
  683                   Configuration[] tablesets = conf.getChildren("table-set");
  684                   String setname = null;
  685                   boolean found = false;
  686   
  687                   // find tables contained in tableset
  688                   int j = 0;
  689                   for (j = 0; j < tablesets.length; j++) {
  690                       setname = tablesets[j].getAttribute ("name", "");
  691                       if (tablesetname.trim().equals (setname.trim ())) {
  692                           found = true;
  693                           break;
  694                       }
  695                   }
  696                   if (!found) {
  697                       throw new IOException(" given set " + tablesetname + " does not exists in a description file.");
  698                   }
  699   
  700                   Configuration[] set = tablesets[j].getChildren("table");
  701   
  702                   for (int i = 0; i < set.length; i++) {
  703                       // look for alternative mode types
  704                       modeTypes = new HashMap(6);
  705                       modeTypes.put( MODE_AUTOINCR, set[i].getAttribute( "autoincr-mode", "autoincr" ) );
  706                       modeTypes.put( MODE_OTHERS, set[i].getAttribute( "others-mode",   "others" ) );
  707                       modeTypes.put( MODE_OUTPUT, outputMode );
  708                       tableName=set[i].getAttribute("name","");
  709                       if (tableIndex.containsKey(tableName)) {
  710                           j = ((Integer)tableIndex.get(tableName)).intValue();
  711                           rows += processTable( tables[j], conn, objectModel, results, modeTypes );
  712                       } else {
  713                           throw new IOException(" given table " + tableName + " does not exists in a description file.");
  714                       }
  715                   }
  716               }
  717   
  718               if (conn.getAutoCommit() == false) {
  719                   conn.commit();
  720               }
  721   
  722               // obtain output mode module and rollback output
  723               ServiceSelector outputSelector = null;
  724               OutputModule output = null;
  725               try {
  726                   outputSelector = (ServiceSelector) this.manager.lookup(OUTPUT_MODULE_SELECTOR);
  727                   if (outputMode != null && outputSelector != null && outputSelector.isSelectable(outputMode)){
  728                       output = (OutputModule) outputSelector.select(outputMode);
  729                   }
  730                   if (output != null) {
  731                       output.commit(null, objectModel);
  732                   } else if (getLogger().isWarnEnabled()) {
  733                       getLogger().warn("Could not select output mode " + outputMode);
  734                   }
  735               } catch (ServiceException e) {
  736                   if (getLogger().isWarnEnabled()) {
  737                       getLogger().warn("Could not select output mode " + outputMode + ":" + e.getMessage());
  738                   }
  739               } finally {
  740                   if (outputSelector != null) {
  741                       if (output != null) {
  742                           outputSelector.release(output);
  743                       }
  744                       this.manager.release(outputSelector);
  745                   }
  746               }
  747           } catch (Exception e) {
  748               failed = true;
  749               if ( conn != null ) {
  750                   try {
  751                       if (getLogger().isDebugEnabled()) {
  752                           getLogger().debug( "Rolling back transaction. Caused by " + e.getMessage() );
  753                           e.printStackTrace();
  754                       }
  755                       conn.rollback();
  756                       results = null;
  757   
  758                       // obtain output mode module and commit output
  759                       ServiceSelector outputSelector = null;
  760                       OutputModule output = null;
  761                       try {
  762                           outputSelector = (ServiceSelector) this.manager.lookup(OUTPUT_MODULE_SELECTOR);
  763                           if (outputMode != null && outputSelector != null && outputSelector.isSelectable(outputMode)){
  764                               output = (OutputModule) outputSelector.select(outputMode);
  765                           }
  766                           if (output != null) {
  767                               output.rollback( null, objectModel, e);
  768                           } else if (getLogger().isWarnEnabled()) {
  769                               getLogger().warn("Could not select output mode " + outputMode);
  770                           }
  771                       } catch (ServiceException e2) {
  772                           if (getLogger().isWarnEnabled()) {
  773                               getLogger().warn("Could not select output mode " + outputMode + ":" + e2.getMessage());
  774                           }
  775                       } finally {
  776                           if (outputSelector != null) {
  777                               if (output != null) {
  778                                   outputSelector.release(output);
  779                               }
  780                               this.manager.release(outputSelector);
  781                           }
  782                       }
  783                   } catch (SQLException se) {
  784                       if (getLogger().isDebugEnabled())
  785                           getLogger().debug("There was an error rolling back the transaction", se);
  786                   }
  787               }
  788   
  789               //throw new ProcessingException("Could not add record :position = " + currentIndex, e);
  790   
  791               // don't throw an exception, an error has been signalled, that should suffice
  792   
  793               String throwException = (String) this.settings.get( "throw-exception",
  794                                                                   param.getParameter( "throw-exception", null ) );
  795               if ( throwException != null && BooleanUtils.toBoolean(throwException)) {
  796                   throw new ProcessingException("Cannot process the requested SQL statement ",e);
  797               }
  798           } finally {
  799               if (conn != null) {
  800                   try {
  801                       conn.close();
  802                   } catch (SQLException sqe) {
  803                       getLogger().warn("There was an error closing the datasource", sqe);
  804                   }
  805               }
  806   
  807               if (datasource != null)
  808                   this.dbselector.release(datasource);
  809           }
  810           if (results != null) {
  811               if (rows>0 || (!failed && !this.failOnEmpty)) {
  812                       results.put("row-count",new Integer(rows)); 
  813               } else {    
  814                   results = null;
  815               }
  816           } else {
  817               if (rows>0) {
  818                   results = new HashMap(1);
  819                   results.put("row-count",new Integer(rows));
  820               }
  821           }
  822   
  823           return results; // (results == null? results : Collections.unmodifiableMap(results));
  824       }
  825   
  826       // ========================================================================
  827       // abstract methods
  828       // ========================================================================
  829   
  830       /**
  831        * set all necessary ?s and execute the query
  832        * return number of rows processed
  833        *
  834        * This method is intended to be overridden by classes that
  835        * implement other operations e.g. delete
  836        */
  837       protected abstract int processRow( Map objectModel, Connection conn, PreparedStatement statement, String outputMode,
  838                                          Configuration table, CacheHelper queryData, Object[][] columnValues,
  839                                          int rowIndex, Map results )
  840           throws SQLException, ConfigurationException, Exception;
  841   
  842       /**
  843        * determine which mode to use as default mode
  844        *
  845        * This method is intended to be overridden by classes that
  846        * implement other operations e.g. delete
  847        */
  848       protected abstract String selectMode( boolean isAutoIncrement, Map modes );
  849   
  850       /**
  851        * determine whether autoincrement columns should be honoured by
  852        * this operation. This is usually snsible only for INSERTs.
  853        *
  854        * This method is intended to be overridden by classes that
  855        * implement other operations e.g. delete
  856        */
  857       protected abstract boolean honourAutoIncrement();
  858   
  859       /**
  860        * Fetch all values for all columns that are needed to do the
  861        * database operation.
  862        *
  863        * This method is intended to be overridden by classes that
  864        * implement other operations e.g. delete
  865        */
  866       abstract Object[][] getColumnValues( Configuration tableConf, CacheHelper queryData, Map objectModel )
  867           throws ConfigurationException, ServiceException;
  868   
  869       /**
  870        * Get the String representation of the PreparedStatement.  This is
  871        * mapped to the Configuration object itself, so if it doesn't exist,
  872        * it will be created.
  873        *
  874        * This method is intended to be overridden by classes that
  875        * implement other operations e.g. delete
  876        *
  877        * @param table the table's configuration object
  878        * @return the insert query as a string
  879        */
  880       protected abstract CacheHelper getQuery( Configuration table, Map modeTypes, Map defaultModeNames )
  881           throws ConfigurationException, ServiceException;
  882   
  883   }

Save This Page
Home » cocoon-2.1.11-src » org.apache » cocoon » acting » modular » [javadoc | source]