001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/alias/tags/sakai_2-4-1/alias-impl/impl/src/java/org/sakaiproject/alias/impl/DbAliasService.java $
003: * $Id: DbAliasService.java 7314 2006-04-01 19:58:32Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.alias.impl;
021:
022: import java.sql.Connection;
023: import java.sql.ResultSet;
024: import java.sql.SQLException;
025: import java.util.Collections;
026: import java.util.HashSet;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Set;
030: import java.util.Vector;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.commons.logging.LogFactory;
034: import org.sakaiproject.alias.api.Alias;
035: import org.sakaiproject.alias.api.AliasEdit;
036: import org.sakaiproject.db.api.SqlReader;
037: import org.sakaiproject.db.api.SqlService;
038: import org.sakaiproject.entity.api.ResourceProperties;
039: import org.sakaiproject.entity.api.ResourcePropertiesEdit;
040: import org.sakaiproject.time.api.Time;
041: import org.sakaiproject.util.BaseDbFlatStorage;
042: import org.sakaiproject.util.BaseDbSingleStorage;
043: import org.sakaiproject.util.StorageUser;
044: import org.sakaiproject.util.StringUtil;
045: import org.sakaiproject.util.Xml;
046: import org.w3c.dom.Document;
047: import org.w3c.dom.Element;
048:
049: /**
050: * <p>
051: * DbAliasService is an extension of the BaseAliasService with a database storage. Fields are fully relational. Full properties are not yet supported - core ones are. Code to find and convert records from before, from the XML based CHEF_ALIAS table is
052: * included.
053: * </p>
054: */
055: public abstract class DbAliasService extends BaseAliasService {
056: /** Our logger. */
057: private static Log M_log = LogFactory.getLog(DbAliasService.class);
058:
059: /** Table name for aliases. */
060: protected String m_tableName = "SAKAI_ALIAS";
061:
062: /** Table name for properties. */
063: protected String m_propTableName = "SAKAI_ALIAS_PROPERTY";
064:
065: /** ID field. */
066: protected String m_idFieldName = "ALIAS_ID";
067:
068: /** All fields. */
069: protected String[] m_fieldNames = { "ALIAS_ID", "TARGET",
070: "CREATEDBY", "MODIFIEDBY", "CREATEDON", "MODIFIEDON" };
071:
072: /** If true, we do our locks in the remote database, otherwise we do them here. */
073: protected boolean m_useExternalLocks = true;
074:
075: /** Set if we are to run the from-old conversion. */
076: protected boolean m_convertOld = false;
077:
078: /**
079: * Configuration: run the from-old conversion.
080: *
081: * @param value
082: * The conversion desired value.
083: */
084: public void setConvertOld(String value) {
085: m_convertOld = new Boolean(value).booleanValue();
086: }
087:
088: /**********************************************************************************************************************************************************************************************************************************************************
089: * Dependencies
090: *********************************************************************************************************************************************************************************************************************************************************/
091:
092: /**
093: * @return the MemoryService collaborator.
094: */
095: protected abstract SqlService sqlService();
096:
097: /**********************************************************************************************************************************************************************************************************************************************************
098: * Configuration
099: *********************************************************************************************************************************************************************************************************************************************************/
100:
101: /**
102: * Configuration: set the external locks value.
103: *
104: * @param value
105: * The external locks value.
106: */
107: public void setExternalLocks(String value) {
108: m_useExternalLocks = new Boolean(value).booleanValue();
109: }
110:
111: /** Configuration: check the old table, too. */
112: protected boolean m_checkOld = false;
113:
114: /**
115: * Configuration: set the locks-in-db
116: *
117: * @param value
118: * The locks-in-db value.
119: */
120: public void setCheckOld(String value) {
121: m_checkOld = new Boolean(value).booleanValue();
122: }
123:
124: /** Configuration: to run the ddl on init or not. */
125: protected boolean m_autoDdl = false;
126:
127: /**
128: * Configuration: to run the ddl on init or not.
129: *
130: * @param value
131: * the auto ddl value.
132: */
133: public void setAutoDdl(String value) {
134: m_autoDdl = new Boolean(value).booleanValue();
135: }
136:
137: /**********************************************************************************************************************************************************************************************************************************************************
138: * Init and Destroy
139: *********************************************************************************************************************************************************************************************************************************************************/
140:
141: /**
142: * Final initialization, once all dependencies are set.
143: */
144: public void init() {
145: try {
146: // if we are auto-creating our schema, check and create
147: if (m_autoDdl) {
148: sqlService().ddl(this .getClass().getClassLoader(),
149: "sakai_alias");
150: }
151:
152: super .init();
153:
154: M_log.info("init(): table: " + m_tableName
155: + " external locks: " + m_useExternalLocks
156: + " checkOld: " + m_checkOld);
157:
158: // convert?
159: if (m_convertOld) {
160: m_convertOld = false;
161: convertOld();
162: }
163:
164: // do a count which might find no old records so we can ignore old!
165: if (m_checkOld) {
166: m_storage.count();
167: }
168: } catch (Throwable t) {
169: M_log.warn("init(): ", t);
170: }
171: }
172:
173: /**********************************************************************************************************************************************************************************************************************************************************
174: * BaseAliasService extensions
175: *********************************************************************************************************************************************************************************************************************************************************/
176:
177: /**
178: * Construct a Storage object.
179: *
180: * @return The new storage object.
181: */
182: protected Storage newStorage() {
183: return new DbStorage(this );
184:
185: } // newStorage
186:
187: /**********************************************************************************************************************************************************************************************************************************************************
188: * Storage implementation
189: *********************************************************************************************************************************************************************************************************************************************************/
190:
191: /**
192: * Covers for the BaseXmlFileStorage, providing User and AliasEdit parameters
193: */
194: protected class DbStorage extends BaseDbFlatStorage implements
195: Storage, SqlReader {
196: /** A prior version's storage model. */
197: protected Storage m_oldStorage = null;
198:
199: /**
200: * Construct.
201: *
202: * @param user
203: * The StorageUser class to call back for creation of Resource and Edit objects.
204: */
205: public DbStorage(StorageUser user) {
206: super (m_tableName, m_idFieldName, m_fieldNames,
207: m_propTableName, m_useExternalLocks, null,
208: sqlService());
209: m_reader = this ;
210: setCaseInsensitivity(true);
211:
212: // setup for old-new stradling
213: if (m_checkOld) {
214: m_oldStorage = new DbStorageOld(user);
215: }
216:
217: } // DbStorage
218:
219: public boolean check(String id) {
220: boolean rv = super .checkResource(id);
221:
222: // if not, check old
223: if (m_checkOld && (!rv)) {
224: rv = m_oldStorage.check(id);
225: }
226:
227: return rv;
228: }
229:
230: public AliasEdit get(String id) {
231: AliasEdit rv = (AliasEdit) super .getResource(id);
232:
233: // if not, check old
234: if (m_checkOld && (rv == null)) {
235: rv = m_oldStorage.get(id);
236: }
237: return rv;
238: }
239:
240: public List getAll() {
241: // if we have to be concerned with old stuff, we cannot let the db do the range selection
242: if (m_checkOld) {
243: List all = super .getAllResources();
244:
245: // add in any additional defined in old
246: Set merge = new HashSet();
247: merge.addAll(all);
248:
249: // add those in the old not already (id based equals) in all
250: List more = m_oldStorage.getAll();
251: merge.addAll(more);
252:
253: all.clear();
254: all.addAll(merge);
255:
256: return all;
257: }
258:
259: // let the db do range selection
260: List all = super .getAllResources();
261: return all;
262: }
263:
264: public List getAll(int first, int last) {
265: // if we have to be concerned with old stuff, we cannot let the db do the range selection
266: if (m_checkOld) {
267: List all = super .getAllResources();
268:
269: // add in any additional defined in old
270: Set merge = new HashSet();
271: merge.addAll(all);
272:
273: // add those in the old not already (id based equals) in all
274: List more = m_oldStorage.getAll();
275: merge.addAll(more);
276:
277: all.clear();
278: all.addAll(merge);
279:
280: Collections.sort(all);
281:
282: // subset by position
283: if (first < 1)
284: first = 1;
285: if (last >= all.size())
286: last = all.size();
287:
288: all = all.subList(first - 1, last);
289: return all;
290: }
291:
292: // let the db do range selection
293: List all = super .getAllResources(first, last);
294: return all;
295: }
296:
297: public int count() {
298: // if we have to be concerned with old stuff, we cannot let the db do all the counting
299: if (m_checkOld) {
300: int count = super .countAllResources();
301: count += m_oldStorage.count();
302:
303: return count;
304: }
305:
306: return super .countAllResources();
307: }
308:
309: public List getAll(String target) {
310: Object[] fields = new Object[1];
311: fields[0] = target;
312:
313: // if we have to be concerned with old stuff, we cannot let the db do the range selection
314: if (m_checkOld) {
315: List all = super .getSelectedResources("TARGET = ?",
316: fields);
317:
318: // add in any additional defined in old
319: Set merge = new HashSet();
320: merge.addAll(all);
321:
322: // add those in the old not already (id based equals) in all
323: List more = m_oldStorage.getAll(target);
324: merge.addAll(more);
325:
326: all.clear();
327: all.addAll(merge);
328:
329: return all;
330: }
331:
332: List all = super .getSelectedResources("TARGET = ?", fields);
333: return all;
334: }
335:
336: public List getAll(String target, int first, int last) {
337: Object[] fields = new Object[1];
338: fields[0] = target;
339:
340: // if we have to be concerned with old stuff, we cannot let the db do the range selection
341: if (m_checkOld) {
342: List all = super .getSelectedResources("TARGET = ?",
343: fields);
344:
345: // add in any additional defined in old
346: Set merge = new HashSet();
347: merge.addAll(all);
348:
349: // add those in the old not already (id based equals) in all
350: List more = m_oldStorage.getAll(target);
351: merge.addAll(more);
352:
353: all.clear();
354: all.addAll(merge);
355:
356: Collections.sort(all);
357:
358: // subset by position
359: if (first < 1)
360: first = 1;
361: if (last >= all.size())
362: last = all.size();
363:
364: all = all.subList(first - 1, last);
365: return all;
366: }
367:
368: List all = super .getSelectedResources("TARGET = ?", fields,
369: first, last);
370: return all;
371: }
372:
373: public AliasEdit put(String id) {
374: // check for already exists (new or old)
375: if (check(id))
376: return null;
377:
378: BaseAliasEdit rv = (BaseAliasEdit) super .putResource(id,
379: fields(id, null, false));
380: if (rv != null)
381: rv.activate();
382: return rv;
383: }
384:
385: public AliasEdit edit(String id) {
386: BaseAliasEdit rv = (BaseAliasEdit) super .editResource(id);
387:
388: // if not found, try from the old (convert to the new)
389: if (m_checkOld && (rv == null)) {
390: // this locks the old table/record
391: rv = (BaseAliasEdit) m_oldStorage.edit(id);
392: if (rv != null) {
393: // create the record in new, also locking it into an edit
394: rv = (BaseAliasEdit) super .putResource(id, fields(
395: id, rv, false));
396:
397: // delete the old record
398: m_oldStorage.remove(rv);
399: }
400: }
401:
402: if (rv != null)
403: rv.activate();
404: return rv;
405: }
406:
407: public void commit(AliasEdit edit) {
408: super .commitResource(edit,
409: fields(edit.getId(), edit, true), edit
410: .getProperties());
411: }
412:
413: public void cancel(AliasEdit edit) {
414: super .cancelResource(edit);
415: }
416:
417: public void remove(AliasEdit edit) {
418: super .removeResource(edit);
419: }
420:
421: public List search(String criteria, int first, int last) {
422: // if we have to be concerned with old stuff, we cannot let the db do the search
423: if (m_checkOld) {
424: List all = getAll();
425: List rv = new Vector();
426:
427: for (Iterator i = all.iterator(); i.hasNext();) {
428: Alias a = (Alias) i.next();
429: if (StringUtil.containsIgnoreCase(a.getId(),
430: criteria)
431: || StringUtil.containsIgnoreCase(a
432: .getTarget(), criteria)) {
433: rv.add(a);
434: }
435: }
436:
437: Collections.sort(rv);
438:
439: // subset by position
440: if (first < 1)
441: first = 1;
442: if (last >= rv.size())
443: last = rv.size();
444:
445: rv = rv.subList(first - 1, last);
446:
447: return rv;
448: }
449:
450: Object[] fields = new Object[2];
451: fields[0] = "%" + criteria + "%";
452: fields[1] = fields[0];
453: List all = super
454: .getSelectedResources(
455: "UPPER(ALIAS_ID) LIKE UPPER(?) OR UPPER(TARGET) LIKE UPPER(?)",
456: fields, first, last);
457:
458: return all;
459: }
460:
461: public int countSearch(String criteria) {
462: // if we have to be concerned with old stuff, we cannot let the db do the search and count
463: if (m_checkOld) {
464: List all = getAll();
465: List rv = new Vector();
466:
467: for (Iterator i = all.iterator(); i.hasNext();) {
468: Alias a = (Alias) i.next();
469: if (StringUtil.containsIgnoreCase(a.getId(),
470: criteria)
471: || StringUtil.containsIgnoreCase(a
472: .getTarget(), criteria)) {
473: rv.add(a);
474: }
475: }
476:
477: return rv.size();
478: }
479:
480: Object[] fields = new Object[2];
481: fields[0] = "%" + criteria + "%";
482: fields[1] = fields[0];
483: int rv = super
484: .countSelectedResources(
485: "UPPER(ALIAS_ID) LIKE UPPER(?) OR UPPER(TARGET) LIKE UPPER(?)",
486: fields);
487:
488: return rv;
489: }
490:
491: /**
492: * Read properties from storage into the edit's properties.
493: *
494: * @param edit
495: * The user to read properties for.
496: */
497: public void readProperties(AliasEdit edit,
498: ResourcePropertiesEdit props) {
499: super .readProperties(edit, props);
500: }
501:
502: /**
503: * Get the fields for the database from the edit for this id, and the id again at the end if needed
504: *
505: * @param id
506: * The resource id
507: * @param edit
508: * The edit (may be null in a new)
509: * @param idAgain
510: * If true, include the id field again at the end, else don't.
511: * @return The fields for the database.
512: */
513: protected Object[] fields(String id, AliasEdit edit,
514: boolean idAgain) {
515: Object[] rv = new Object[idAgain ? 7 : 6];
516: rv[0] = caseId(id);
517: if (idAgain) {
518: rv[6] = rv[0];
519: }
520:
521: if (edit == null) {
522: String current = sessionManager()
523: .getCurrentSessionUserId();
524: if (current == null)
525: current = "";
526:
527: Time now = timeService().newTime();
528: rv[1] = "";
529: rv[2] = current;
530: rv[3] = current;
531: rv[4] = now;
532: rv[5] = now;
533: }
534:
535: else {
536: rv[1] = edit.getTarget();
537: ResourceProperties props = edit.getProperties();
538: rv[2] = StringUtil
539: .trimToZero(((BaseAliasEdit) edit).m_createdUserId);
540: rv[3] = StringUtil
541: .trimToZero(((BaseAliasEdit) edit).m_lastModifiedUserId);
542: rv[4] = edit.getCreatedTime();
543: rv[5] = edit.getModifiedTime();
544: }
545:
546: return rv;
547: }
548:
549: /**
550: * Read from the result one set of fields to create a Resource.
551: *
552: * @param result
553: * The Sql query result.
554: * @return The Resource object.
555: */
556: public Object readSqlResultRecord(ResultSet result) {
557: try {
558: String id = result.getString(1);
559: String target = result.getString(2);
560: String createdBy = result.getString(3);
561: String modifiedBy = result.getString(4);
562: Time createdOn = timeService().newTime(
563: result.getTimestamp(5, sqlService().getCal())
564: .getTime());
565: Time modifiedOn = timeService().newTime(
566: result.getTimestamp(6, sqlService().getCal())
567: .getTime());
568:
569: // create the Resource from these fields
570: return new BaseAliasEdit(id, target, createdBy,
571: createdOn, modifiedBy, modifiedOn);
572: } catch (SQLException ignore) {
573: return null;
574: }
575: }
576:
577: } // DbStorage
578:
579: /**
580: * This is how to access the old chef_alias table (CTools through 2.0.7)
581: */
582: protected class DbStorageOld extends BaseDbSingleStorage implements
583: Storage {
584: /**
585: * Construct.
586: *
587: * @param user
588: * The StorageUser class to call back for creation of Resource and Edit objects.
589: */
590: public DbStorageOld(StorageUser user) {
591: super ("CHEF_ALIAS", "ALIAS_ID", null, false, "alias", user,
592: sqlService());
593: setCaseInsensitivity(true);
594:
595: } // DbStorage
596:
597: public boolean check(String id) {
598: return super .checkResource(id);
599: }
600:
601: public AliasEdit get(String id) {
602: return (AliasEdit) super .getResource(id);
603: }
604:
605: public List getAll(int first, int last) {
606: return super .getAllResources(first, last);
607: }
608:
609: public List getAll() {
610: return super .getAllResources();
611: }
612:
613: public int count() {
614: int rv = super .countAllResources();
615:
616: // if we find no more records in the old table, we can start ignoring it...
617: // Note: this means once they go away they cannot come back (old versions cannot run in the cluster
618: // and write to the old cluster table). -ggolden
619: if (rv == 0) {
620: m_checkOld = false;
621: M_log.info(" ** starting to ignore old");
622: }
623: return rv;
624: }
625:
626: public AliasEdit put(String id) {
627: return (AliasEdit) super .putResource(id, null);
628: }
629:
630: public AliasEdit edit(String id) {
631: return (AliasEdit) super .editResource(id);
632: }
633:
634: public void commit(AliasEdit edit) {
635: super .commitResource(edit);
636: }
637:
638: public void cancel(AliasEdit edit) {
639: super .cancelResource(edit);
640: }
641:
642: public void remove(AliasEdit edit) {
643: super .removeResource(edit);
644: }
645:
646: public List getAll(String target) {
647: List all = super .getAllResources();
648:
649: // pick out from all those that are for this target
650: List found = new Vector();
651: for (Iterator iAll = all.iterator(); iAll.hasNext();) {
652: BaseAliasEdit a = (BaseAliasEdit) iAll.next();
653: if (a.getTarget().equals(target))
654: found.add(a);
655: }
656:
657: return found;
658: }
659:
660: public List getAll(String target, int first, int last) {
661: List all = super .getAllResources();
662:
663: // pick out from all those that are for this target
664: List found = new Vector();
665: for (Iterator iAll = all.iterator(); iAll.hasNext();) {
666: BaseAliasEdit a = (BaseAliasEdit) iAll.next();
667: if (a.getTarget().equals(target))
668: found.add(a);
669: }
670:
671: // sort for position check
672: Collections.sort(found);
673:
674: // subset by position
675: if (first < 1)
676: first = 1;
677: if (last >= found.size())
678: last = found.size();
679:
680: found = found.subList(first - 1, last);
681:
682: return found;
683: }
684:
685: /**
686: * Search for aliases with id or target matching criteria, in range.
687: *
688: * @param criteria
689: * The search criteria.
690: * @param first
691: * The first record position to return.
692: * @param last
693: * The last record position to return.
694: * @return The List (BaseAliasEdit) of all alias.
695: */
696: public List search(String criteria, int first, int last) {
697: List all = super .getAllResources();
698:
699: List rv = new Vector();
700: for (Iterator i = all.iterator(); i.hasNext();) {
701: Alias a = (Alias) i.next();
702: if (StringUtil.containsIgnoreCase(a.getId(), criteria)
703: || StringUtil.containsIgnoreCase(a.getTarget(),
704: criteria)) {
705: rv.add(a);
706: }
707: }
708:
709: Collections.sort(rv);
710:
711: // subset by position
712: if (first < 1)
713: first = 1;
714: if (last >= rv.size())
715: last = rv.size();
716:
717: rv = rv.subList(first - 1, last);
718:
719: return rv;
720: }
721:
722: /**
723: * Count all the aliases with id or target matching criteria.
724: *
725: * @param criteria
726: * The search criteria.
727: * @return The count of all aliases with id or target matching criteria.
728: */
729: public int countSearch(String criteria) {
730: List all = super .getAllResources();
731:
732: Vector rv = new Vector();
733: for (Iterator i = all.iterator(); i.hasNext();) {
734: Alias a = (Alias) i.next();
735: if (StringUtil.containsIgnoreCase(a.getId(), criteria)
736: || StringUtil.containsIgnoreCase(a.getTarget(),
737: criteria)) {
738: rv.add(a);
739: }
740: }
741:
742: return rv.size();
743: }
744:
745: /**
746: * Read properties from storage into the edit's properties.
747: *
748: * @param edit
749: * The user to read properties for.
750: */
751: public void readProperties(AliasEdit edit,
752: ResourcePropertiesEdit props) {
753: M_log.warn("readProperties: should not be called.");
754: }
755: }
756:
757: /**
758: * Create a new table record for all old table records found, and delete the old.
759: */
760: protected void convertOld() {
761: M_log.info("convertOld");
762:
763: try {
764: // get a connection
765: final Connection connection = sqlService()
766: .borrowConnection();
767: boolean wasCommit = connection.getAutoCommit();
768: connection.setAutoCommit(false);
769:
770: // read all alias ids
771: String sql = "select ALIAS_ID, XML from CHEF_ALIAS";
772: sqlService().dbRead(connection, sql, null, new SqlReader() {
773: private int count = 0;
774:
775: public Object readSqlResultRecord(ResultSet result) {
776: try {
777: // create the Resource from the db xml
778: String id = result.getString(1);
779: String xml = result.getString(2);
780:
781: // read the xml
782: Document doc = Xml.readDocumentFromString(xml);
783:
784: // verify the root element
785: Element root = doc.getDocumentElement();
786: if (!root.getTagName().equals("alias")) {
787: M_log
788: .warn("convertOld: XML root element not alias: "
789: + root.getTagName());
790: return null;
791: }
792: AliasEdit a = new BaseAliasEdit(root);
793:
794: // pick up the fields
795: Object[] fields = ((DbStorage) m_storage)
796: .fields(id, a, false);
797:
798: // insert the record
799: boolean ok = ((DbStorage) m_storage)
800: .insertResource(id, fields, connection);
801: if (!ok) {
802: M_log.warn("convertOld: failed to insert: "
803: + id);
804: }
805:
806: // delete the old record
807: String statement = "delete from CHEF_ALIAS where ALIAS_ID = ?";
808: fields = new Object[1];
809: fields[0] = id;
810: ok = sqlService().dbWrite(connection,
811: statement, fields);
812: if (!ok) {
813: M_log.warn("convertOld: failed to delete: "
814: + id);
815: }
816:
817: // m_logger.info(" ** alias converted: " + id);
818:
819: return null;
820: } catch (Throwable ignore) {
821: return null;
822: }
823: }
824: });
825:
826: connection.commit();
827: connection.setAutoCommit(wasCommit);
828: sqlService().returnConnection(connection);
829: } catch (Throwable t) {
830: M_log.warn("convertOld: failed: " + t);
831: }
832:
833: M_log.info("convertOld: done");
834: }
835:
836: }
|