/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-util/src/main/java/org/sakaiproject/util/BaseDbDualSingleStorage.java $ * $Id: BaseDbDualSingleStorage.java 82133 2010-09-07 21:45:01Z aaronz@vt.edu $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.util; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.db.api.SqlReader; import org.sakaiproject.db.api.SqlService; import org.sakaiproject.entity.api.Edit; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.entity.api.serialize.EntityParseException; import org.sakaiproject.entity.api.serialize.EntityReader; import org.sakaiproject.entity.api.serialize.EntityReaderHandler; import org.sakaiproject.event.cover.UsageSessionService; import org.sakaiproject.javax.Filter; import org.sakaiproject.time.cover.TimeService; /** * <p> * BaseDbDualSingleStorage is a class that stores Resources (of some type) in a * database, <br /> * provides locked access, and generally implements a services "storage" class. * The resources are encoded into two fields. * Optionally a second storage can be provided which is where the items are loaded * from when a putDeleteResource is called. * The <br /> * service's storage class can extend this to provide covers to turn Resource * and <br /> * Edit into something more type specific to the service. * </p> * <p> * Note: the methods here are all "id" based, with the following assumptions: * <br /> - just the Resource Id field is enough to distinguish one Resource * from another <br /> - a resource's reference is based on no more than the * resource id <br /> - a resource's id cannot change. * </p> * <p> * In order to handle Unicode characters properly, the SQL statements executed * by this class <br /> * should not embed Unicode characters into the SQL statement text; rather, * Unicode values <br /> * should be inserted as fields in a PreparedStatement. Databases handle Unicode * better in fields. * </p> */ public class BaseDbDualSingleStorage implements DbSingleStorage { public static final String STORAGE_FIELDS = "XML, BINARY_ENTITY"; /** Our logger. */ private static Log M_log = LogFactory.getLog(BaseDbDualSingleStorage.class); /** Table name for resource records. */ protected String m_resourceTableName = null; /** The field in the resource table that holds the resource id. */ protected String m_resourceTableIdField = null; /** * The additional field names in the resource table that go between the two * ids and the xml */ protected String[] m_resourceTableOtherFields = null; /** The xml tag name for the element holding each actual resource entry. */ protected String m_resourceEntryTagName = null; /** If true, we do our locks in the remote database. */ protected boolean m_locksAreInDb = false; /** * If true, we do our locks in the remove database using a separate locking * table. */ protected boolean m_locksAreInTable = true; /** The StorageUser to callback for new Resource and Edit objects. */ protected SingleStorageUser m_user = null; /** * Locks, keyed by reference, holding Connections (or, if locks are done * locally, holding an Edit). */ protected Hashtable m_locks = null; /** If set, we treat reasource ids as case insensitive. */ protected boolean m_caseInsensitive = false; /** Injected (by constructor) SqlService. */ protected SqlService m_sql = null; /** contains a map of the database dependent handlers. */ protected static Map<String, MultiSingleStorageSql> databaseBeans; /** The db handler we are using. */ protected MultiSingleStorageSql singleStorageSql; private long ttotal = 0; private int ntime = 0; private int rntime = 0; private long rttotal = 0; private long rmtotal = 0; private long mtotal = 0; public void setDatabaseBeans(Map databaseBeans) { this.databaseBeans = databaseBeans; } /** * sets which bean containing database dependent code should be used * depending on the database vendor. */ public void setSingleStorageSql(String vendor) { this.singleStorageSql = (databaseBeans.containsKey(vendor) ? databaseBeans .get(vendor) : databaseBeans.get("default")); } // since spring is not used and this class is instatiated directly, we need // to "inject" these values ourselves static { databaseBeans = new Hashtable<String, MultiSingleStorageSql>(); databaseBeans.put("default", new MultiSingleStorageSqlDefault(STORAGE_FIELDS)); databaseBeans.put("hsql", new MultiSingleStorageSqlHSql(STORAGE_FIELDS)); databaseBeans.put("mysql", new MultiSingleStorageSqlMySql(STORAGE_FIELDS)); databaseBeans.put("oracle", new MultiSingleStorageSqlOracle(STORAGE_FIELDS)); } /** * Construct. * * @param resourceTableName * Table name for resources. * @param resourceTableIdField * The field in the resource table that holds the id. * @param resourceTableOtherFields * The other fields in the resource table (between the two id and the * xml fields). * @param locksInDb * If true, we do our locks in the remote database, otherwise we do * them here. * @param resourceEntryName * The xml tag name for the element holding each actual resource * entry. * @param user * The StorageUser class to call back for creation of Resource and * Edit objects. This must implement EntityReader interface as well. * @param sqlService * The SqlService. */ public BaseDbDualSingleStorage(String resourceTableName, String resourceTableIdField, String[] resourceTableOtherFields, boolean locksInDb, String resourceEntryName, SingleStorageUser user, SqlService sqlService) { this(resourceTableName, resourceTableIdField, resourceTableOtherFields, locksInDb, resourceEntryName, user, sqlService, null); } // support for SAK-12874 protected DbSingleStorage m_storage = null; /** * Construct. * * @param resourceTableName * Table name for resources. * @param resourceTableIdField * The field in the resource table that holds the id. * @param resourceTableOtherFields * The other fields in the resource table (between the two id and the * xml fields). * @param locksInDb * If true, we do our locks in the remote database, otherwise we do * them here. * @param resourceEntryName * The xml tag name for the element holding each actual resource * entry. * @param user * The StorageUser class to call back for creation of Resource and * Edit objects. * @param sqlService * The SqlService. * @param storage * The storage for the normal resource (only used by delete storage), this is how we load the original resource. */ public BaseDbDualSingleStorage(String resourceTableName, String resourceTableIdField, String[] resourceTableOtherFields, boolean locksInDb, String resourceEntryName, SingleStorageUser user, SqlService sqlService, DbSingleStorage storage) { m_resourceTableName = resourceTableName; m_resourceTableIdField = resourceTableIdField; m_resourceTableOtherFields = resourceTableOtherFields; m_locksAreInDb = locksInDb; m_resourceEntryTagName = resourceEntryName; m_user = user; m_sql = sqlService; // support for SAK-12874 m_storage = storage; if (m_storage == null && m_resourceTableName != null && m_resourceTableName.toUpperCase().contains("DELETE")) { // warn if the delete storage does not have the main storage set M_log.warn("resource storage is not set, delete table resource file paths will be invalid"); } setSingleStorageSql(m_sql.getVendor()); } /** * Open and be ready to read / write. */ public void open() { // setup for locks m_locks = new Hashtable(); } /** * Close. */ public void close() { if (!m_locks.isEmpty()) { M_log.warn("close(): locks remain!"); // %%% } m_locks.clear(); m_locks = null; } /** * Read one Resource from xml * * @param xml * An string containing the xml which describes the resource. * @return The Resource object created from the xml. */ protected Entity readResource(String xml, byte[] blob) { Runtime r = Runtime.getRuntime(); long ms = r.freeMemory(); long start = System.currentTimeMillis(); String type = ""; try { type = "direct"; EntityReader de_user = (EntityReader) m_user; EntityReaderHandler de_handler = de_user.getHandler(); return de_handler.parse(null, xml, blob); } catch (Exception e) { M_log.warn("readResource(): " + e.getMessage()); M_log.warn("readResource(): ", e); return null; } finally { long t = System.currentTimeMillis() - start; long me = r.freeMemory(); long md = ms - me; if (md >= 0) { rmtotal += md; } else { if (rntime != 0) { rmtotal += (rmtotal / rntime); } } rttotal += t; rntime++; if (rntime % 100 == 0) { double a = (1.0 * rttotal) / (1.0 * rntime); double m = (1.0 * rmtotal) / (1.0 * rntime); M_log.debug("Average " + type + " Parse now " + (a) + "ms " + m + " bytes"); } } } /** * Check if a Resource by this id exists. * * @param id * The id. * @return true if a Resource by this id exists, false if not. */ public boolean checkResource(String id) { // just see if the record exists String sql = singleStorageSql.getResourceIdSql(m_resourceTableIdField, m_resourceTableName); Object fields[] = new Object[1]; fields[0] = caseId(id); List ids = m_sql.dbRead(sql, fields, null); return (!ids.isEmpty()); } /** * Get the Resource with this id, or null if not found. * * @param id * The id. * @return The Resource with this id, or null if not found. */ public Entity getResource(String id) { Entity entry = null; // get the user from the db List xml = null; String sql = singleStorageSql.getXmlSql(m_resourceTableIdField, m_resourceTableName); Object fields[] = new Object[1]; fields[0] = caseId(id); xml = loadResources(sql, fields); if (!xml.isEmpty()) { // create the Resource from the db xml entry = (Entity) xml.get(0); } return entry; } public boolean isEmpty() { // count int count = countAllResources(); return (count == 0); } public List getAllResources() { List all = new Vector(); // read all users from the db List xml = null; String sql = singleStorageSql.getXmlSql(m_resourceTableName); xml = loadResources(sql, null); // %%% + "order by " + m_resourceTableOrderField + " asc"; // process all result xml into user objects if (!xml.isEmpty()) { for (int i = 0; i < xml.size(); i++) { Entity entry = (Entity) xml.get(i); if (entry != null) all.add(entry); } } return all; } public List getAllResources(int first, int last) { Object[] fields = singleStorageSql.getXmlFields(first, last); List xml = null; String sql = singleStorageSql.getXmlSql(m_resourceTableIdField, m_resourceTableName, first, last); xml = loadResources(sql, fields); List rv = new Vector(); // process all result xml into user objects if (!xml.isEmpty()) { for (int i = 0; i < xml.size(); i++) { Entity entry = (Entity) xml.get(i); if (entry != null) rv.add(entry); } } return rv; } public int countAllResources() { // read all count String sql = singleStorageSql.getNumRowsSql(m_resourceTableName); List results = m_sql.dbRead(sql, null, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { int count = result.getInt(1); return Integer.valueOf(count); } catch (SQLException ignore) { return null; } } }); if (results.isEmpty()) return 0; return ((Integer) results.get(0)).intValue(); } public int countSelectedResourcesWhere(String sqlWhere) { // read all where count String sql = singleStorageSql.getNumRowsSql(m_resourceTableName, sqlWhere); List results = m_sql.dbRead(sql, null, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { int count = result.getInt(1); return Integer.valueOf(count); } catch (SQLException ignore) { return null; } } }); if (results.isEmpty()) return 0; return ((Integer) results.get(0)).intValue(); } /** * Get all Resources where the given field matches the given value. * * @param field * The db field name for the selection. * @param value * The value to select. * @return The list of all Resources that meet the criteria. */ public List getAllResourcesWhere(String field, String value) { // read all users from the db String sql = singleStorageSql.getXmlSql(field, m_resourceTableName); Object[] fields = new Object[1]; fields[0] = value; // %%% + "order by " + m_resourceTableOrderField + " asc"; return loadResources(sql, fields); } /** * Get all Resources where the given field matches the given value. * * @param field * The db field name for the selection. * @param value * The value to select. * @return The list of all Resources that meet the criteria. */ public List getAllResourcesWhere(String selectBy, String selectByValue, String orderBy, int first, int pageSize) { // read all users from the db String sql = singleStorageSql.getXmlWhereLimitSql(selectBy, orderBy, m_resourceTableName, first, pageSize); Object[] fields = new Object[1]; fields[0] = selectByValue; // %%% + "order by " + m_resourceTableOrderField + " asc"; return loadResources(sql, fields); } protected List loadResources(String sql, Object[] fields) { List all = m_sql.dbRead(sql, fields, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { // create the Resource from the db xml return readResource(result.getString(1), result.getBytes(2)); } catch (SQLException ignore) { return null; } } }); return all; } public List getAllResourcesWhereLike(String field, String value) { String sql = singleStorageSql.getXmlLikeSql(field, m_resourceTableName); Object[] fields = new Object[1]; fields[0] = value; // %%% + "order by " + m_resourceTableOrderField + " asc"; return loadResources(sql, fields); } /** * Get selected Resources, filtered by a test on the id field * * @param filter * A filter to select what gets returned. * @return The list of selected Resources. */ public List getSelectedResources(final Filter filter) { List all = new Vector(); // read all users from the db String sql = singleStorageSql.getXmlAndFieldSql(m_resourceTableIdField, m_resourceTableName); // %%% + "order by " + m_resourceTableOrderField + " asc"; List xml = m_sql.dbRead(sql, null, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { // read the id m_resourceTableIdField String id = result.getString(1); // read the xml String xml = result.getString(2); byte[] blob = result.getBytes(3); if (!filter.accept(caseId(id))) return null; return readResource(xml, blob); } catch (SQLException ignore) { return null; } } }); // process all result xml into user objects if (!xml.isEmpty()) { for (int i = 0; i < xml.size(); i++) { Entity entry = (Entity) xml.get(i); if (entry != null) all.add(entry); } } return all; } /** * Get selected Resources, using a supplied where clause * * @param sqlWhere * The SQL where clause. * @return The list of selected Resources. */ public List getSelectedResourcesWhere(String sqlWhere) { List all = new Vector(); // read all users from the db String sql = singleStorageSql.getXmlWhereSql(m_resourceTableName, sqlWhere); List xml = loadResources(sql, null); // process all result xml into user objects if (!xml.isEmpty()) { for (int i = 0; i < xml.size(); i++) { Entity entry = (Entity) xml.get(i); if (entry != null) all.add(entry); } } return all; } /** * Add a new Resource with this id. * * @param id * The id. * @param others * Other fields for the newResource call * @return The locked Resource object with this id, or null if the id is in * use. */ public Edit putResource(String id, Object[] others) { // create one with just the id, and perhaps some other fields as well Entity entry = m_user.newResource(null, id, others); // form the XML and SQL for the insert Object blob = getBlob(entry); String statement = null; if (blob instanceof byte[]) { statement = // singleStorageSql. "insert into " + m_resourceTableName + insertFields(m_resourceTableIdField, m_resourceTableOtherFields, "BINARY_ENTITY, XML") + " values ( ?, " + valuesParams(m_resourceTableOtherFields) + " ? , NULL )"; } else { statement = // singleStorageSql. "insert into " + m_resourceTableName + insertFields(m_resourceTableIdField, m_resourceTableOtherFields, "XML, BINARY_ENTITY ") + " values ( ?, " + valuesParams(m_resourceTableOtherFields) + " ?, NULL )"; } Object[] flds = m_user.storageFields(entry); if (flds == null) flds = new Object[0]; Object[] fields = new Object[flds.length + 2]; System.arraycopy(flds, 0, fields, 1, flds.length); fields[0] = caseId(entry.getId()); fields[fields.length - 1] = blob; // process the insert boolean ok = m_sql.dbWrite(statement, fields); // if this failed, assume a key conflict (i.e. id in use) if (!ok) return null; // now get a lock on the record for edit Edit edit = editResource(id); if (edit == null) { M_log.warn("putResource(): didn't get a lock!"); return null; } return edit; } /** * store the record in content_resource_delete table along with * resource_uuid and date */ public Edit putDeleteResource(String id, String uuid, String userId, Object[] others) { // support for SAK-12874 Entity entry = null; if (m_storage != null) { // use the object being deleted entry = m_storage.getResource(id); } if (entry == null) { // failsafe to the old method entry = m_user.newResource(null, id, others); } // form the XML and SQL for the insert Object blob = getBlob(entry); String statement = null; if (blob instanceof byte[]) { statement = "insert into " + m_resourceTableName + insertDeleteFields(m_resourceTableIdField, m_resourceTableOtherFields, "RESOURCE_UUID", "DELETE_DATE", "DELETE_USERID", "BINARY_ENTITY, XML") + " values ( ?, " + valuesParams(m_resourceTableOtherFields) + " ? ,? ,? ,?, NULL)"; } else { statement = "insert into " + m_resourceTableName + insertDeleteFields(m_resourceTableIdField, m_resourceTableOtherFields, "RESOURCE_UUID", "DELETE_DATE", "DELETE_USERID", "XML, BINARY_ENTITY") + " values ( ?, " + valuesParams(m_resourceTableOtherFields) + " ? ,? ,? ,?, NULL)"; } Object[] flds = m_user.storageFields(entry); if (flds == null) flds = new Object[0]; Object[] fields = new Object[flds.length + 5]; System.arraycopy(flds, 0, fields, 1, flds.length); fields[0] = caseId(entry.getId()); // uuid added here fields[fields.length - 4] = uuid; // date added here fields[fields.length - 3] = TimeService.newTime();// .toStringLocalDate(); // userId added here fields[fields.length - 2] = userId; fields[fields.length - 1] = blob; // process the insert boolean ok = m_sql.dbWrite(statement, fields); // if this failed, assume a key conflict (i.e. id in use) if (!ok) return null; // now get a lock on the record for edit Edit edit = editResource(id); if (edit == null) { M_log.warn("putResourceDelete(): didn't get a lock!"); return null; } return edit; } /** Construct the SQL statement */ protected String insertDeleteFields(String before, String[] fields, String uuid, String date, String userId, String after) { StringBuilder buf = new StringBuilder(); buf.append(" ("); buf.append(before); buf.append(","); if (fields != null) { for (int i = 0; i < fields.length; i++) { buf.append(fields[i] + ","); } } buf.append(uuid); buf.append(","); buf.append(date); buf.append(","); buf.append(userId); buf.append(","); buf.append(after); buf.append(")"); return buf.toString(); } /** update XML attribute on properties and remove locks */ public void commitDeleteResource(Edit edit, String uuid) { // form the SQL statement and the var w/ the XML Object blob = getBlob(edit); String statement = null; if (blob instanceof byte[]) { statement = "update " + m_resourceTableName + " set " + updateSet(m_resourceTableOtherFields) + " BINARY_ENTITY = ?, XML = NULL where ( RESOURCE_UUID = ? )"; } else { statement = "update " + m_resourceTableName + " set " + updateSet(m_resourceTableOtherFields) + " XML = ?, BINARY_ENTITY = NULL where ( RESOURCE_UUID = ? )"; } Object[] flds = m_user.storageFields(edit); if (flds == null) flds = new Object[0]; Object[] fields = new Object[flds.length + 2]; System.arraycopy(flds, 0, fields, 0, flds.length); fields[fields.length - 2] = blob; fields[fields.length - 1] = uuid;// caseId(edit.getId()); if (m_locksAreInDb) { // use this connection that is stored with the lock Connection lock = (Connection) m_locks.get(edit.getReference()); if (lock == null) { M_log.warn("commitResource(): edit not in locks"); return; } // update, commit, release the lock's connection m_sql.dbUpdateCommit(statement, fields, null, lock); // remove the lock m_locks.remove(edit.getReference()); } else if (m_locksAreInTable) { // process the update m_sql.dbWrite(statement, fields); // remove the lock statement = singleStorageSql.getDeleteLocksSql(); // collect the fields Object lockFields[] = new Object[2]; lockFields[0] = m_resourceTableName; lockFields[1] = internalRecordId(caseId(edit.getId())); boolean ok = m_sql.dbWrite(statement, lockFields); if (!ok) { M_log.warn("commit: missing lock for table: " + lockFields[0] + " key: " + lockFields[1]); } } else { // just process the update m_sql.dbWrite(statement, fields); // remove the lock m_locks.remove(edit.getReference()); } } /** * Get a lock on the Resource with this id, or null if a lock cannot be * gotten. * * @param id * The user id. * @return The locked Resource with this id, or null if this records cannot * be locked. */ public Edit editResource(String id) { Edit edit = null; if (m_locksAreInDb) { if ("oracle".equals(m_sql.getVendor())) { final List<Entity> l = new ArrayList<Entity>(); Connection lock = null; if (m_user instanceof EntityReaderHandler) { // read the record and get a lock on it (non blocking) String statement = "select XML from " + m_resourceTableName + " where ( " + m_resourceTableIdField + " = '" + StorageUtils.escapeSql(caseId(id)) + "' )" + " for update nowait"; lock = m_sql.dbReadLock(statement, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { l.add(readResource(result.getString(1), result .getBytes(2))); } catch (SQLException e) { M_log.warn("Failed to retrieve record ", e); } return null; } }); } else { // read the record and get a lock on it (non blocking) String statement = "select BENTRY, XML from " + m_resourceTableName + " where ( " + m_resourceTableIdField + " = '" + StorageUtils.escapeSql(caseId(id)) + "' )" + " for update nowait"; lock = m_sql.dbReadLock(statement, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { l.add(readResource(result.getString(1), result .getBytes(2))); } catch (SQLException e) { M_log.warn("Failed to retrieve record ", e); } return null; } }); } // for missing or already locked... if ((lock == null) || (l.size() == 0)) return null; // make first a Resource, then an Edit Entity entry = l.get(0); edit = m_user.newResourceEdit(null, entry); // store the lock for this object m_locks.put(entry.getReference(), lock); } else { throw new UnsupportedOperationException( "Record locking only available when configured with Oracle database"); } } // if the locks are in a separate table in the db else if (m_locksAreInTable) { // read the record - fail if not there Entity entry = getResource(id); if (entry == null) return null; // write a lock to the lock table - if we can do it, we get the lock String statement = singleStorageSql.getInsertLocks(); // we need session id and user id String sessionId = UsageSessionService.getSessionId(); if (sessionId == null) { sessionId = ""; } // collect the fields Object fields[] = new Object[4]; fields[0] = m_resourceTableName; fields[1] = internalRecordId(caseId(id)); fields[2] = TimeService.newTime(); fields[3] = sessionId; // add the lock - if fails, someone else has the lock boolean ok = m_sql.dbWriteFailQuiet(null, statement, fields); if (!ok) { return null; } // we got the lock! - make the edit from the Resource edit = m_user.newResourceEdit(null, entry); } // otherwise, get the lock locally else { // get the entry, and check for existence Entity entry = getResource(id); if (entry == null) return null; // we only sync this getting - someone may release a lock out of // sync synchronized (m_locks) { // if already locked if (m_locks.containsKey(entry.getReference())) return null; // make the edit from the Resource edit = m_user.newResourceEdit(null, entry); // store the edit in the locks by reference m_locks.put(entry.getReference(), edit); } } return edit; } /** * Commit the changes and release the lock. * * @param user * The Edit to commit. */ public void commitResource(Edit edit) { // form the SQL statement and the var w/ the XML Object blob = getBlob(edit); String statement = null; if (blob instanceof byte[]) { statement = "update " + m_resourceTableName + " set " + updateSet(m_resourceTableOtherFields) + " BINARY_ENTITY = ?, XML = NULL where ( " + m_resourceTableIdField + " = ? )"; } else { statement = "update " + m_resourceTableName + " set " + updateSet(m_resourceTableOtherFields) + " XML = ?, BINARY_ENTITY = NULL where ( " + m_resourceTableIdField + " = ? )"; } Object[] flds = m_user.storageFields(edit); if (flds == null) flds = new Object[0]; Object[] fields = new Object[flds.length + 2]; System.arraycopy(flds, 0, fields, 0, flds.length); fields[fields.length - 2] = blob; fields[fields.length - 1] = caseId(edit.getId()); // singleStorageSql.getUpdateXml(m_resourceTableIdField, // m_resourceTableOtherFields, m_resourceTableName); if (m_locksAreInDb) { // use this connection that is stored with the lock Connection lock = (Connection) m_locks.get(edit.getReference()); if (lock == null) { M_log.warn("commitResource(): edit not in locks"); return; } // update, commit, release the lock's connection m_sql.dbUpdateCommit(statement, fields, null, lock); // remove the lock m_locks.remove(edit.getReference()); } else if (m_locksAreInTable) { // process the update m_sql.dbWrite(statement, fields); // remove the lock statement = singleStorageSql.getDeleteLocksSql(); // collect the fields Object lockFields[] = new Object[2]; lockFields[0] = m_resourceTableName; lockFields[1] = internalRecordId(caseId(edit.getId())); boolean ok = m_sql.dbWrite(statement, lockFields); if (!ok) { M_log.warn("commit: missing lock for table: " + lockFields[0] + " key: " + lockFields[1]); } } else { // just process the update m_sql.dbWrite(statement, fields); // remove the lock m_locks.remove(edit.getReference()); } } /** * Cancel the changes and release the lock. * * @param user * The Edit to cancel. */ public void cancelResource(Edit edit) { if (m_locksAreInDb) { // use this connection that is stored with the lock Connection lock = (Connection) m_locks.get(edit.getReference()); if (lock == null) { M_log.warn("cancelResource(): edit not in locks"); return; } // rollback and release the lock's connection m_sql.dbCancel(lock); // release the lock m_locks.remove(edit.getReference()); } else if (m_locksAreInTable) { // remove the lock String statement = singleStorageSql.getDeleteLocksSql(); // collect the fields Object lockFields[] = new Object[2]; lockFields[0] = m_resourceTableName; lockFields[1] = internalRecordId(caseId(edit.getId())); boolean ok = m_sql.dbWrite(statement, lockFields); if (!ok) { M_log.warn("cancel: missing lock for table: " + lockFields[0] + " key: " + lockFields[1]); } } else { // release the lock m_locks.remove(edit.getReference()); } } /** * Remove this (locked) Resource. * * @param user * The Edit to remove. */ public void removeResource(Edit edit) { // form the SQL delete statement String statement = singleStorageSql.getDeleteSql(m_resourceTableIdField, m_resourceTableName); Object fields[] = new Object[1]; fields[0] = caseId(edit.getId()); if (m_locksAreInDb) { // use this connection that is stored with the lock Connection lock = (Connection) m_locks.get(edit.getReference()); if (lock == null) { M_log.warn("removeResource(): edit not in locks"); return; } // process the delete statement, commit, and release the lock's // connection m_sql.dbUpdateCommit(statement, fields, null, lock); // release the lock m_locks.remove(edit.getReference()); } else if (m_locksAreInTable) { // process the delete statement m_sql.dbWrite(statement, fields); // remove the lock statement = singleStorageSql.getDeleteLocksSql(); // collect the fields Object lockFields[] = new Object[2]; lockFields[0] = m_resourceTableName; lockFields[1] = internalRecordId(caseId(edit.getId())); boolean ok = m_sql.dbWrite(statement, lockFields); if (!ok) { M_log.warn("remove: missing lock for table: " + lockFields[0] + " key: " + lockFields[1]); } } else { // process the delete statement m_sql.dbWrite(statement, fields); // release the lock m_locks.remove(edit.getReference()); } } /** * Form a string of n question marks with commas, for sql value statements, * one for each item in the values array, or an empty string if null. * * @param values * The values to be inserted into the sql statement. * @return A sql statement fragment for the values part of an insert, one * for each value in the array. */ protected String valuesParams(String[] fields) { if ((fields == null) || (fields.length == 0)) return ""; StringBuilder buf = new StringBuilder(); for (int i = 0; i < fields.length; i++) { buf.append(" ?,"); } return buf.toString(); } /** * Form a string of n name=?, for sql update set statements, one for each * item in the values array, or an empty string if null. * * @param values * The values to be inserted into the sql statement. * @return A sql statement fragment for the values part of an insert, one * for each value in the array. */ protected String updateSet(String[] fields) { if ((fields == null) || (fields.length == 0)) return ""; StringBuilder buf = new StringBuilder(); for (int i = 0; i < fields.length; i++) { buf.append(fields[i] + " = ?,"); } return buf.toString(); } /** * Form a string of (field, field, field), for sql insert statements, one * for each item in the fields array, plus one before, and one after. * * @param before * The first field name. * @param values * The extra field names, in the middle. * @param after * The last field name. * @return A sql statement fragment for the insert fields. */ protected String insertFields(String before, String[] fields, String after) { StringBuilder buf = new StringBuilder(); buf.append(" ("); buf.append(before); buf.append(","); if (fields != null) { for (int i = 0; i < fields.length; i++) { buf.append(fields[i] + ","); } } buf.append(after); buf.append(")"); return buf.toString(); } /** * Fix the case of resource ids to support case insensitive ids if enabled * * @param The * id to fix. * @return The id, case modified as needed. */ protected String caseId(String id) { if (m_caseInsensitive) { return id.toLowerCase(); } return id; } /** * Enable / disable case insensitive ids. * * @param setting * true to set case insensitivity, false to set case sensitivity. */ protected void setCaseInsensitivity(boolean setting) { m_caseInsensitive = setting; } /** * Return a record ID to use internally in the database. This is needed for * databases (MySQL) that have limits on key lengths. The hash code ensures * that the record ID will be unique, even if the DB only considers a prefix * of a very long record ID. * * @param recordId * @return The record ID to use internally in the database */ private String internalRecordId(String recordId) { if ("mysql".equals(m_sql.getVendor())) { if (recordId == null) recordId = "null"; return recordId.hashCode() + " - " + recordId; } else // oracle, hsqldb { return recordId; } } /** * @param entry * @return */ private Object getBlob(Entity entry) { Runtime r = Runtime.getRuntime(); long ms = r.freeMemory(); long start = System.currentTimeMillis(); try { EntityReader er_user = (EntityReader) m_user; try { EntityReaderHandler erHandler = er_user.getHandler(); return erHandler.serialize(entry); } catch (EntityParseException ep) { M_log.warn("Unable to Serialize Entity, falling back to XML " + entry.getId(), ep); } return null; } finally { long t = System.currentTimeMillis() - start; long me = r.freeMemory(); long md = ms - me; if (md >= 0) { mtotal += md; } else { if (ntime != 0) { mtotal += (mtotal / ntime); } } ttotal += t; ntime++; if (ntime % 100 == 0) { double a = (1.0 * ttotal) / (1.0 * ntime); double m = (1.0 * mtotal) / (1.0 * ntime); M_log.debug("Average Serialization now " + (a) + "ms " + m + " bytes"); } } } }