001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: package com.jcorporate.expresso.services.controller.dbmaint;
066:
067: import com.jcorporate.expresso.core.controller.Block;
068: import com.jcorporate.expresso.core.controller.Controller;
069: import com.jcorporate.expresso.core.controller.ControllerException;
070: import com.jcorporate.expresso.core.controller.ControllerRequest;
071: import com.jcorporate.expresso.core.controller.Output;
072: import com.jcorporate.expresso.core.controller.Transition;
073: import com.jcorporate.expresso.core.dataobjects.DataFieldMetaData;
074: import com.jcorporate.expresso.core.dataobjects.DataObject;
075: import com.jcorporate.expresso.core.dataobjects.DataObjectMetaData;
076: import com.jcorporate.expresso.core.db.DBException;
077: import com.jcorporate.expresso.core.dbobj.ValidValue;
078: import com.jcorporate.expresso.core.misc.Format;
079: import com.jcorporate.expresso.core.misc.RecordPaginator;
080: import com.jcorporate.expresso.core.security.User;
081:
082: import java.text.DateFormat;
083: import java.text.SimpleDateFormat;
084: import java.util.ArrayList;
085: import java.util.Date;
086: import java.util.Iterator;
087: import java.util.Locale;
088: import java.util.StringTokenizer;
089:
090: /**
091: * Base class for the DBMaint commands that display lists of records, such
092: * as Search and List.
093: *
094: * @author Michael Nash
095: */
096: public abstract class ListBase extends DynamicCmd {
097: private long totalRecordCount = 0;
098:
099: private RecordPaginator paginator;
100:
101: public ListBase() {
102: super ();
103: }
104:
105: /**
106: * Constructor
107: *
108: * @param code The name of the state.
109: * @param descrip The friendly name of the state
110: */
111: public ListBase(String code, String descrip) {
112: super (code, descrip);
113: } /* ListBase(String, String) */
114:
115: /**
116: * Base class
117: * Search the database for a list of records that match the given criteria
118: * and display them in a table. Key fields are made clickable for the user
119: * to edit/delete the current record (security permitting).
120: *
121: * @throws DBException If the search fails with a database problem
122: */
123: private synchronized void autoList() throws DBException,
124: ControllerException {
125: DataObject myDBObj = this .getDataObject();
126: String oneFieldName = null;
127: long recordCount = 0;
128:
129: if (myDBObj == null) {
130: throw new DBException(
131: "Database object not initialized - cannot list");
132: }
133:
134: DataObjectMetaData metadata = myDBObj.getMetaData();
135:
136: showUserName(metadata.getDescription(this
137: .getControllerRequest().getLocale()));
138:
139: Block myTable = new Block("recordList");
140: myTable.setDescription(metadata.getDescription(this
141: .getControllerRequest().getLocale())
142: + " ("
143: + User.getLoginFromId(this .getUid(), this
144: .getDataContext()) + ")");
145: myTable.setAttribute("table", "Y");
146: addBlock(myTable);
147:
148: /* Now the heading row for the table */
149: StringBuffer headerString = new StringBuffer();
150: boolean pipeNeeded = false;
151:
152: //following changes by Pete allow viewing a select group of fields, if specified
153: ArrayList browseArrayList = (ArrayList) myDBObj
154: .getAttribute("browseArrayList");
155: boolean hasBrowseArrayList = false;
156:
157: if (browseArrayList != null) {
158: hasBrowseArrayList = true;
159: }
160: for (Iterator e = (hasBrowseArrayList) ? browseArrayList
161: .iterator() : myDBObj.getMetaData().getFieldListArray()
162: .iterator(); e.hasNext();) {
163: oneFieldName = (String) e.next();
164: DataFieldMetaData fieldMetadata = myDBObj
165: .getFieldMetaData(oneFieldName);
166: if (!fieldMetadata.isSecret()) {
167: if (pipeNeeded) {
168: headerString.append("|");
169: }
170:
171: headerString.append(metadata.getDescription(this
172: .getControllerRequest().getLocale(),
173: oneFieldName));
174: pipeNeeded = true;
175: }
176: } /* for each field */
177:
178: myTable.setAttribute("header-row", headerString.toString());
179:
180: String sortKey = getSortKey();
181: recordCount = listRecords(sortKey, myTable);
182:
183: if (recordCount == 0) {
184: addOutput(new Output("recordCount", "No records found"));
185: } else {
186: addOutput(new Output("recordCount", "" + totalRecordCount
187: + " records found"));
188:
189: addBlock(this .generatePageTransitions(
190: getControllerRequest(), this .getRecordPaginator(),
191: 10));
192: }
193: } /* autoList() */
194:
195: /**
196: * Gleaned from {@link http://www.jguru.com/faq/view.jsp?EID=429821} to
197: * make the SimpleDateFormat give us 4 year dates instead.
198: *
199: * @param df the DateFormat class
200: * @return DateFormat instance
201: */
202: private DateFormat modifyDateFormat(DateFormat df) {
203: SimpleDateFormat sdf = null;
204: try {
205: sdf = (SimpleDateFormat) df;
206: } catch (ClassCastException cce) {
207: return df;
208: }
209: String sTemp = sdf.toPattern();
210: int iLen = sTemp.length();
211: int i = sTemp.lastIndexOf('y') + 1;
212: sTemp = sTemp.substring(0, i) + "yy"
213: + (i < iLen ? sTemp.substring(i, iLen) : "");
214: sdf.applyPattern(sTemp);
215:
216: return sdf;
217: } // end do4
218:
219: /**
220: * Same as display value but for Date/DateTime types. Formats things appropriate
221: * to the user's locale
222: *
223: * @param metaData the Data Field's metadata
224: * @param dt The date value to format
225: * @param l the User's Locale gathered from the ControllerResponse object
226: * @return a properly formatted date
227: */
228: private String displayDateValue(DataFieldMetaData metaData,
229: Date dt, Locale l) {
230: if (dt == null) {
231: return "";
232: }
233: if (metaData.isDateOnlyType()) {
234: DateFormat df = DateFormat.getDateInstance(
235: DateFormat.SHORT, l);
236: df = this .modifyDateFormat(df);
237: return df.format(dt);
238: } else if (metaData.isTimeType()) {
239: DateFormat df = DateFormat.getTimeInstance(
240: DateFormat.SHORT, l);
241: df = this .modifyDateFormat(df);
242: return df.format(dt);
243: } else {
244: DateFormat df = DateFormat.getDateTimeInstance(
245: DateFormat.SHORT, DateFormat.SHORT, l);
246: df = this .modifyDateFormat(df);
247: return df.format(dt);
248:
249: }
250:
251: }
252:
253: /**
254: * Gets the currently used paginator
255: *
256: * @return RecordPaginator instance
257: */
258: protected RecordPaginator getRecordPaginator()
259: throws ControllerException {
260: if (paginator == null) {
261: paginator = new RecordPaginator();
262: }
263: paginator.setCountRecords(true);
264: paginator.setPageNumber(getPageNumber());
265:
266: return paginator;
267: }
268:
269: /**
270: * Lists the records for display
271: *
272: * @param sortKey The key to sort against
273: * @param myTable The <code>Block</code> to insert the table into.
274: * @return the total number of records listed
275: * @throws DBException Upon error communicating with the DBObject
276: * @throws ControllerException upon other errors
277: */
278: protected long listRecords(String sortKey, Block myTable)
279: throws DBException, ControllerException {
280: DataObject myDBObj = this .getDataObject();
281:
282: int startShowingRecord = 0;
283: int endShowingRecord = 0;
284: paginator = getRecordPaginator();
285: setupListSearchCriteria();
286:
287: ArrayList allRecords = paginator.searchAndRetrieve(myDBObj,
288: sortKey);
289: totalRecordCount = paginator.getTotalRecordCount();
290:
291: startShowingRecord = paginator.getStartRecordNumber();
292: endShowingRecord = paginator.getEndRecordNumber();
293: if (allRecords.size() > 0) {
294: addRecordsToBlock(allRecords, myTable);
295: }
296:
297: //xun li add for record count
298: showNext = paginator.isMoreRecords();
299: addOutput(new Output("recordRange", "Records "
300: + startShowingRecord + " to " + endShowingRecord));
301:
302: return totalRecordCount;
303: } /* listRecords(String, Output) */
304:
305: /**
306: * This method determines how the list is sorted. The default sort order
307: * is by the key fields of the underlying DBObject. To change the
308: * default just override this method in a derived class.
309: *
310: * @return String the sort key coded with pipes.
311: * @throws DBException upon error communicating with the DBObject
312: * @throws ControllerException upon other errors
313: */
314: protected String getSortKey() throws DBException,
315: ControllerException {
316:
317: DataObject dataobj = this .getDataObject();
318:
319: String sortKey = ("");
320: boolean needPipe = false;
321:
322: for (Iterator k = dataobj.getMetaData().getKeyFieldListArray()
323: .iterator(); k.hasNext();) {
324: if (needPipe) {
325: sortKey = sortKey + "|";
326: }
327:
328: sortKey = sortKey + (String) k.next();
329: needPipe = true;
330: }
331:
332: return sortKey;
333: }
334:
335: /**
336: * This method sets the search conditions on the current DBObject.
337: * The implementation at this level uses the searchParam if it has
338: * been set, you can override this method to use other criteria,
339: * like a custom where clause.
340: *
341: * @throws DBException upon error communicating with the DBObject
342: * @throws ControllerException upon other errors
343: */
344: protected void setupListSearchCriteria() throws DBException,
345: ControllerException {
346: String searchLimits = getSearchParam();
347:
348: if (!searchLimits.equals("")) {
349: DataObject myDBObj = getDataObject();
350: StringTokenizer stk = new StringTokenizer(searchLimits, "|");
351: String oneSearchFieldName = null;
352: String oneSearchFieldValue = null;
353:
354: while (stk.hasMoreTokens()) {
355: oneSearchFieldName = stk.nextToken();
356:
357: if (!stk.hasMoreTokens()) {
358: throw new DBException(
359: "Parameter 'search' must "
360: + "contain name/value pairs seperated with "
361: + "the '|' symbol. It contained '"
362: + searchLimits + "'");
363: }
364:
365: oneSearchFieldValue = stk.nextToken();
366: myDBObj.set(oneSearchFieldName, oneSearchFieldValue);
367: }
368: } /* if any search limits */
369:
370: }
371:
372: /**
373: * Generates an arrayList of transitions to allow direct jumping to a page.
374: *
375: * @param request The controllerRequest object so we can set the appropriate
376: * parameters from the previous page.
377: * @param rp a queried and instantiated RecordPaginator object
378: * @param pageLimit the maximum number of records per screen.
379: * @return Block of <code>Transition</code> objects.
380: * @throws IllegalArgumentException if isCountRecords() == false or
381: * if no search has been run yet.
382: * @throws ControllerException upon all other errors
383: */
384: protected Block generatePageTransitions(ControllerRequest request,
385: RecordPaginator rp, int pageLimit)
386: throws ControllerException {
387: if (rp.isCountRecords() == false) {
388: throw new java.lang.IllegalArgumentException(
389: "You must have countRecords set "
390: + "to true for pagination to work");
391: }
392: Block returnBlock = new Block();
393: returnBlock.setName("PageJumpBlock");
394:
395: int curPage = rp.getPageNumber();
396: int endPage = rp.getPageCount();
397:
398: int startPage;
399: int index = 1;
400: if (curPage >= 10) {
401: startPage = curPage - (curPage % 10);
402: } else {
403: startPage = curPage - (curPage % 10) + 1;
404: }
405: if (endPage >= (startPage + 10)) {
406: endPage = curPage + (10 - (curPage % 10));
407: }
408:
409: Class controllerClass = this .getController().getClass();
410: java.util.Hashtable allParameters = request.getParameters();
411: allParameters.remove(Controller.CONTROLLER_PARAM_KEY);
412: String state = (String) allParameters
413: .get(Controller.STATE_PARAM_KEY);
414: allParameters.remove(Controller.STATE_PARAM_KEY);
415:
416: if (curPage != 1) {
417: Transition t = new Transition();
418: t.setParams(allParameters);
419: t.setState(state);
420: t.setControllerObject(controllerClass);
421: t.addParam("page", Integer.toString(curPage - 1));
422: t.setLabel("< Previous");
423: t.setName(Integer.toString(index));
424: index++;
425: returnBlock.add(t);
426: }
427:
428: for (int i = startPage; i <= endPage; i++) {
429: Transition t = new Transition();
430: t.setParams(allParameters);
431: t.setControllerObject(controllerClass);
432: t.addParam("page", Integer.toString(i));
433: t.setLabel(Integer.toString(i));
434: t.setState(state);
435: if (curPage == i) {
436: t.setAttribute("currentPage", "true");
437: Output o = new Output();
438: o.setName("CurrentPage");
439: o.setContent(Integer.toString(curPage));
440: returnBlock.add(o);
441: }
442: t.setName(Integer.toString(index));
443: index++;
444:
445: returnBlock.add(t);
446: }
447:
448: if (curPage < endPage) {
449: Transition t = new Transition();
450: t.setParams(allParameters);
451: t.setControllerObject(controllerClass);
452: t.addParam("page", Integer.toString(curPage + 1));
453: t.setState(state);
454: t.setLabel("Next >");
455: t.setName(Integer.toString(index));
456: index++;
457: returnBlock.add(t);
458: }
459:
460: addOutput(new Output("pageCount", Integer.toString(this
461: .getRecordPaginator().getPageCount())
462: + " Pages"));
463: return returnBlock;
464: }
465:
466: /**
467: * Given an interator containing database objects add all of those
468: * objects to the given output block as rows. All non-secret
469: * fields of each object should be added to the row.
470: *
471: * @param i_AllRecords an arrayList of DBObjects to add to the specified block
472: * @param myTable the <code>Block</code> obejct to add the records to.
473: * @throws DBException upon error communicating with the DBObjects
474: * @throws ControllerException upon other error.
475: */
476: protected void addRecordsToBlock(ArrayList i_AllRecords,
477: Block myTable) throws DBException, ControllerException {
478: DataObject myDBObj = this .getDataObject();
479:
480: int recordCount = 0;
481: String oneFieldName = null;
482: String oneFieldValue = null;
483: DataObject oneRecord = null;
484: String controller = getControllerName();
485: Iterator i = i_AllRecords.iterator();
486:
487: //added by Pete to allow showing of only specified fields
488: ArrayList browseArrayList = (ArrayList) myDBObj
489: .getAttribute("browseArrayList");
490: boolean hasBrowseArrayList = false;
491:
492: if (browseArrayList != null) {
493: hasBrowseArrayList = true;
494: }
495: while (i.hasNext()) {
496: oneRecord = (DataObject) i.next();
497: recordCount++;
498:
499: Block oneRow = new Block("" + recordCount);
500: oneRow.setAttribute("row", "Y");
501: myTable.add(oneRow);
502:
503: /* for each field in this kind of obect */
504: for (Iterator e2 = (hasBrowseArrayList) ? browseArrayList
505: .iterator() : myDBObj.getMetaData()
506: .getFieldListArray().iterator(); e2.hasNext();) {
507: oneFieldName = (String) e2.next();
508: DataFieldMetaData metaData = myDBObj
509: .getFieldMetaData(oneFieldName);
510:
511: if (!metaData.isSecret()) {
512: oneFieldValue = oneRecord.getField(oneFieldName);
513:
514: if (oneFieldValue == null) {
515: oneFieldValue = "";
516: }
517:
518: Output oneCell = new Output();
519: oneCell.setName(oneFieldName);
520: oneRow.addNested(oneCell);
521:
522: if (metaData.isKey()) {
523: showEditLink(oneFieldName, oneFieldValue,
524: oneRecord, oneCell, controller);
525: } else {
526: if (oneFieldValue.equalsIgnoreCase("")) {
527: oneCell.setContent(oneFieldValue);
528: } else if (myDBObj.getFieldMetaData(
529: oneFieldName).getTypeString()
530: .equalsIgnoreCase("money")) {
531: try {
532: oneCell
533: .setContent(new Format(
534: "%-10.2f")
535: .form(new Double(
536: oneFieldValue)
537: .doubleValue()));
538: } catch (NumberFormatException ne) {
539: throw new DBException(
540: "Number for "
541: + "field '"
542: + oneFieldName
543: + "' not in a valid numeric format:"
544: + oneFieldValue + ":"
545: + ne.getMessage());
546: }
547: } else { /* else not blank */
548: if (metaData.isMultiValued()) {
549: java.util.List values = myDBObj
550: .getValidValuesList(oneFieldName);
551:
552: if (values == null) {
553: throw new DBException(
554: "Valid values for field '"
555: + oneFieldName
556: + "' from object "
557: + ((Object) myDBObj)
558: .getClass()
559: .getName()
560: + " were null");
561: }
562:
563: String fieldValue = null;
564: ValidValue oneVV = null;
565:
566: for (Iterator ve = values.iterator(); ve
567: .hasNext();) {
568: oneVV = (ValidValue) ve.next();
569:
570: if (oneVV.getValue().equals(
571: oneFieldValue)) {
572: fieldValue = oneVV
573: .getDescription();
574: }
575: }
576: if (fieldValue == null) {
577: oneCell.setContent(oneFieldValue);
578: } else {
579: oneCell.setContent(fieldValue);
580: }
581: } else { /* if field is multi_valued */
582: if (metaData.isDateType()) {
583: java.util.Date dt = oneRecord
584: .getDataField(oneFieldName)
585: .asDate();
586: Locale l = this .getResponse()
587: .getLocale();
588: oneCell
589: .setContent(displayDateValue(
590: metaData, dt, l));
591: } else if (metaData
592: .isCharacterLongObjectType()) {
593: oneCell.setContent(oneFieldValue
594: + "");
595: } else {
596: oneCell.setContent(oneFieldValue);
597: }
598: }
599: }
600: } /* else key field */
601:
602: } else { /* if not a secret field */
603:
604: //xun li add below code for ref link
605: if (isKeyField(oneFieldName)) {
606: oneFieldName = (String) e2.next();
607: oneFieldValue = oneRecord
608: .getField(oneFieldName);
609:
610: if (oneFieldValue == null) {
611: oneFieldValue = "";
612: }
613:
614: Output oneCell = new Output();
615: oneCell.setName(oneFieldName);
616: oneRow.addNested(oneCell);
617: showEditLink(oneFieldName, oneFieldValue,
618: oneRecord, oneCell, controller);
619: }
620: }
621: } /* each field */
622:
623: } /* for each record */
624:
625: }
626:
627: /**
628: * Base class
629: * Show a list of database records to the user, allowing the key
630: * to be clicked on to request an edit of the object
631: *
632: * @throws DBException If a problem occurs retrieving the list
633: */
634: protected void showList() throws DBException, ControllerException {
635: autoList();
636: showOptions();
637: } /* showList() */
638:
639: } /* ListBase */
|