0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.cocoon.acting.modular;
0019:
0020: import java.io.IOException;
0021: import java.sql.Connection;
0022: import java.sql.PreparedStatement;
0023: import java.sql.SQLException;
0024: import java.util.Map;
0025:
0026: import org.apache.avalon.excalibur.datasource.DataSourceComponent;
0027: import org.apache.avalon.framework.activity.Disposable;
0028: import org.apache.avalon.framework.configuration.Configuration;
0029: import org.apache.avalon.framework.configuration.ConfigurationException;
0030: import org.apache.avalon.framework.parameters.Parameters;
0031: import org.apache.avalon.framework.service.ServiceException;
0032: import org.apache.avalon.framework.service.ServiceManager;
0033: import org.apache.avalon.framework.service.ServiceSelector;
0034: import org.apache.avalon.framework.thread.ThreadSafe;
0035:
0036: import org.apache.cocoon.Constants;
0037: import org.apache.cocoon.ProcessingException;
0038: import org.apache.cocoon.acting.AbstractComplementaryConfigurableAction;
0039: import org.apache.cocoon.components.modules.database.AutoIncrementModule;
0040: import org.apache.cocoon.components.modules.input.InputModule;
0041: import org.apache.cocoon.components.modules.output.OutputModule;
0042: import org.apache.cocoon.environment.Redirector;
0043: import org.apache.cocoon.environment.SourceResolver;
0044: import org.apache.cocoon.util.HashMap;
0045: import org.apache.cocoon.util.JDBCTypeConversions;
0046: import org.apache.commons.lang.BooleanUtils;
0047:
0048: /**
0049: * Abstract action for common function needed by database actions.
0050: * The difference to the other Database*Actions is, that the actions
0051: * in this package use additional components ("modules") for reading
0052: * and writing parameters. In addition the descriptor format has
0053: * changed to accomodate the new features.
0054: *
0055: * <p>This action is heavily based upon the original DatabaseAddActions.</p>
0056: *
0057: * <p>Modes have to be configured in cocoon.xconf. Mode names from
0058: * descriptor.xml file are looked up in the component service. Default
0059: * mode names can only be set during action setup. </p>
0060: *
0061: * <p>The number of affected rows is returned to the sitemap with the
0062: * "row-count" parameter if at least one row was affected.</p>
0063: *
0064: * <p>All known column types can be found in
0065: * {@link org.apache.cocoon.util.JDBCTypeConversions JDBCTypeConversions}.</p>
0066: *
0067: * <table>
0068: * <tr><td colspan="2">Configuration options (setup):</td></tr>
0069: * <tr><td>input </td><td>default mode name for reading values (request-param)</td></tr>
0070: * <tr><td>autoincrement </td><td>default mode name for obtaining values from autoincrement columns (auto)</td></tr>
0071: * <tr><td>append-row </td><td>append row number in square brackets to column name for output (yes)</td></tr>
0072: * <tr><td>append-table-name</td><td>add table name to column name for both in- and output (yes)</td></tr>
0073: * <tr><td>first-row </td><td>row index of first row (0)</td></tr>
0074: * <tr><td>path-separator </td><td>string to separate table name from column name (.)</td></tr>
0075: * </table>
0076: *
0077: * <table>
0078: * <tr><td colspan="2">Configuration options (setup and per invocation):</td></tr>
0079: * <tr><td>throw-exception </td><td>throw an exception when an error occurs (default: false)</td></tr>
0080: * <tr><td>descriptor </td><td>file containing database description</td></tr>
0081: * <tr><td>table-set </td><td>table-set name to work with </td></tr>
0082: * <tr><td>output </td><td>mode name for writing values (request-attr)</td></tr>
0083: * <tr><td>reloadable </td><td>dynamically reload descriptor file if change is detected</td></tr>
0084: * <tr><td>use-transactions </td><td>defaults to yes</td></tr>
0085: * <tr><td>connection </td><td>configured datasource connection to use (overrides value from descriptor file)</td></tr>
0086: * <tr><td>fail-on-empty </td><td>(boolean) fail is statement affected zero rows (true)</td></tr>
0087: * </table>
0088: *
0089: * @author <a href="mailto:haul@apache.org">Christian Haul</a>
0090: * @version $Id: DatabaseAction.java 433543 2006-08-22 06:22:54Z crossley $
0091: * @see org.apache.cocoon.components.modules.input
0092: * @see org.apache.cocoon.components.modules.output
0093: * @see org.apache.cocoon.components.modules.database
0094: * @see org.apache.cocoon.util.JDBCTypeConversions
0095: */
0096: public abstract class DatabaseAction extends
0097: AbstractComplementaryConfigurableAction implements Disposable,
0098: ThreadSafe {
0099:
0100: // ========================================================================
0101: // constants
0102: // ========================================================================
0103:
0104: static final Integer MODE_AUTOINCR = new Integer(0);
0105: static final Integer MODE_OTHERS = new Integer(1);
0106: static final Integer MODE_OUTPUT = new Integer(2);
0107:
0108: static final String ATTRIBUTE_KEY = "org.apache.cocoon.action.modular.DatabaseAction.outputModeName";
0109:
0110: // These can be overidden from cocoon.xconf
0111: static final String inputHint = "request-param"; // default to request parameters
0112: static final String outputHint = "request-attr"; // default to request attributes
0113: static final String databaseHint = "manual"; // default to manual auto increments
0114:
0115: static final String INPUT_MODULE_SELECTOR = InputModule.ROLE
0116: + "Selector";
0117: static final String OUTPUT_MODULE_SELECTOR = OutputModule.ROLE
0118: + "Selector";
0119: static final String DATABASE_MODULE_SELECTOR = AutoIncrementModule.ROLE
0120: + "Selector";
0121:
0122: // ========================================================================
0123: // instance vars
0124: // ========================================================================
0125:
0126: protected ServiceSelector dbselector;
0127: protected Map defaultModeNames = new HashMap(3);
0128: protected final HashMap cachedQueryData = new HashMap();
0129: protected String pathSeparator = ".";
0130: protected int firstRow = 0;
0131: protected boolean failOnEmpty = true;
0132:
0133: // ========================================================================
0134: // inner helper classes
0135: // ========================================================================
0136:
0137: /**
0138: * Structure that takes all processed data for one column.
0139: */
0140: protected static class Column {
0141: boolean isKey = false;
0142: boolean isSet = false;
0143: boolean isAutoIncrement = false;
0144: String mode = null;
0145: Configuration modeConf = null;
0146: Configuration columnConf = null;
0147: }
0148:
0149: /**
0150: * Structure that takes all processed data for a table depending
0151: * on current default modes
0152: */
0153: protected static class CacheHelper {
0154: /**
0155: * Generated query string
0156: */
0157: public String queryString = null;
0158: /**
0159: * if a set is used, column number which is used to determine
0160: * the number of rows to insert.
0161: */
0162: public int setMaster = -1;
0163: public boolean isSet = false;
0164: public int noOfKeys = 0;
0165: public Column[] columns = null;
0166:
0167: public CacheHelper(int cols) {
0168: this (0, cols);
0169: }
0170:
0171: public CacheHelper(int keys, int cols) {
0172: noOfKeys = keys;
0173: columns = new Column[cols];
0174: for (int i = 0; i < cols; i++) {
0175: columns[i] = new Column();
0176: }
0177: }
0178: }
0179:
0180: /**
0181: * Structure that takes up both current mode types for database
0182: * operations and table configuration data. Used to access parsed
0183: * configuration data.
0184: */
0185: protected static class LookUpKey {
0186: public Configuration tableConf = null;
0187: public Map modeTypes = null;
0188:
0189: public LookUpKey(Configuration tableConf, Map modeTypes) {
0190: this .tableConf = tableConf;
0191: this .modeTypes = modeTypes;
0192: }
0193:
0194: /* (non-Javadoc)
0195: * @see java.lang.Object#equals(java.lang.Object)
0196: */
0197: public boolean equals(Object obj) {
0198: boolean result = false;
0199: if (obj != null && obj instanceof LookUpKey) {
0200: LookUpKey luk = (LookUpKey) obj;
0201: result = true;
0202: result = result
0203: && (luk.tableConf == null ? this .tableConf == null
0204: : luk.tableConf.equals(this .tableConf));
0205: result = result
0206: && (luk.modeTypes == null ? this .modeTypes == null
0207: : luk.modeTypes.equals(this .modeTypes));
0208: }
0209:
0210: return result;
0211: }
0212:
0213: /* (non-Javadoc)
0214: * @see java.lang.Object#hashCode()
0215: */
0216: public int hashCode() {
0217: return (this .tableConf != null ? this .tableConf.hashCode()
0218: : (this .modeTypes != null ? this .modeTypes
0219: .hashCode() : super .hashCode()));
0220: }
0221: }
0222:
0223: // set up default modes
0224: // <input/>
0225: // <output/>
0226: // <autoincrement/>
0227: //
0228: // all other modes need to be declared in cocoon.xconf
0229: // no need to declare them per action (anymore!)
0230: public void configure(Configuration conf)
0231: throws ConfigurationException {
0232: super .configure(conf);
0233: if (this .settings != null) {
0234: this .defaultModeNames.put(MODE_OTHERS, this .settings.get(
0235: "input", inputHint));
0236: this .defaultModeNames.put(MODE_OUTPUT, this .settings.get(
0237: "output", outputHint));
0238: this .defaultModeNames.put(MODE_AUTOINCR, this .settings.get(
0239: "autoincrement", databaseHint));
0240: this .pathSeparator = (String) this .settings.get(
0241: "path-separator", this .pathSeparator);
0242: String tmp = (String) this .settings.get("first-row", null);
0243: if (tmp != null) {
0244: try {
0245: this .firstRow = Integer.parseInt(tmp);
0246: } catch (NumberFormatException nfe) {
0247: if (getLogger().isWarnEnabled())
0248: getLogger().warn(
0249: "problem parsing first row option "
0250: + tmp
0251: + " using default instead.");
0252: }
0253: }
0254: tmp = (String) this .settings.get("fail-on-empty", String
0255: .valueOf(this .failOnEmpty));
0256: this .failOnEmpty = BooleanUtils.toBoolean(tmp);
0257: }
0258: }
0259:
0260: // ========================================================================
0261: // Avalon methods
0262: // ========================================================================
0263:
0264: /**
0265: * Compose the Actions so that we can select our databases.
0266: */
0267: public void service(ServiceManager manager) throws ServiceException {
0268: super .service(manager);
0269: this .dbselector = (ServiceSelector) manager
0270: .lookup(DataSourceComponent.ROLE + "Selector");
0271: }
0272:
0273: /**
0274: * dispose
0275: */
0276: public void dispose() {
0277: this .manager.release(dbselector);
0278: }
0279:
0280: // ========================================================================
0281: // protected utility methods
0282: // ========================================================================
0283:
0284: /**
0285: * Get the Datasource we need.
0286: */
0287: protected DataSourceComponent getDataSource(Configuration conf,
0288: Parameters parameters) throws ServiceException {
0289:
0290: String sourceName = parameters.getParameter("connection",
0291: (String) settings.get("connection"));
0292: if (sourceName == null) {
0293: Configuration dsn = conf.getChild("connection");
0294: return (DataSourceComponent) this .dbselector.select(dsn
0295: .getValue(""));
0296: } else {
0297: if (getLogger().isDebugEnabled())
0298: getLogger().debug("Using datasource: " + sourceName);
0299: return (DataSourceComponent) this .dbselector
0300: .select(sourceName);
0301: }
0302: }
0303:
0304: /**
0305: * Return whether a type is a Large Object (BLOB/CLOB).
0306: */
0307: protected final boolean isLargeObject(String type) {
0308: if ("ascii".equals(type))
0309: return true;
0310: if ("binary".equals(type))
0311: return true;
0312: if ("image".equals(type))
0313: return true;
0314: return false;
0315: }
0316:
0317: /**
0318: * Store a key/value pair in the output attributes. We prefix the key
0319: * with the name of this class to prevent potential name collisions.
0320: */
0321: protected void setOutputAttribute(Map objectModel,
0322: String outputMode, String key, Object value) {
0323:
0324: ServiceSelector outputSelector = null;
0325: OutputModule output = null;
0326: try {
0327: outputSelector = (ServiceSelector) this .manager
0328: .lookup(OUTPUT_MODULE_SELECTOR);
0329: if (outputMode != null && outputSelector != null
0330: && outputSelector.isSelectable(outputMode)) {
0331: output = (OutputModule) outputSelector
0332: .select(outputMode);
0333: }
0334: if (output != null) {
0335: output.setAttribute(null, objectModel, key, value);
0336: } else if (getLogger().isWarnEnabled()) {
0337: getLogger().warn(
0338: "Could not select output mode " + outputMode);
0339: }
0340: } catch (Exception e) {
0341: if (getLogger().isWarnEnabled()) {
0342: getLogger().warn(
0343: "Could not select output mode " + outputMode
0344: + ":" + e.getMessage());
0345: }
0346: } finally {
0347: if (outputSelector != null) {
0348: if (output != null)
0349: outputSelector.release(output);
0350: this .manager.release(outputSelector);
0351: }
0352: }
0353: }
0354:
0355: /**
0356: * Inserts a row or a set of rows into the given table based on the
0357: * request parameters
0358: *
0359: * @param table the table's configuration
0360: * @param conn the database connection
0361: * @param objectModel the objectModel
0362: */
0363: protected int processTable(Configuration table, Connection conn,
0364: Map objectModel, Map results, Map modeTypes)
0365: throws SQLException, ConfigurationException, Exception {
0366:
0367: PreparedStatement statement = null;
0368: int rows = 0;
0369: try {
0370: LookUpKey luk = new LookUpKey(table, modeTypes);
0371: CacheHelper queryData = null;
0372:
0373: if (getLogger().isDebugEnabled())
0374: getLogger().debug("modeTypes : " + modeTypes);
0375:
0376: // get cached data
0377: // synchronize complete block since we don't want 100s of threads
0378: // generating the same cached data set. In the long run all data
0379: // is cached anyways so this won't cost much.
0380: synchronized (this .cachedQueryData) {
0381: queryData = (CacheHelper) this .cachedQueryData.get(luk,
0382: null);
0383: if (queryData == null) {
0384: queryData = this .getQuery(table, modeTypes,
0385: defaultModeNames);
0386: this .cachedQueryData.put(luk, queryData);
0387: }
0388: }
0389:
0390: if (getLogger().isDebugEnabled())
0391: getLogger().debug("query: " + queryData.queryString);
0392: statement = conn.prepareStatement(queryData.queryString);
0393:
0394: Object[][] columnValues = this .getColumnValues(table,
0395: queryData, objectModel);
0396:
0397: int setLength = 1;
0398: if (queryData.isSet) {
0399: if (columnValues[queryData.setMaster] != null) {
0400: setLength = columnValues[queryData.setMaster].length;
0401: } else {
0402: setLength = 0;
0403: }
0404: }
0405:
0406: for (int rowIndex = 0; rowIndex < setLength; rowIndex++) {
0407: if (getLogger().isDebugEnabled()) {
0408: getLogger().debug("====> row no. " + rowIndex);
0409: }
0410: rows += processRow(objectModel, conn, statement,
0411: (String) modeTypes.get(MODE_OUTPUT), table,
0412: queryData, columnValues, rowIndex, results);
0413: }
0414: } finally {
0415: try {
0416: if (statement != null) {
0417: statement.close();
0418: }
0419: } catch (SQLException e) {
0420: }
0421: }
0422: return rows;
0423: }
0424:
0425: /**
0426: * Choose a mode configuration based on its name.
0427: * @param conf Configuration (i.e. a column's configuration) that might have
0428: * several children configurations named "mode".
0429: * @param type desired type (i.e. every mode has a type
0430: * attribute), find the first mode that has a compatible type.
0431: * Special mode "all" matches all queried types.
0432: * @return configuration that has desired type or type "all" or null.
0433: */
0434: protected Configuration getMode(Configuration conf, String type)
0435: throws ConfigurationException {
0436:
0437: String modeAll = "all";
0438: Configuration[] modes = conf.getChildren("mode");
0439: Configuration modeConfig = null;
0440:
0441: for (int i = 0; i < modes.length; i++) {
0442: String modeType = modes[i].getAttribute("type", "others");
0443: if (modeType.equals(type) || modeType.equals(modeAll)) {
0444: if (getLogger().isDebugEnabled())
0445: getLogger().debug(
0446: "requested mode was \"" + type
0447: + "\" returning \"" + modeType
0448: + "\"");
0449: modeConfig = modes[i];
0450: break;
0451: }
0452: }
0453: return modeConfig;
0454: }
0455:
0456: /**
0457: * compose name for output a long the lines of "table.column"
0458: */
0459: protected String getOutputName(Configuration tableConf,
0460: Configuration columnConf) {
0461:
0462: return getOutputName(tableConf, columnConf, -1);
0463: }
0464:
0465: /**
0466: * compose name for output a long the lines of "table.column[row]" or
0467: * "table.column" if rowIndex is -1.
0468: * If the section of the sitemap corresponding to the action contains
0469: * <append-table-name>false</append-table-name>
0470: * the name for output is "column[row]"
0471: * If the section of the sitemap corresponding to the action contains
0472: * <append-row>false</append-row>
0473: * the name for output is "column"
0474: */
0475: protected String getOutputName(Configuration tableConf,
0476: Configuration columnConf, int rowIndex) {
0477:
0478: if (rowIndex != -1
0479: && this .settings.containsKey("append-row")
0480: && (this .settings.get("append-row").toString()
0481: .equalsIgnoreCase("false") || this .settings
0482: .get("append-row").toString().equalsIgnoreCase(
0483: "0"))) {
0484: rowIndex = -1;
0485: } else {
0486: rowIndex = rowIndex + this .firstRow;
0487: }
0488: if (this .settings.containsKey("append-table-name")
0489: && (this .settings.get("append-table-name").toString()
0490: .equalsIgnoreCase("false") || this .settings
0491: .get("append-table-name").toString()
0492: .equalsIgnoreCase("0"))) {
0493: return (columnConf.getAttribute("name", null) + (rowIndex == -1 ? ""
0494: : "[" + rowIndex + "]"));
0495: } else {
0496: return (tableConf.getAttribute("alias", tableConf
0497: .getAttribute("name", null))
0498: + this .pathSeparator
0499: + columnConf.getAttribute("name", null) + (rowIndex == -1 ? ""
0500: : "[" + rowIndex + "]"));
0501: }
0502: }
0503:
0504: /*
0505: * Read all values for a column from an InputModule
0506: *
0507: * If the given column is an autoincrement column, an empty array
0508: * is returned, otherwise if it is part of a set, all available
0509: * values are fetched, or only the first one if it is not part of
0510: * a set.
0511: *
0512: */
0513: protected Object[] getColumnValue(Configuration tableConf,
0514: Column column, Map objectModel)
0515: throws ConfigurationException, ServiceException {
0516:
0517: if (column.isAutoIncrement) {
0518: return new Object[1];
0519: } else {
0520: Object[] values;
0521: String cname = getOutputName(tableConf, column.columnConf);
0522:
0523: // obtain input module and read values
0524: ServiceSelector inputSelector = null;
0525: InputModule input = null;
0526: try {
0527: inputSelector = (ServiceSelector) this .manager
0528: .lookup(INPUT_MODULE_SELECTOR);
0529: if (column.mode != null && inputSelector != null
0530: && inputSelector.isSelectable(column.mode)) {
0531: input = (InputModule) inputSelector
0532: .select(column.mode);
0533: }
0534:
0535: if (column.isSet) {
0536: if (getLogger().isDebugEnabled()) {
0537: getLogger()
0538: .debug(
0539: "Trying to set column "
0540: + cname
0541: + " from "
0542: + column.mode
0543: + " using getAttributeValues method");
0544: }
0545: values = input.getAttributeValues(cname,
0546: column.modeConf, objectModel);
0547: } else {
0548: if (getLogger().isDebugEnabled()) {
0549: getLogger().debug(
0550: "Trying to set column " + cname
0551: + " from " + column.mode
0552: + " using getAttribute method");
0553: }
0554: values = new Object[1];
0555: values[0] = input.getAttribute(cname,
0556: column.modeConf, objectModel);
0557: }
0558:
0559: if (values != null) {
0560: for (int i = 0; i < values.length; i++) {
0561: if (getLogger().isDebugEnabled()) {
0562: getLogger().debug(
0563: "Setting column " + cname + " ["
0564: + i + "] " + values[i]);
0565: }
0566: }
0567: }
0568: } finally {
0569: if (inputSelector != null) {
0570: if (input != null) {
0571: inputSelector.release(input);
0572: }
0573: this .manager.release(inputSelector);
0574: }
0575: }
0576: return values;
0577: }
0578: }
0579:
0580: /**
0581: * Setup parsed attribute configuration object
0582: */
0583: protected void fillModes(Configuration[] conf, boolean isKey,
0584: Map defaultModeNames, Map modeTypes, CacheHelper set)
0585: throws ConfigurationException {
0586:
0587: String setMode = null;
0588: int offset = (isKey ? 0 : set.noOfKeys);
0589:
0590: for (int i = offset; i < conf.length + offset; i++) {
0591: if (getLogger().isDebugEnabled()) {
0592: getLogger().debug("i=" + i);
0593: }
0594: set.columns[i].columnConf = conf[i - offset];
0595: set.columns[i].isSet = false;
0596: set.columns[i].isKey = isKey;
0597: set.columns[i].isAutoIncrement = false;
0598: if (isKey & this .honourAutoIncrement()) {
0599: set.columns[i].isAutoIncrement = set.columns[i].columnConf
0600: .getAttributeAsBoolean("autoincrement", false);
0601: }
0602: set.columns[i].modeConf = getMode(
0603: set.columns[i].columnConf, selectMode(
0604: set.columns[i].isAutoIncrement, modeTypes));
0605: set.columns[i].mode = (set.columns[i].modeConf != null ? set.columns[i].modeConf
0606: .getAttribute("name", selectMode(isKey,
0607: defaultModeNames))
0608: : selectMode(isKey, defaultModeNames));
0609: // Determine set mode for a whole column ...
0610: setMode = set.columns[i].columnConf.getAttribute("set",
0611: null); // master vs slave vs null
0612: if (setMode == null && set.columns[i].modeConf != null) {
0613: // ... or for each mode individually
0614: setMode = set.columns[i].modeConf.getAttribute("set",
0615: null);
0616: }
0617: if (setMode != null) {
0618: set.columns[i].isSet = true;
0619: set.isSet = true;
0620: if (setMode.equals("master")) {
0621: set.setMaster = i;
0622: }
0623: }
0624: }
0625: }
0626:
0627: /**
0628: * create a unique name using the getOutputName method and write
0629: * the value to the output module and the results map if present.
0630: *
0631: */
0632: protected void setOutput(Map objectModel, String outputMode,
0633: Map results, Configuration table, Configuration column,
0634: int rowIndex, Object value) {
0635:
0636: String param = this .getOutputName(table, column, rowIndex);
0637: if (getLogger().isDebugEnabled()) {
0638: getLogger().debug(
0639: "Setting column " + param + " to " + value);
0640: }
0641: this .setOutputAttribute(objectModel, outputMode, param, value);
0642: if (results != null) {
0643: results.put(param, String.valueOf(value));
0644: }
0645: }
0646:
0647: /**
0648: * set a column in a statement using the appropriate JDBC setXXX method.
0649: *
0650: */
0651: protected void setColumn(PreparedStatement statement, int position,
0652: Configuration entry, Object value) throws Exception {
0653: JDBCTypeConversions.setColumn(statement, position, value,
0654: (Integer) JDBCTypeConversions.typeConstants.get(entry
0655: .getAttribute("type")));
0656: }
0657:
0658: /**
0659: * set a column in a statement using the appropriate JDBC setXXX
0660: * method and propagate the value to the output module and results
0661: * map if present. Effectively combines calls to setColumn and
0662: * setOutput.
0663: *
0664: */
0665: protected void setColumn(Map objectModel, String outputMode,
0666: Map results, Configuration table, Configuration column,
0667: int rowIndex, Object value, PreparedStatement statement,
0668: int position) throws Exception {
0669:
0670: if (results != null) {
0671: this .setOutput(objectModel, outputMode, results, table,
0672: column, rowIndex, value);
0673: }
0674: this .setColumn(statement, position, column, value);
0675: }
0676:
0677: // ========================================================================
0678: // main method
0679: // ========================================================================
0680:
0681: /**
0682: * Add a record to the database. This action assumes that
0683: * the file referenced by the "descriptor" parameter conforms
0684: * to the AbstractDatabaseAction specifications.
0685: */
0686: public Map act(Redirector redirector, SourceResolver resolver,
0687: Map objectModel, String source, Parameters param)
0688: throws Exception {
0689:
0690: DataSourceComponent datasource = null;
0691: Connection conn = null;
0692: Map results = new HashMap();
0693: int rows = 0;
0694: boolean failed = false;
0695:
0696: // read global parameter settings
0697: boolean reloadable = Constants.DESCRIPTOR_RELOADABLE_DEFAULT;
0698:
0699: // call specific default modes apart from output mode are not supported
0700: // set request attribute
0701: String outputMode = param.getParameter("output",
0702: (String) defaultModeNames.get(MODE_OUTPUT));
0703:
0704: if (this .settings.containsKey("reloadable")) {
0705: reloadable = Boolean.valueOf(
0706: (String) this .settings.get("reloadable"))
0707: .booleanValue();
0708: }
0709: // read local parameter settings
0710: try {
0711: Configuration conf = this .getConfiguration(param
0712: .getParameter("descriptor", (String) this .settings
0713: .get("descriptor")), resolver, param
0714: .getParameterAsBoolean("reloadable", reloadable));
0715:
0716: // get database connection and try to turn off autocommit
0717: datasource = this .getDataSource(conf, param);
0718: conn = datasource.getConnection();
0719: if (conn.getAutoCommit() == true) {
0720: try {
0721: conn.setAutoCommit(false);
0722: } catch (Exception ex) {
0723: String tmp = param.getParameter("use-transactions",
0724: (String) this .settings.get(
0725: "use-transactions", null));
0726: if (tmp != null
0727: && (tmp.equalsIgnoreCase("no")
0728: || tmp.equalsIgnoreCase("false") || tmp
0729: .equalsIgnoreCase("0"))) {
0730: if (getLogger().isErrorEnabled())
0731: getLogger()
0732: .error(
0733: "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\".");
0734: throw ex;
0735: }
0736: }
0737: }
0738:
0739: // find tables to work with
0740: Configuration[] tables = conf.getChildren("table");
0741: String tablesetname = param.getParameter("table-set",
0742: (String) this .settings.get("table-set"));
0743:
0744: Map modeTypes = null;
0745:
0746: if (tablesetname == null) {
0747: modeTypes = new HashMap(6);
0748: modeTypes.put(MODE_AUTOINCR, "autoincr");
0749: modeTypes.put(MODE_OTHERS, "others");
0750: modeTypes.put(MODE_OUTPUT, outputMode);
0751: for (int i = 0; i < tables.length; i++) {
0752: rows += processTable(tables[i], conn, objectModel,
0753: results, modeTypes);
0754: }
0755: } else {
0756: // new set based behaviour
0757:
0758: // create index for table names / aliases
0759: Map tableIndex = new HashMap(2 * tables.length);
0760: String tableName = null;
0761: Object result = null;
0762: for (int i = 0; i < tables.length; i++) {
0763: tableName = tables[i].getAttribute("alias",
0764: tables[i].getAttribute("name", ""));
0765: result = tableIndex.put(tableName, new Integer(i));
0766: if (result != null) {
0767: throw new IOException(
0768: "Duplicate table entry for "
0769: + tableName + " at positions "
0770: + result + " and " + i);
0771: }
0772: }
0773:
0774: Configuration[] tablesets = conf
0775: .getChildren("table-set");
0776: String setname = null;
0777: boolean found = false;
0778:
0779: // find tables contained in tableset
0780: int j = 0;
0781: for (j = 0; j < tablesets.length; j++) {
0782: setname = tablesets[j].getAttribute("name", "");
0783: if (tablesetname.trim().equals(setname.trim())) {
0784: found = true;
0785: break;
0786: }
0787: }
0788: if (!found) {
0789: throw new IOException(" given set " + tablesetname
0790: + " does not exists in a description file.");
0791: }
0792:
0793: Configuration[] set = tablesets[j].getChildren("table");
0794:
0795: for (int i = 0; i < set.length; i++) {
0796: // look for alternative mode types
0797: modeTypes = new HashMap(6);
0798: modeTypes.put(MODE_AUTOINCR, set[i].getAttribute(
0799: "autoincr-mode", "autoincr"));
0800: modeTypes.put(MODE_OTHERS, set[i].getAttribute(
0801: "others-mode", "others"));
0802: modeTypes.put(MODE_OUTPUT, outputMode);
0803: tableName = set[i].getAttribute("name", "");
0804: if (tableIndex.containsKey(tableName)) {
0805: j = ((Integer) tableIndex.get(tableName))
0806: .intValue();
0807: rows += processTable(tables[j], conn,
0808: objectModel, results, modeTypes);
0809: } else {
0810: throw new IOException(
0811: " given table "
0812: + tableName
0813: + " does not exists in a description file.");
0814: }
0815: }
0816: }
0817:
0818: if (conn.getAutoCommit() == false) {
0819: conn.commit();
0820: }
0821:
0822: // obtain output mode module and rollback output
0823: ServiceSelector outputSelector = null;
0824: OutputModule output = null;
0825: try {
0826: outputSelector = (ServiceSelector) this .manager
0827: .lookup(OUTPUT_MODULE_SELECTOR);
0828: if (outputMode != null && outputSelector != null
0829: && outputSelector.isSelectable(outputMode)) {
0830: output = (OutputModule) outputSelector
0831: .select(outputMode);
0832: }
0833: if (output != null) {
0834: output.commit(null, objectModel);
0835: } else if (getLogger().isWarnEnabled()) {
0836: getLogger().warn(
0837: "Could not select output mode "
0838: + outputMode);
0839: }
0840: } catch (ServiceException e) {
0841: if (getLogger().isWarnEnabled()) {
0842: getLogger()
0843: .warn(
0844: "Could not select output mode "
0845: + outputMode + ":"
0846: + e.getMessage());
0847: }
0848: } finally {
0849: if (outputSelector != null) {
0850: if (output != null) {
0851: outputSelector.release(output);
0852: }
0853: this .manager.release(outputSelector);
0854: }
0855: }
0856: } catch (Exception e) {
0857: failed = true;
0858: if (conn != null) {
0859: try {
0860: if (getLogger().isDebugEnabled()) {
0861: getLogger().debug(
0862: "Rolling back transaction. Caused by "
0863: + e.getMessage());
0864: e.printStackTrace();
0865: }
0866: conn.rollback();
0867: results = null;
0868:
0869: // obtain output mode module and commit output
0870: ServiceSelector outputSelector = null;
0871: OutputModule output = null;
0872: try {
0873: outputSelector = (ServiceSelector) this .manager
0874: .lookup(OUTPUT_MODULE_SELECTOR);
0875: if (outputMode != null
0876: && outputSelector != null
0877: && outputSelector
0878: .isSelectable(outputMode)) {
0879: output = (OutputModule) outputSelector
0880: .select(outputMode);
0881: }
0882: if (output != null) {
0883: output.rollback(null, objectModel, e);
0884: } else if (getLogger().isWarnEnabled()) {
0885: getLogger().warn(
0886: "Could not select output mode "
0887: + outputMode);
0888: }
0889: } catch (ServiceException e2) {
0890: if (getLogger().isWarnEnabled()) {
0891: getLogger().warn(
0892: "Could not select output mode "
0893: + outputMode + ":"
0894: + e2.getMessage());
0895: }
0896: } finally {
0897: if (outputSelector != null) {
0898: if (output != null) {
0899: outputSelector.release(output);
0900: }
0901: this .manager.release(outputSelector);
0902: }
0903: }
0904: } catch (SQLException se) {
0905: if (getLogger().isDebugEnabled())
0906: getLogger()
0907: .debug(
0908: "There was an error rolling back the transaction",
0909: se);
0910: }
0911: }
0912:
0913: //throw new ProcessingException("Could not add record :position = " + currentIndex, e);
0914:
0915: // don't throw an exception, an error has been signalled, that should suffice
0916:
0917: String throwException = (String) this .settings.get(
0918: "throw-exception", param.getParameter(
0919: "throw-exception", null));
0920: if (throwException != null
0921: && BooleanUtils.toBoolean(throwException)) {
0922: throw new ProcessingException(
0923: "Cannot process the requested SQL statement ",
0924: e);
0925: }
0926: } finally {
0927: if (conn != null) {
0928: try {
0929: conn.close();
0930: } catch (SQLException sqe) {
0931: getLogger()
0932: .warn(
0933: "There was an error closing the datasource",
0934: sqe);
0935: }
0936: }
0937:
0938: if (datasource != null)
0939: this .dbselector.release(datasource);
0940: }
0941: if (results != null) {
0942: if (rows > 0 || (!failed && !this .failOnEmpty)) {
0943: results.put("row-count", new Integer(rows));
0944: } else {
0945: results = null;
0946: }
0947: } else {
0948: if (rows > 0) {
0949: results = new HashMap(1);
0950: results.put("row-count", new Integer(rows));
0951: }
0952: }
0953:
0954: return results; // (results == null? results : Collections.unmodifiableMap(results));
0955: }
0956:
0957: // ========================================================================
0958: // abstract methods
0959: // ========================================================================
0960:
0961: /**
0962: * set all necessary ?s and execute the query
0963: * return number of rows processed
0964: *
0965: * This method is intended to be overridden by classes that
0966: * implement other operations e.g. delete
0967: */
0968: protected abstract int processRow(Map objectModel, Connection conn,
0969: PreparedStatement statement, String outputMode,
0970: Configuration table, CacheHelper queryData,
0971: Object[][] columnValues, int rowIndex, Map results)
0972: throws SQLException, ConfigurationException, Exception;
0973:
0974: /**
0975: * determine which mode to use as default mode
0976: *
0977: * This method is intended to be overridden by classes that
0978: * implement other operations e.g. delete
0979: */
0980: protected abstract String selectMode(boolean isAutoIncrement,
0981: Map modes);
0982:
0983: /**
0984: * determine whether autoincrement columns should be honoured by
0985: * this operation. This is usually snsible only for INSERTs.
0986: *
0987: * This method is intended to be overridden by classes that
0988: * implement other operations e.g. delete
0989: */
0990: protected abstract boolean honourAutoIncrement();
0991:
0992: /**
0993: * Fetch all values for all columns that are needed to do the
0994: * database operation.
0995: *
0996: * This method is intended to be overridden by classes that
0997: * implement other operations e.g. delete
0998: */
0999: abstract Object[][] getColumnValues(Configuration tableConf,
1000: CacheHelper queryData, Map objectModel)
1001: throws ConfigurationException, ServiceException;
1002:
1003: /**
1004: * Get the String representation of the PreparedStatement. This is
1005: * mapped to the Configuration object itself, so if it doesn't exist,
1006: * it will be created.
1007: *
1008: * This method is intended to be overridden by classes that
1009: * implement other operations e.g. delete
1010: *
1011: * @param table the table's configuration object
1012: * @return the insert query as a string
1013: */
1014: protected abstract CacheHelper getQuery(Configuration table,
1015: Map modeTypes, Map defaultModeNames)
1016: throws ConfigurationException, ServiceException;
1017:
1018: }
|