0001: /**********************************************************************************
0002: * $URL: https://source.sakaiproject.org/svn/db/tags/sakai_2-4-1/db-util/storage/src/java/org/sakaiproject/util/BaseDbSingleStorage.java $
0003: * $Id: BaseDbSingleStorage.java 7084 2006-03-28 00:27:56Z ggolden@umich.edu $
0004: ***********************************************************************************
0005: *
0006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
0007: *
0008: * Licensed under the Educational Community License, Version 1.0 (the "License");
0009: * you may not use this file except in compliance with the License.
0010: * You may obtain a copy of the License at
0011: *
0012: * http://www.opensource.org/licenses/ecl1.php
0013: *
0014: * Unless required by applicable law or agreed to in writing, software
0015: * distributed under the License is distributed on an "AS IS" BASIS,
0016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: * See the License for the specific language governing permissions and
0018: * limitations under the License.
0019: *
0020: **********************************************************************************/package org.sakaiproject.util;
0021:
0022: import java.sql.Connection;
0023: import java.sql.ResultSet;
0024: import java.sql.SQLException;
0025: import java.util.Hashtable;
0026: import java.util.List;
0027: import java.util.Stack;
0028: import java.util.Vector;
0029:
0030: import org.apache.commons.logging.Log;
0031: import org.apache.commons.logging.LogFactory;
0032: import org.sakaiproject.db.api.SqlReader;
0033: import org.sakaiproject.db.api.SqlService;
0034: import org.sakaiproject.entity.api.Edit;
0035: import org.sakaiproject.entity.api.Entity;
0036: import org.sakaiproject.event.cover.UsageSessionService;
0037: import org.sakaiproject.javax.Filter;
0038: import org.sakaiproject.time.cover.TimeService;
0039: import org.w3c.dom.Document;
0040: import org.w3c.dom.Element;
0041:
0042: /**
0043: * <p>
0044: * BaseDbSingleStorage is a class that stores Resources (of some type) in a database, <br />
0045: * provides locked access, and generally implements a services "storage" class. The <br />
0046: * service's storage class can extend this to provide covers to turn Resource and <br />
0047: * Edit into something more type specific to the service.
0048: * </p>
0049: * <p>
0050: * 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
0051: * resource's id cannot change.
0052: * </p>
0053: * <p>
0054: * In order to handle Unicode characters properly, the SQL statements executed by this class <br />
0055: * should not embed Unicode characters into the SQL statement text; rather, Unicode values <br />
0056: * should be inserted as fields in a PreparedStatement. Databases handle Unicode better in fields.
0057: * </p>
0058: */
0059: public class BaseDbSingleStorage {
0060: /** Our logger. */
0061: private static Log M_log = LogFactory
0062: .getLog(BaseDbSingleStorage.class);
0063:
0064: /** Table name for resource records. */
0065: protected String m_resourceTableName = null;
0066:
0067: /** The field in the resource table that holds the resource id. */
0068: protected String m_resourceTableIdField = null;
0069:
0070: /** The additional field names in the resource table that go between the two ids and the xml */
0071: protected String[] m_resourceTableOtherFields = null;
0072:
0073: /** The xml tag name for the element holding each actual resource entry. */
0074: protected String m_resourceEntryTagName = null;
0075:
0076: /** If true, we do our locks in the remote database. */
0077: protected boolean m_locksAreInDb = false;
0078:
0079: /** If true, we do our locks in the remove database using a separate locking table. */
0080: protected boolean m_locksAreInTable = true;
0081:
0082: /** The StorageUser to callback for new Resource and Edit objects. */
0083: protected StorageUser m_user = null;
0084:
0085: /**
0086: * Locks, keyed by reference, holding Connections (or, if locks are done locally, holding an Edit).
0087: */
0088: protected Hashtable m_locks = null;
0089:
0090: /** If set, we treat reasource ids as case insensitive. */
0091: protected boolean m_caseInsensitive = false;
0092:
0093: /** Injected (by constructor) SqlService. */
0094: protected SqlService m_sql = null;
0095:
0096: /**
0097: * Construct.
0098: *
0099: * @param resourceTableName
0100: * Table name for resources.
0101: * @param resourceTableIdField
0102: * The field in the resource table that holds the id.
0103: * @param resourceTableOtherFields
0104: * The other fields in the resource table (between the two id and the xml fields).
0105: * @param locksInDb
0106: * If true, we do our locks in the remote database, otherwise we do them here.
0107: * @param resourceEntryName
0108: * The xml tag name for the element holding each actual resource entry.
0109: * @param user
0110: * The StorageUser class to call back for creation of Resource and Edit objects.
0111: * @param sqlService
0112: * The SqlService.
0113: */
0114: public BaseDbSingleStorage(String resourceTableName,
0115: String resourceTableIdField,
0116: String[] resourceTableOtherFields, boolean locksInDb,
0117: String resourceEntryName, StorageUser user,
0118: SqlService sqlService) {
0119: m_resourceTableName = resourceTableName;
0120: m_resourceTableIdField = resourceTableIdField;
0121: m_resourceTableOtherFields = resourceTableOtherFields;
0122: m_locksAreInDb = locksInDb;
0123: m_resourceEntryTagName = resourceEntryName;
0124: m_user = user;
0125: m_sql = sqlService;
0126: }
0127:
0128: /**
0129: * Open and be ready to read / write.
0130: */
0131: public void open() {
0132: // setup for locks
0133: m_locks = new Hashtable();
0134: }
0135:
0136: /**
0137: * Close.
0138: */
0139: public void close() {
0140: if (!m_locks.isEmpty()) {
0141: M_log.warn("close(): locks remain!");
0142: // %%%
0143: }
0144: m_locks.clear();
0145: m_locks = null;
0146: }
0147:
0148: /**
0149: * Read one Resource from xml
0150: *
0151: * @param xml
0152: * An string containing the xml which describes the resource.
0153: * @return The Resource object created from the xml.
0154: */
0155: protected Entity readResource(String xml) {
0156: try {
0157: // read the xml
0158: Document doc = Xml.readDocumentFromString(xml);
0159:
0160: // verify the root element
0161: Element root = doc.getDocumentElement();
0162: if (!root.getTagName().equals(m_resourceEntryTagName)) {
0163: M_log.warn("readResource(): not = "
0164: + m_resourceEntryTagName + " : "
0165: + root.getTagName());
0166: return null;
0167: }
0168:
0169: // re-create a resource
0170: Entity entry = m_user.newResource(null, root);
0171:
0172: return entry;
0173: } catch (Exception e) {
0174: M_log.debug("readResource(): ", e);
0175: return null;
0176: }
0177: }
0178:
0179: /**
0180: * Check if a Resource by this id exists.
0181: *
0182: * @param id
0183: * The id.
0184: * @return true if a Resource by this id exists, false if not.
0185: */
0186: public boolean checkResource(String id) {
0187: // just see if the record exists
0188: String sql = "select " + m_resourceTableIdField + " from "
0189: + m_resourceTableName + " where ( "
0190: + m_resourceTableIdField + " = ? )";
0191:
0192: Object fields[] = new Object[1];
0193: fields[0] = caseId(id);
0194: List ids = m_sql.dbRead(sql, fields, null);
0195:
0196: return (!ids.isEmpty());
0197: }
0198:
0199: /**
0200: * Get the Resource with this id, or null if not found.
0201: *
0202: * @param id
0203: * The id.
0204: * @return The Resource with this id, or null if not found.
0205: */
0206: public Entity getResource(String id) {
0207: Entity entry = null;
0208:
0209: // get the user from the db
0210: String sql = "select XML from " + m_resourceTableName
0211: + " where ( " + m_resourceTableIdField + " = ? )";
0212:
0213: Object fields[] = new Object[1];
0214: fields[0] = caseId(id);
0215: List xml = m_sql.dbRead(sql, fields, null);
0216: if (!xml.isEmpty()) {
0217: // create the Resource from the db xml
0218: entry = readResource((String) xml.get(0));
0219: }
0220:
0221: return entry;
0222: }
0223:
0224: public boolean isEmpty() {
0225: // count
0226: int count = countAllResources();
0227: return (count == 0);
0228: }
0229:
0230: public List getAllResources() {
0231: List all = new Vector();
0232:
0233: // read all users from the db
0234: String sql = "select XML from " + m_resourceTableName;
0235: // %%% + "order by " + m_resourceTableOrderField + " asc";
0236:
0237: List xml = m_sql.dbRead(sql);
0238:
0239: // process all result xml into user objects
0240: if (!xml.isEmpty()) {
0241: for (int i = 0; i < xml.size(); i++) {
0242: Entity entry = readResource((String) xml.get(i));
0243: if (entry != null)
0244: all.add(entry);
0245: }
0246: }
0247:
0248: return all;
0249: }
0250:
0251: public List getAllResources(int first, int last) {
0252: String sql;
0253: Object[] fields = null;
0254: if ("oracle".equals(m_sql.getVendor())) {
0255: // use Oracle RANK function
0256: sql = "select XML from" + " (select XML" + " ,RANK() OVER"
0257: + " (order by " + m_resourceTableIdField
0258: + ") as rank" + " from " + m_resourceTableName
0259: + " order by " + m_resourceTableIdField + " asc)"
0260: + " where rank between ? and ?";
0261: fields = new Object[2];
0262: fields[0] = new Long(first);
0263: fields[1] = new Long(last);
0264: } else if ("mysql".equals(m_sql.getVendor())) {
0265: // use MySQL SQL LIMIT clause
0266: sql = "select XML from " + m_resourceTableName
0267: + " order by " + m_resourceTableIdField + " asc "
0268: + " limit " + (last - first + 1) + " offset "
0269: + (first - 1);
0270: } else
0271: // if ("hsqldb".equals(m_sql.getVendor()))
0272: {
0273: // use SQL2000 LIMIT clause
0274: sql = "select " + "limit " + (first - 1) + " "
0275: + (last - first + 1) + " " + "XML from "
0276: + m_resourceTableName + " order by "
0277: + m_resourceTableIdField + " asc";
0278: }
0279:
0280: List xml = m_sql.dbRead(sql, fields, null);
0281: List rv = new Vector();
0282:
0283: // process all result xml into user objects
0284: if (!xml.isEmpty()) {
0285: for (int i = 0; i < xml.size(); i++) {
0286: Entity entry = readResource((String) xml.get(i));
0287: if (entry != null)
0288: rv.add(entry);
0289: }
0290: }
0291:
0292: return rv;
0293: }
0294:
0295: public int countAllResources() {
0296: List all = new Vector();
0297:
0298: // read all count
0299: String sql = "select count(1) from " + m_resourceTableName;
0300:
0301: List results = m_sql.dbRead(sql, null, new SqlReader() {
0302: public Object readSqlResultRecord(ResultSet result) {
0303: try {
0304: int count = result.getInt(1);
0305: return new Integer(count);
0306: } catch (SQLException ignore) {
0307: return null;
0308: }
0309: }
0310: });
0311:
0312: if (results.isEmpty())
0313: return 0;
0314:
0315: return ((Integer) results.get(0)).intValue();
0316: }
0317:
0318: public int countSelectedResourcesWhere(String sqlWhere) {
0319: List all = new Vector();
0320:
0321: // read all where count
0322: String sql = "select count(1) from " + m_resourceTableName
0323: + " " + sqlWhere;
0324: List results = m_sql.dbRead(sql, null, new SqlReader() {
0325: public Object readSqlResultRecord(ResultSet result) {
0326: try {
0327: int count = result.getInt(1);
0328: return new Integer(count);
0329: } catch (SQLException ignore) {
0330: return null;
0331: }
0332: }
0333: });
0334:
0335: if (results.isEmpty())
0336: return 0;
0337:
0338: return ((Integer) results.get(0)).intValue();
0339: }
0340:
0341: /**
0342: * Get all Resources where the given field matches the given value.
0343: *
0344: * @param field
0345: * The db field name for the selection.
0346: * @param value
0347: * The value to select.
0348: * @return The list of all Resources that meet the criteria.
0349: */
0350: public List getAllResourcesWhere(String field, String value) {
0351: // read all users from the db
0352: String sql = "select XML from " + m_resourceTableName
0353: + " where " + field + " = ?";
0354: Object[] fields = new Object[1];
0355: fields[0] = value;
0356: // %%% + "order by " + m_resourceTableOrderField + " asc";
0357:
0358: return loadResources(sql, fields);
0359: }
0360:
0361: protected List loadResources(String sql, Object[] fields) {
0362: List all = m_sql.dbRead(sql, fields, new SqlReader() {
0363: public Object readSqlResultRecord(ResultSet result) {
0364: try {
0365: // create the Resource from the db xml
0366: String xml = result.getString(1);
0367: Entity entry = readResource(xml);
0368: return entry;
0369: } catch (SQLException ignore) {
0370: return null;
0371: }
0372: }
0373: });
0374: return all;
0375: }
0376:
0377: public List getAllResourcesWhereLike(String field, String value) {
0378: String sql = "select XML from " + m_resourceTableName
0379: + " where " + field + " like ?";
0380: Object[] fields = new Object[1];
0381: fields[0] = value;
0382: // %%% + "order by " + m_resourceTableOrderField + " asc";
0383:
0384: return loadResources(sql, fields);
0385: }
0386:
0387: /**
0388: * Get selected Resources, filtered by a test on the id field
0389: *
0390: * @param filter
0391: * A filter to select what gets returned.
0392: * @return The list of selected Resources.
0393: */
0394: public List getSelectedResources(final Filter filter) {
0395: List all = new Vector();
0396:
0397: // read all users from the db
0398: String sql = "select " + m_resourceTableIdField + ", XML from "
0399: + m_resourceTableName;
0400: // %%% + "order by " + m_resourceTableOrderField + " asc";
0401:
0402: List xml = m_sql.dbRead(sql, null, new SqlReader() {
0403: public Object readSqlResultRecord(ResultSet result) {
0404: try {
0405: // read the id m_resourceTableIdField
0406: String id = result.getString(1);
0407:
0408: // read the xml
0409: String xml = result.getString(2);
0410:
0411: if (!filter.accept(caseId(id)))
0412: return null;
0413:
0414: return xml;
0415: } catch (SQLException ignore) {
0416: return null;
0417: }
0418: }
0419: });
0420:
0421: // process all result xml into user objects
0422: if (!xml.isEmpty()) {
0423: for (int i = 0; i < xml.size(); i++) {
0424: Entity entry = readResource((String) xml.get(i));
0425: if (entry != null)
0426: all.add(entry);
0427: }
0428: }
0429:
0430: return all;
0431: }
0432:
0433: /**
0434: * Get selected Resources, using a supplied where clause
0435: *
0436: * @param sqlWhere
0437: * The SQL where clause.
0438: * @return The list of selected Resources.
0439: */
0440: public List getSelectedResourcesWhere(String sqlWhere) {
0441: List all = new Vector();
0442:
0443: // read all users from the db
0444: String sql = "select XML from " + m_resourceTableName + " "
0445: + sqlWhere;
0446: // %%% + "order by " + m_resourceTableOrderField + " asc";
0447:
0448: List xml = m_sql.dbRead(sql);
0449:
0450: // process all result xml into user objects
0451: if (!xml.isEmpty()) {
0452: for (int i = 0; i < xml.size(); i++) {
0453: Entity entry = readResource((String) xml.get(i));
0454: if (entry != null)
0455: all.add(entry);
0456: }
0457: }
0458:
0459: return all;
0460: }
0461:
0462: /**
0463: * Add a new Resource with this id.
0464: *
0465: * @param id
0466: * The id.
0467: * @param others
0468: * Other fields for the newResource call
0469: * @return The locked Resource object with this id, or null if the id is in use.
0470: */
0471: public Edit putResource(String id, Object[] others) {
0472: // create one with just the id, and perhaps some other fields as well
0473: Entity entry = m_user.newResource(null, id, others);
0474:
0475: // form the XML and SQL for the insert
0476: Document doc = Xml.createDocument();
0477: entry.toXml(doc, new Stack());
0478: String xml = Xml.writeDocumentToString(doc);
0479: String statement = "insert into "
0480: + m_resourceTableName
0481: + insertFields(m_resourceTableIdField,
0482: m_resourceTableOtherFields, "XML")
0483: + " values ( ?, "
0484: + valuesParams(m_resourceTableOtherFields) + " ? )";
0485:
0486: Object[] flds = m_user.storageFields(entry);
0487: if (flds == null)
0488: flds = new Object[0];
0489: Object[] fields = new Object[flds.length + 2];
0490: System.arraycopy(flds, 0, fields, 1, flds.length);
0491: fields[0] = caseId(entry.getId());
0492: fields[fields.length - 1] = xml;
0493:
0494: // process the insert
0495: boolean ok = m_sql.dbWrite(statement, fields);
0496:
0497: // if this failed, assume a key conflict (i.e. id in use)
0498: if (!ok)
0499: return null;
0500:
0501: // now get a lock on the record for edit
0502: Edit edit = editResource(id);
0503: if (edit == null) {
0504: M_log.warn("putResource(): didn't get a lock!");
0505: return null;
0506: }
0507:
0508: return edit;
0509: }
0510:
0511: /** store the record in content_resource_delete table along with resource_uuid and date */
0512: public Edit putDeleteResource(String id, String uuid,
0513: String userId, Object[] others) {
0514: Entity entry = m_user.newResource(null, id, others);
0515:
0516: // form the XML and SQL for the insert
0517: Document doc = Xml.createDocument();
0518: entry.toXml(doc, new Stack());
0519: String xml = Xml.writeDocumentToString(doc);
0520: String statement = "insert into "
0521: + m_resourceTableName
0522: + insertDeleteFields(m_resourceTableIdField,
0523: m_resourceTableOtherFields, "RESOURCE_UUID",
0524: "DELETE_DATE", "DELETE_USERID", "XML")
0525: + " values ( ?, "
0526: + valuesParams(m_resourceTableOtherFields)
0527: + " ? ,? ,? ,?)";
0528:
0529: Object[] flds = m_user.storageFields(entry);
0530: if (flds == null)
0531: flds = new Object[0];
0532: Object[] fields = new Object[flds.length + 5];
0533: System.arraycopy(flds, 0, fields, 1, flds.length);
0534: fields[0] = caseId(entry.getId());
0535: // uuid added here
0536: fields[fields.length - 4] = uuid;
0537: // date added here
0538: fields[fields.length - 3] = TimeService.newTime();// .toStringLocalDate();
0539:
0540: // userId added here
0541: fields[fields.length - 2] = userId;
0542: fields[fields.length - 1] = xml;
0543:
0544: // process the insert
0545: boolean ok = m_sql.dbWrite(statement, fields);
0546:
0547: // if this failed, assume a key conflict (i.e. id in use)
0548: if (!ok)
0549: return null;
0550:
0551: // now get a lock on the record for edit
0552: Edit edit = editResource(id);
0553: if (edit == null) {
0554: M_log.warn("putResourceDelete(): didn't get a lock!");
0555: return null;
0556: }
0557:
0558: return edit;
0559: }
0560:
0561: /** Construct the SQL statement */
0562: protected String insertDeleteFields(String before, String[] fields,
0563: String uuid, String date, String userId, String after) {
0564: StringBuffer buf = new StringBuffer();
0565: buf.append(" (");
0566: buf.append(before);
0567: buf.append(",");
0568: if (fields != null) {
0569: for (int i = 0; i < fields.length; i++) {
0570: buf.append(fields[i] + ",");
0571: }
0572: }
0573: buf.append(uuid);
0574: buf.append(",");
0575: buf.append(date);
0576: buf.append(",");
0577: buf.append(userId);
0578: buf.append(",");
0579: buf.append(after);
0580: buf.append(")");
0581:
0582: return buf.toString();
0583: }
0584:
0585: /** update XML attribute on properties and remove locks */
0586: public void commitDeleteResource(Edit edit, String uuid) {
0587: // form the SQL statement and the var w/ the XML
0588: Document doc = Xml.createDocument();
0589: edit.toXml(doc, new Stack());
0590: String xml = Xml.writeDocumentToString(doc);
0591: Object[] flds = m_user.storageFields(edit);
0592: if (flds == null)
0593: flds = new Object[0];
0594: Object[] fields = new Object[flds.length + 2];
0595: System.arraycopy(flds, 0, fields, 0, flds.length);
0596: fields[fields.length - 2] = xml;
0597: fields[fields.length - 1] = uuid;// caseId(edit.getId());
0598:
0599: String statement = "update " + m_resourceTableName + " set "
0600: + updateSet(m_resourceTableOtherFields) + " XML = ?"
0601: + " where ( RESOURCE_UUID = ? )";
0602:
0603: if (m_locksAreInDb) {
0604: // use this connection that is stored with the lock
0605: Connection lock = (Connection) m_locks.get(edit
0606: .getReference());
0607: if (lock == null) {
0608: M_log.warn("commitResource(): edit not in locks");
0609: return;
0610: }
0611: // update, commit, release the lock's connection
0612: m_sql.dbUpdateCommit(statement, fields, null, lock);
0613: // remove the lock
0614: m_locks.remove(edit.getReference());
0615: }
0616:
0617: else if (m_locksAreInTable) {
0618: // process the update
0619: m_sql.dbWrite(statement, fields);
0620:
0621: // remove the lock
0622: statement = "delete from SAKAI_LOCKS where TABLE_NAME = ? and RECORD_ID = ?";
0623:
0624: // collect the fields
0625: Object lockFields[] = new Object[2];
0626: lockFields[0] = m_resourceTableName;
0627: lockFields[1] = internalRecordId(caseId(edit.getId()));
0628: boolean ok = m_sql.dbWrite(statement, lockFields);
0629: if (!ok) {
0630: M_log.warn("commit: missing lock for table: "
0631: + lockFields[0] + " key: " + lockFields[1]);
0632: }
0633: } else {
0634: // just process the update
0635: m_sql.dbWrite(statement, fields);
0636:
0637: // remove the lock
0638: m_locks.remove(edit.getReference());
0639: }
0640: }
0641:
0642: /**
0643: * Get a lock on the Resource with this id, or null if a lock cannot be gotten.
0644: *
0645: * @param id
0646: * The user id.
0647: * @return The locked Resource with this id, or null if this records cannot be locked.
0648: */
0649: public Edit editResource(String id) {
0650: Edit edit = null;
0651:
0652: if (m_locksAreInDb) {
0653: if ("oracle".equals(m_sql.getVendor())) {
0654: // read the record and get a lock on it (non blocking)
0655: String statement = "select XML from "
0656: + m_resourceTableName + " where ( "
0657: + m_resourceTableIdField + " = '"
0658: + Validator.escapeSql(caseId(id)) + "' )"
0659: + " for update nowait";
0660: StringBuffer result = new StringBuffer();
0661: Connection lock = m_sql.dbReadLock(statement, result);
0662:
0663: // for missing or already locked...
0664: if ((lock == null) || (result.length() == 0))
0665: return null;
0666:
0667: // make first a Resource, then an Edit
0668: Entity entry = readResource(result.toString());
0669: edit = m_user.newResourceEdit(null, entry);
0670:
0671: // store the lock for this object
0672: m_locks.put(entry.getReference(), lock);
0673: } else {
0674: throw new UnsupportedOperationException(
0675: "Record locking only available when configured with Oracle database");
0676: }
0677: }
0678:
0679: // if the locks are in a separate table in the db
0680: else if (m_locksAreInTable) {
0681: // read the record - fail if not there
0682: Entity entry = getResource(id);
0683: if (entry == null)
0684: return null;
0685:
0686: // write a lock to the lock table - if we can do it, we get the lock
0687: String statement = "insert into SAKAI_LOCKS"
0688: + " (TABLE_NAME,RECORD_ID,LOCK_TIME,USAGE_SESSION_ID)"
0689: + " values (?, ?, ?, ?)";
0690:
0691: // we need session id and user id
0692: String sessionId = UsageSessionService.getSessionId();
0693: if (sessionId == null) {
0694: sessionId = "";
0695: }
0696:
0697: // collect the fields
0698: Object fields[] = new Object[4];
0699: fields[0] = m_resourceTableName;
0700: fields[1] = internalRecordId(caseId(id));
0701: fields[2] = TimeService.newTime();
0702: fields[3] = sessionId;
0703:
0704: // add the lock - if fails, someone else has the lock
0705: boolean ok = m_sql
0706: .dbWriteFailQuiet(null, statement, fields);
0707: if (!ok) {
0708: return null;
0709: }
0710:
0711: // we got the lock! - make the edit from the Resource
0712: edit = m_user.newResourceEdit(null, entry);
0713: }
0714:
0715: // otherwise, get the lock locally
0716: else {
0717: // get the entry, and check for existence
0718: Entity entry = getResource(id);
0719: if (entry == null)
0720: return null;
0721:
0722: // we only sync this getting - someone may release a lock out of sync
0723: synchronized (m_locks) {
0724: // if already locked
0725: if (m_locks.containsKey(entry.getReference()))
0726: return null;
0727:
0728: // make the edit from the Resource
0729: edit = m_user.newResourceEdit(null, entry);
0730:
0731: // store the edit in the locks by reference
0732: m_locks.put(entry.getReference(), edit);
0733: }
0734: }
0735:
0736: return edit;
0737: }
0738:
0739: /**
0740: * Commit the changes and release the lock.
0741: *
0742: * @param user
0743: * The Edit to commit.
0744: */
0745: public void commitResource(Edit edit) {
0746: // form the SQL statement and the var w/ the XML
0747: Document doc = Xml.createDocument();
0748: edit.toXml(doc, new Stack());
0749: String xml = Xml.writeDocumentToString(doc);
0750: Object[] flds = m_user.storageFields(edit);
0751: if (flds == null)
0752: flds = new Object[0];
0753: Object[] fields = new Object[flds.length + 2];
0754: System.arraycopy(flds, 0, fields, 0, flds.length);
0755: fields[fields.length - 2] = xml;
0756: fields[fields.length - 1] = caseId(edit.getId());
0757:
0758: String statement = "update " + m_resourceTableName + " set "
0759: + updateSet(m_resourceTableOtherFields) + " XML = ?"
0760: + " where ( " + m_resourceTableIdField + " = ? )";
0761:
0762: if (m_locksAreInDb) {
0763: // use this connection that is stored with the lock
0764: Connection lock = (Connection) m_locks.get(edit
0765: .getReference());
0766: if (lock == null) {
0767: M_log.warn("commitResource(): edit not in locks");
0768: return;
0769: }
0770:
0771: // update, commit, release the lock's connection
0772: m_sql.dbUpdateCommit(statement, fields, null, lock);
0773:
0774: // remove the lock
0775: m_locks.remove(edit.getReference());
0776: }
0777:
0778: else if (m_locksAreInTable) {
0779: // process the update
0780: m_sql.dbWrite(statement, fields);
0781:
0782: // remove the lock
0783: statement = "delete from SAKAI_LOCKS where TABLE_NAME = ? and RECORD_ID = ?";
0784:
0785: // collect the fields
0786: Object lockFields[] = new Object[2];
0787: lockFields[0] = m_resourceTableName;
0788: lockFields[1] = internalRecordId(caseId(edit.getId()));
0789: boolean ok = m_sql.dbWrite(statement, lockFields);
0790: if (!ok) {
0791: M_log.warn("commit: missing lock for table: "
0792: + lockFields[0] + " key: " + lockFields[1]);
0793: }
0794: }
0795:
0796: else {
0797: // just process the update
0798: m_sql.dbWrite(statement, fields);
0799:
0800: // remove the lock
0801: m_locks.remove(edit.getReference());
0802: }
0803: }
0804:
0805: /**
0806: * Cancel the changes and release the lock.
0807: *
0808: * @param user
0809: * The Edit to cancel.
0810: */
0811: public void cancelResource(Edit edit) {
0812: if (m_locksAreInDb) {
0813: // use this connection that is stored with the lock
0814: Connection lock = (Connection) m_locks.get(edit
0815: .getReference());
0816: if (lock == null) {
0817: M_log.warn("cancelResource(): edit not in locks");
0818: return;
0819: }
0820:
0821: // rollback and release the lock's connection
0822: m_sql.dbCancel(lock);
0823:
0824: // release the lock
0825: m_locks.remove(edit.getReference());
0826: }
0827:
0828: else if (m_locksAreInTable) {
0829: // remove the lock
0830: String statement = "delete from SAKAI_LOCKS where TABLE_NAME = ? and RECORD_ID = ?";
0831:
0832: // collect the fields
0833: Object lockFields[] = new Object[2];
0834: lockFields[0] = m_resourceTableName;
0835: lockFields[1] = internalRecordId(caseId(edit.getId()));
0836: boolean ok = m_sql.dbWrite(statement, lockFields);
0837: if (!ok) {
0838: M_log.warn("cancel: missing lock for table: "
0839: + lockFields[0] + " key: " + lockFields[1]);
0840: }
0841: }
0842:
0843: else {
0844: // release the lock
0845: m_locks.remove(edit.getReference());
0846: }
0847: }
0848:
0849: /**
0850: * Remove this (locked) Resource.
0851: *
0852: * @param user
0853: * The Edit to remove.
0854: */
0855: public void removeResource(Edit edit) {
0856: // form the SQL delete statement
0857: String statement = "delete from " + m_resourceTableName
0858: + " where ( " + m_resourceTableIdField + " = ? )";
0859:
0860: Object fields[] = new Object[1];
0861: fields[0] = caseId(edit.getId());
0862:
0863: if (m_locksAreInDb) {
0864: // use this connection that is stored with the lock
0865: Connection lock = (Connection) m_locks.get(edit
0866: .getReference());
0867: if (lock == null) {
0868: M_log.warn("removeResource(): edit not in locks");
0869: return;
0870: }
0871:
0872: // process the delete statement, commit, and release the lock's connection
0873: m_sql.dbUpdateCommit(statement, fields, null, lock);
0874:
0875: // release the lock
0876: m_locks.remove(edit.getReference());
0877: }
0878:
0879: else if (m_locksAreInTable) {
0880: // process the delete statement
0881: m_sql.dbWrite(statement, fields);
0882:
0883: // remove the lock
0884: statement = "delete from SAKAI_LOCKS where TABLE_NAME = ? and RECORD_ID = ?";
0885:
0886: // collect the fields
0887: Object lockFields[] = new Object[2];
0888: lockFields[0] = m_resourceTableName;
0889: lockFields[1] = internalRecordId(caseId(edit.getId()));
0890: boolean ok = m_sql.dbWrite(statement, lockFields);
0891: if (!ok) {
0892: M_log.warn("remove: missing lock for table: "
0893: + lockFields[0] + " key: " + lockFields[1]);
0894: }
0895: }
0896:
0897: else {
0898: // process the delete statement
0899: m_sql.dbWrite(statement, fields);
0900:
0901: // release the lock
0902: m_locks.remove(edit.getReference());
0903: }
0904: }
0905:
0906: /**
0907: * 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.
0908: *
0909: * @param values
0910: * The values to be inserted into the sql statement.
0911: * @return A sql statement fragment for the values part of an insert, one for each value in the array.
0912: */
0913: protected String valuesParams(String[] fields) {
0914: if ((fields == null) || (fields.length == 0))
0915: return "";
0916: StringBuffer buf = new StringBuffer();
0917: for (int i = 0; i < fields.length; i++) {
0918: buf.append(" ?,");
0919: }
0920: return buf.toString();
0921: }
0922:
0923: /**
0924: * 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.
0925: *
0926: * @param values
0927: * The values to be inserted into the sql statement.
0928: * @return A sql statement fragment for the values part of an insert, one for each value in the array.
0929: */
0930: protected String updateSet(String[] fields) {
0931: if ((fields == null) || (fields.length == 0))
0932: return "";
0933: StringBuffer buf = new StringBuffer();
0934: for (int i = 0; i < fields.length; i++) {
0935: buf.append(fields[i] + " = ?,");
0936: }
0937: return buf.toString();
0938: }
0939:
0940: /**
0941: * 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.
0942: *
0943: * @param before
0944: * The first field name.
0945: * @param values
0946: * The extra field names, in the middle.
0947: * @param after
0948: * The last field name.
0949: * @return A sql statement fragment for the insert fields.
0950: */
0951: protected String insertFields(String before, String[] fields,
0952: String after) {
0953: StringBuffer buf = new StringBuffer();
0954: buf.append(" (");
0955:
0956: buf.append(before);
0957: buf.append(",");
0958:
0959: if (fields != null) {
0960: for (int i = 0; i < fields.length; i++) {
0961: buf.append(fields[i] + ",");
0962: }
0963: }
0964:
0965: buf.append(after);
0966:
0967: buf.append(")");
0968:
0969: return buf.toString();
0970: }
0971:
0972: /**
0973: * Fix the case of resource ids to support case insensitive ids if enabled
0974: *
0975: * @param The
0976: * id to fix.
0977: * @return The id, case modified as needed.
0978: */
0979: protected String caseId(String id) {
0980: if (m_caseInsensitive) {
0981: return id.toLowerCase();
0982: }
0983:
0984: return id;
0985: }
0986:
0987: /**
0988: * Enable / disable case insensitive ids.
0989: *
0990: * @param setting
0991: * true to set case insensitivity, false to set case sensitivity.
0992: */
0993: protected void setCaseInsensitivity(boolean setting) {
0994: m_caseInsensitive = setting;
0995: }
0996:
0997: /**
0998: * 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.
0999: *
1000: * @param recordId
1001: * @return The record ID to use internally in the database
1002: */
1003: private String internalRecordId(String recordId) {
1004: if ("mysql".equals(m_sql.getVendor())) {
1005: if (recordId == null)
1006: recordId = "null";
1007: return recordId.hashCode() + " - " + recordId;
1008: } else
1009: // oracle, hsqldb
1010: {
1011: return recordId;
1012: }
1013: }
1014: }
|