| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cocoon.acting; |
| |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| import org.apache.avalon.excalibur.datasource.DataSourceComponent; |
| import org.apache.avalon.framework.configuration.Configuration; |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.avalon.framework.parameters.Parameters; |
| import org.apache.avalon.framework.thread.ThreadSafe; |
| import org.apache.cocoon.Constants; |
| import org.apache.cocoon.ProcessingException; |
| import org.apache.cocoon.environment.ObjectModelHelper; |
| import org.apache.cocoon.environment.Redirector; |
| import org.apache.cocoon.environment.Request; |
| import org.apache.cocoon.environment.SourceResolver; |
| import org.apache.commons.lang.StringUtils; |
| |
| /** |
| * Adds record in a database. The action can update one or more tables, |
| * and can add more than one row to a table at a time. The form descriptor |
| * semantics for this are still in a bit of a state of flux. Note |
| * that if a secondary table relies on the value of a new primary key in a |
| * primary table, the primary key must be created using manual mode. |
| * |
| * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a> |
| * @author <a href="mailto:balld@apache.org">Donald Ball</a> |
| * @version CVS $Id$ |
| */ |
| public class DatabaseAddAction extends AbstractDatabaseAction implements ThreadSafe { |
| protected static final Map addStatements = new HashMap(); |
| private static final Map selectStatements = new HashMap(); |
| |
| /** |
| * Add a record to the database. This action assumes that |
| * the file referenced by the "descriptor" parameter conforms |
| * to the AbstractDatabaseAction specifications. |
| */ |
| public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters param) throws Exception { |
| DataSourceComponent datasource = null; |
| Connection conn = null; |
| Map results = new HashMap(); |
| |
| // read global parameter settings |
| boolean reloadable = Constants.DESCRIPTOR_RELOADABLE_DEFAULT; |
| if (this.settings.containsKey("reloadable")) |
| reloadable = Boolean.valueOf((String) this.settings.get("reloadable")).booleanValue(); |
| // read local parameter settings |
| try { |
| Configuration conf = |
| this.getConfiguration(param.getParameter("descriptor", (String) this.settings.get("descriptor")), resolver, |
| param.getParameterAsBoolean("reloadable",reloadable)); |
| |
| datasource = this.getDataSource(conf); |
| conn = datasource.getConnection(); |
| Request request = ObjectModelHelper.getRequest(objectModel); |
| |
| if (conn.getAutoCommit()) { |
| conn.setAutoCommit(false); |
| } |
| |
| Configuration[] tables = conf.getChildren("table"); |
| for (int i=0; i<tables.length; i++) { |
| Configuration table = tables[i]; |
| processTable(table,conn,request,results); |
| } |
| conn.commit(); |
| } catch (Exception e) { |
| if (conn != null) { |
| try { |
| conn.rollback(); |
| } catch (SQLException se) { |
| getLogger().debug("There was an error rolling back the transaction", se); |
| } |
| } |
| |
| //throw new ProcessingException("Could not add record :position = " + currentIndex, e); |
| throw new ProcessingException("Could not add record",e); |
| } finally { |
| if (conn != null) { |
| try { |
| conn.close(); |
| } catch (SQLException sqe) { |
| getLogger().warn("There was an error closing the datasource", sqe); |
| } |
| } |
| |
| if (datasource != null) this.dbselector.release(datasource); |
| } |
| |
| return Collections.unmodifiableMap(results); |
| } |
| |
| /** |
| * Inserts a row or a set of rows into the given table based on the |
| * request parameters |
| * |
| * @param table the table's configuration |
| * @param conn the database connection |
| * @param request the request |
| */ |
| void processTable(Configuration table, Connection conn, Request request, Map results) throws SQLException,ConfigurationException,Exception { |
| PreparedStatement statement = null; |
| try { |
| String query = this.getAddQuery(table); |
| getLogger().debug("Add query: "+query); |
| statement = conn.prepareStatement(query); |
| Configuration[] keys = table.getChild("keys").getChildren("key"); |
| Configuration[] values = table.getChild("values").getChildren("value"); |
| int currentIndex = 1; |
| boolean manyrows = false; |
| int wildcardIndex = -1; |
| String wildcardParam = null; |
| for (int i=0; i<keys.length; i++) { |
| wildcardParam = keys[i].getAttribute("param"); |
| if ((wildcardIndex = wildcardParam.indexOf('*')) != -1) { |
| manyrows = true; |
| break; |
| } |
| } |
| if (manyrows) { |
| /** |
| * This table has a column with a wildcard, so we're going |
| * to be inserting n rows, where 0 <= n |
| */ |
| String prefix = wildcardParam.substring(0,wildcardIndex); |
| String suffix = StringUtils.substring(wildcardParam, wildcardIndex + 1); |
| Enumeration names = request.getParameterNames(); |
| SortedSet matchset = new TreeSet(); |
| int prefixLength = prefix.length(); |
| int length = prefixLength + suffix.length(); |
| while (names.hasMoreElements()) { |
| String name = (String)names.nextElement(); |
| if (name.startsWith(prefix) && name.endsWith(suffix)) { |
| String wildcard = StringUtils.mid(name, prefixLength, name.length() - length); |
| matchset.add(wildcard); |
| } |
| } |
| int rowIndex = 1; |
| Iterator iterator = matchset.iterator(); |
| while (iterator.hasNext()) { |
| String wildcard = (String)iterator.next(); |
| currentIndex = 1; |
| for (int j=0; j<keys.length; j++) { |
| String myparam = getActualParam(keys[j].getAttribute("param"),wildcard); |
| currentIndex += setKey(table,keys[j],conn,statement,currentIndex,request,myparam,results); |
| } |
| for (int j=0; j<values.length; j++) { |
| String myparam = getActualParam(values[j].getAttribute("param"),wildcard); |
| this.setColumn(statement,currentIndex,request,values[j],myparam,request.getParameter(myparam),rowIndex); |
| currentIndex++; |
| } |
| statement.execute(); |
| rowIndex++; |
| } |
| } else { |
| /** |
| * This table has no wildcard columns, so we're going to |
| * be inserting 1 row. |
| */ |
| for (int i = 0; i < keys.length; i++) { |
| currentIndex += setKey(table,keys[i],conn,statement,currentIndex,request,keys[i].getAttribute("param",""),results); |
| } |
| for (int i = 0; i < values.length; i++, currentIndex++) { |
| this.setColumn(statement, currentIndex, request, values[i]); |
| } |
| statement.execute(); |
| /** Done processing table **/ |
| } |
| } finally { |
| try { |
| if (statement != null) { |
| statement.close(); |
| } |
| } catch (SQLException e) {} |
| } |
| } |
| |
| /** |
| * Sets the key value on the prepared statement. There are four modes: |
| * |
| * <dl> |
| * <dt>automatic (default)</dt> |
| * <dd>let the database automatically create the key. note this |
| * prohibits the action from storing the key value anywhere.</dd> |
| * <dt>manual</dt> |
| * <dd>create the key value using SELECT(dbcol)+1 from TABLE</dd> |
| * <dt>form</dt> |
| * <dd>look for the key value in the request parameters</dd> |
| * <dt>request-attribute</dt> |
| * <dd>look for the key value in the request attributes</dd> |
| * </dl> |
| * |
| * This method has a couple of side effects. If the mode is manual, |
| * the key value is stored in the request object's attributes for use |
| * by other inserts. The key is the string "key:TABLENAME:DBCOL". |
| * This method also puts the value of manually created keys in the results |
| * map. That key is simply the value of the dbcol attribute. Note this |
| * stuff is definitely up in the air. |
| * |
| * @param table the table's configuration object |
| * @param key the key's configuration object |
| * @param conn the database connection |
| * @param statement the insert statement |
| * @param currentIndex the position of the key column |
| * @param request the request object |
| * @param param the actual name of the request parameter |
| * @return the number of columns by which to increment the currentIndex |
| */ |
| int setKey(Configuration table, Configuration key, Connection conn, PreparedStatement statement, int currentIndex, Request request, String param, Map results) throws ConfigurationException, SQLException,Exception { |
| String mode = key.getAttribute("mode","automatic"); |
| String keyname = new StringBuffer("key:").append(table.getAttribute("name")) |
| .append(':').append(key.getAttribute("dbcol")).toString(); |
| if ("manual".equals(mode)) { |
| // Set the key value using SELECT MAX(keyname)+1 |
| String selectQuery = this.getSelectQuery(key); |
| PreparedStatement select_statement = conn.prepareStatement(selectQuery); |
| ResultSet set = select_statement.executeQuery(); |
| set.next(); |
| int value = set.getInt("maxid") + 1; |
| statement.setInt(currentIndex, value); |
| getLogger().debug("Manually setting key to " + value); |
| setRequestAttribute(request,keyname,new Integer(value)); |
| results.put(key.getAttribute("dbcol"),String.valueOf(value)); |
| set.close(); |
| select_statement.close(); |
| } else if ("form".equals(mode)) { |
| // Set the key value from the request |
| getLogger().debug("Setting key from form"); |
| this.setColumn(statement, currentIndex, request, key, param); |
| } else if ("request-attribute".equals(mode)) { |
| Integer value = (Integer)getRequestAttribute(request,key.getAttribute("request-attribute-name")); |
| getLogger().debug("Setting key from request attribute "+value); |
| statement.setInt(currentIndex,value.intValue()); |
| } else { |
| getLogger().debug("Automatically setting key"); |
| // The database automatically creates a key value |
| return 0; |
| } |
| return 1; |
| } |
| |
| /** |
| * Returns the actual name of the parameter. If the name contains |
| * no wildcard, the param is returned untouched, otherwise the |
| * wildcard value is substituted for the * character. This probably |
| * doesn't deserve a method unto itself, but I can imagine wanting |
| * to use a more sophisticated matching and substitution algorithm. |
| * |
| * @param param the name of the parameter, possibly with a wildcard char |
| * @param wildcard the wildcard value |
| * @return the actual name of the parameter |
| */ |
| String getActualParam(String param, String wildcard) { |
| int index; |
| if ((index = param.indexOf('*')) != -1) { |
| return param.substring(0,index)+wildcard+param.substring(index+1); |
| } else { |
| return param; |
| } |
| } |
| |
| /** |
| * Get the String representation of the PreparedStatement. This is |
| * mapped to the Configuration object itself, so if it doesn't exist, |
| * it will be created. |
| * |
| * @param table the table's configuration object |
| * @return the insert query as a string |
| */ |
| protected String getAddQuery(Configuration table) throws ConfigurationException { |
| String query = null; |
| synchronized (DatabaseAddAction.addStatements) { |
| query = (String) DatabaseAddAction.addStatements.get(table); |
| if (query == null) { |
| Configuration[] values = table.getChild("values").getChildren("value"); |
| Configuration[] keys = table.getChild("keys").getChildren("key"); |
| |
| StringBuffer queryBuffer = new StringBuffer("INSERT INTO "); |
| queryBuffer.append(table.getAttribute("name")); |
| queryBuffer.append(" ("); |
| |
| int numParams = 0; |
| |
| for (int i = 0; i < keys.length; i++) { |
| String mode = keys[i].getAttribute("mode", "automatic"); |
| if ("manual".equals(mode) || "form".equals(mode) || "request-attribute".equals(mode)) { |
| if (numParams > 0) { |
| queryBuffer.append(", "); |
| } |
| queryBuffer.append(keys[i].getAttribute("dbcol")); |
| this.setSelectQuery(table.getAttribute("name"), keys[i]); |
| numParams++; |
| } |
| } |
| queryBuffer.append(buildList(values, numParams)); |
| numParams += values.length; |
| queryBuffer.append(") VALUES ("); |
| if (numParams > 0) { |
| queryBuffer.append("?"); |
| queryBuffer.append(StringUtils.repeat(", ?", numParams - 1)); |
| } |
| queryBuffer.append(")"); |
| query = queryBuffer.toString(); |
| |
| DatabaseAddAction.addStatements.put(table, query); |
| } |
| } |
| return query; |
| } |
| |
| /** |
| * Set the String representation of the MaxID lookup statement. This is |
| * mapped to the Configuration object itself, so if it doesn't exist, |
| * it will be created. |
| */ |
| protected final synchronized void setSelectQuery(String tableName, Configuration entry) throws ConfigurationException { |
| StringBuffer queryBuffer = new StringBuffer("SELECT max("); |
| queryBuffer.append(entry.getAttribute("dbcol")); |
| queryBuffer.append(") AS maxid FROM "); |
| queryBuffer.append(tableName); |
| |
| DatabaseAddAction.selectStatements.put(entry, queryBuffer.toString()); |
| } |
| |
| protected final synchronized String getSelectQuery(Configuration entry) throws ConfigurationException { |
| return (String) DatabaseAddAction.selectStatements.get(entry); |
| } |
| } |