001: /*
002: * Copyright 2001-2006 C:1 Financial Services GmbH
003: *
004: * This software is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License Version 2.1, as published by the Free Software Foundation.
007: *
008: * This software is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
011: * Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public
014: * License along with this library; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
016: */
017:
018: package de.finix.contelligent.components.query;
019:
020: import java.io.IOException;
021: import java.io.StringWriter;
022: import java.io.Writer;
023: import java.util.Collection;
024: import java.util.Iterator;
025: import java.util.LinkedList;
026: import java.util.List;
027: import java.util.Map;
028:
029: import de.finix.contelligent.CallData;
030: import de.finix.contelligent.Component;
031: import de.finix.contelligent.ComponentManager;
032: import de.finix.contelligent.ComponentNotFoundException;
033: import de.finix.contelligent.ComponentPath;
034: import de.finix.contelligent.ExternalRelationSource;
035: import de.finix.contelligent.ModificationVetoException;
036: import de.finix.contelligent.components.Folder;
037: import de.finix.contelligent.content.ContelligentStringContent;
038: import de.finix.contelligent.content.ContentProvider;
039: import de.finix.contelligent.event.ComponentEvent;
040: import de.finix.contelligent.event.ContelligentEvent;
041: import de.finix.contelligent.event.EventQueue;
042: import de.finix.contelligent.event.EventQueueListener;
043: import de.finix.contelligent.exception.ContelligentException;
044: import de.finix.contelligent.logging.LoggingService;
045: import de.finix.contelligent.render.ParameterDescription;
046: import de.finix.contelligent.render.Renderable;
047: import de.finix.contelligent.render.Renderer;
048: import de.finix.contelligent.render.Template;
049:
050: public class QueryResultPager extends Folder implements Renderable,
051: Renderer, ExternalRelationSource {
052:
053: private final static org.apache.log4j.Logger log = LoggingService
054: .getLogger(QueryResultPager.class);
055:
056: private ComponentPath queryResultRenderer;
057:
058: private List cachedQueryResult;
059:
060: private boolean cacheOutput;
061:
062: private String cachedOutput;
063:
064: private EventQueueListener listener;
065:
066: private int range;
067:
068: private boolean showRange;
069:
070: final private static int IN = 0;
071:
072: final private static int OUT = 1;
073:
074: final private static int BORDER = 2;
075:
076: final private static int NO_TEMPLATE = -1;
077:
078: final private static int PRE_TEMPLATE = 0;
079:
080: final private static int POST_TEMPLATE = 1;
081:
082: final private static int FIRST_PAGE_TEMPLATE = 2;
083:
084: final private static int FIRST_PAGE_SELECTED_TEMPLATE = 3;
085:
086: final private static int LAST_PAGE_TEMPLATE = 4;
087:
088: final private static int LAST_PAGE_SELECTED_TEMPLATE = 5;
089:
090: final private static int GO_TO_PAGE_TEMPLATE = 6;
091:
092: final private static int GO_TO_PAGE_EVEN_TEMPLATE = 7;
093:
094: final private static int GO_TO_PAGE_ODD_TEMPLATE = 8;
095:
096: final private static int NEXT_PAGE_AVAILABLE_TEMPLATE = 9;
097:
098: final private static int NEXT_PAGE_UNAVAILABLE_TEMPLATE = 10;
099:
100: final private static int PREVIOUS_PAGE_AVAILABLE_TEMPLATE = 11;
101:
102: final private static int PREVIOUS_PAGE_UNAVAILABLE_TEMPLATE = 12;
103:
104: final private static int SELECTED_PAGE_TEMPLATE = 13;
105:
106: final private static int SEPARATOR_TEMPLATE = 14;
107:
108: final private static int ETC_TEMPLATE = 15;
109:
110: final private static int EMPTY_TEMPLATE = 16;
111:
112: final private static int numberOfTemplates = 17;
113:
114: final private static ComponentPath PRE = new ComponentPath("pre");
115:
116: final private static ComponentPath POST = new ComponentPath("post");
117:
118: final private static ComponentPath FIRST_PAGE = new ComponentPath(
119: "firstPage");
120:
121: final private static ComponentPath FIRST_PAGE_SELECTED = new ComponentPath(
122: "firstPageSelected");
123:
124: final private static ComponentPath LAST_PAGE = new ComponentPath(
125: "lastPage");
126:
127: final private static ComponentPath LAST_PAGE_SELECTED = new ComponentPath(
128: "lastPageSelected");
129:
130: final private static ComponentPath GO_TO_PAGE = new ComponentPath(
131: "goToPage");
132:
133: final private static ComponentPath GO_TO_PAGE_EVEN = new ComponentPath(
134: "goToPageEven");
135:
136: final private static ComponentPath GO_TO_PAGE_ODD = new ComponentPath(
137: "goToPageOdd");
138:
139: final private static ComponentPath NEXT_PAGE_AVAILABLE = new ComponentPath(
140: "nextPageAvailable");
141:
142: final private static ComponentPath NEXT_PAGE_UNAVAILABLE = new ComponentPath(
143: "nextPageUnavailable");
144:
145: final private static ComponentPath PREVIOUS_PAGE_AVAILABLE = new ComponentPath(
146: "previousPageAvailable");
147:
148: final private static ComponentPath PREVIOUS_PAGE_UNAVAILABLE = new ComponentPath(
149: "previousPageUnavailable");
150:
151: final private static ComponentPath SELECTED_PAGE = new ComponentPath(
152: "selectedPage");
153:
154: final private static ComponentPath SEPARATOR = new ComponentPath(
155: "separator");
156:
157: final private static ComponentPath ETC = new ComponentPath("etc");
158:
159: final private static ComponentPath EMPTY = new ComponentPath(
160: "empty");
161:
162: final private static String CURRENT_ITEM_NUMBER = "currentItemNumber";
163:
164: final static String INFO = "info";
165:
166: final static String CURRENT_PAGE = "currentPage";
167:
168: final static String TOTAL_PAGES = "totalPages";
169:
170: final static String FIRST_ITEM = "firstItem";
171:
172: final static String LAST_ITEM = "lastItem";
173:
174: final private static ParameterDescription[] parameters = new ParameterDescription[] { new ParameterDescription(
175: INFO, "Render Informations", false, true, new String[] {
176: CURRENT_PAGE, TOTAL_PAGES, FIRST_ITEM, LAST_ITEM }) };
177:
178: public void postCreate() throws Exception {
179: listener = new EventListener(this .getComponentContext()
180: .getPath());
181: EventQueue.getInstance().addListener(listener);
182: }
183:
184: public void finalize() {
185: EventQueue.getInstance().removeListener(listener);
186: }
187:
188: public void setQueryResultRenderer(ComponentPath queryResultRenderer) {
189: this .queryResultRenderer = queryResultRenderer;
190: }
191:
192: public ComponentPath getQueryResultRenderer() {
193: return this .queryResultRenderer;
194: }
195:
196: public boolean getCacheOutput() {
197: return cacheOutput;
198: }
199:
200: public void setCacheOutput(boolean cacheOutput) {
201: this .cacheOutput = cacheOutput;
202: }
203:
204: public int getRange() {
205: return range;
206: }
207:
208: public void setRange(int range) {
209: this .range = range;
210: }
211:
212: public boolean getShowRange() {
213: return showRange;
214: }
215:
216: public void setShowRange(boolean showRange) {
217: this .showRange = showRange;
218: }
219:
220: public void render(Writer writer, Map parameterMap,
221: CallData callData) throws ContelligentException,
222: IOException {
223: final boolean production = callData.getActualManager().isRoot();
224: ComponentManager manager = callData.getActualManager();
225: try {
226: Component queryRenderer = manager.getSubcomponent(this ,
227: queryResultRenderer, callData);
228: String storageKey = queryRenderer.getComponentContext()
229: .getPath().toString();
230: if (queryRenderer instanceof QueryResultRenderer) {
231:
232: int itemsPerPage = ((QueryResultRenderer) queryRenderer)
233: .getItemsPerPage();
234: if (itemsPerPage <= 0) {
235: log.debug("Skipping pager, items per page is <= 0");
236: return;
237: }
238:
239: int currentPosition = 0;
240: if (callData.getRequestAttribute(
241: QueryResultRenderer.CURRENT_POSITION,
242: storageKey) != null) {
243: currentPosition = ((Integer) callData
244: .getRequestAttribute(
245: QueryResultRenderer.CURRENT_POSITION,
246: storageKey)).intValue();
247: }
248:
249: // collect fragments
250: ComponentPath queryPath = ((QueryResultRenderer) queryRenderer)
251: .getQuery();
252: Component content = manager.getSubcomponent(
253: (QueryResultRenderer) queryRenderer, queryPath,
254: callData);
255: List currentQuery = ((Query) content)
256: .getQueryResult(callData);
257:
258: // XXX: optimize int append to string to writer :)
259: if (parameterMap != null
260: && parameterMap.containsKey(INFO)) {
261: String key = ((String[]) parameterMap.get(INFO))[0];
262: if (key != null) {
263: if (key.equals(CURRENT_PAGE)) {
264: int currentPage = (currentPosition / itemsPerPage) + 1;
265: writer.write("" + currentPage);
266: } else if (key.equals(TOTAL_PAGES)) {
267: int totalPages = (int) Math
268: .ceil(((double) currentQuery.size())
269: / ((double) itemsPerPage));
270: writer.write("" + totalPages);
271: } else if (key.equals(FIRST_ITEM)) {
272: writer.write("" + (currentPosition + 1));
273: } else if (key.equals(LAST_ITEM)) {
274: writer.write(""
275: + Math.min(currentPosition
276: + itemsPerPage,
277: currentQuery.size()));
278: }
279: }
280: return;
281: }
282:
283: log.debug("Start composing pager for query");
284:
285: // check if query result has changed
286: synchronized (this ) {
287: if (!cacheOutput
288: || currentQuery != cachedQueryResult
289: || !production) {
290: StringWriter cacheWriter = new StringWriter(256);
291: if (production) {
292: cachedQueryResult = currentQuery;
293: }
294: Template[] templates = new Template[numberOfTemplates];
295: templates[PRE_TEMPLATE] = loadTemplate(manager,
296: PRE, callData);
297: templates[POST_TEMPLATE] = loadTemplate(
298: manager, POST, callData);
299: templates[FIRST_PAGE_TEMPLATE] = loadTemplate(
300: manager, FIRST_PAGE, callData);
301: templates[LAST_PAGE_TEMPLATE] = loadTemplate(
302: manager, LAST_PAGE, callData);
303: templates[FIRST_PAGE_SELECTED_TEMPLATE] = loadTemplate(
304: manager, FIRST_PAGE_SELECTED, callData);
305: templates[LAST_PAGE_SELECTED_TEMPLATE] = loadTemplate(
306: manager, LAST_PAGE_SELECTED, callData);
307: templates[GO_TO_PAGE_TEMPLATE] = loadTemplate(
308: manager, GO_TO_PAGE, callData);
309: templates[GO_TO_PAGE_EVEN_TEMPLATE] = loadTemplate(
310: manager, GO_TO_PAGE_EVEN, callData);
311: templates[GO_TO_PAGE_ODD_TEMPLATE] = loadTemplate(
312: manager, GO_TO_PAGE_ODD, callData);
313: templates[NEXT_PAGE_AVAILABLE_TEMPLATE] = loadTemplate(
314: manager, NEXT_PAGE_AVAILABLE, callData);
315: templates[NEXT_PAGE_UNAVAILABLE_TEMPLATE] = loadTemplate(
316: manager, NEXT_PAGE_UNAVAILABLE,
317: callData);
318: templates[PREVIOUS_PAGE_AVAILABLE_TEMPLATE] = loadTemplate(
319: manager, PREVIOUS_PAGE_AVAILABLE,
320: callData);
321: templates[PREVIOUS_PAGE_UNAVAILABLE_TEMPLATE] = loadTemplate(
322: manager, PREVIOUS_PAGE_UNAVAILABLE,
323: callData);
324: templates[SELECTED_PAGE_TEMPLATE] = loadTemplate(
325: manager, SELECTED_PAGE, callData);
326: templates[SEPARATOR_TEMPLATE] = loadTemplate(
327: manager, SEPARATOR, callData);
328: templates[ETC_TEMPLATE] = loadTemplate(manager,
329: ETC, callData);
330: templates[EMPTY_TEMPLATE] = loadTemplate(
331: manager, EMPTY, callData);
332: if (currentQuery.size() > 0) {
333: boolean hasNext = false, hasPrevious = false;
334: if (itemsPerPage > 0) {
335: hasNext = (currentPosition
336: + itemsPerPage < currentQuery
337: .size());
338: hasPrevious = (currentPosition > 0);
339: }
340: callData.setRequestAttribute(
341: PagingAction.SESSION_STORE,
342: storageKey);
343: if (templates[PRE_TEMPLATE] != null)
344: templates[PRE_TEMPLATE].write(
345: cacheWriter, this , callData);
346: callData.setRequestAttribute(
347: PagingAction.TARGET_POSITION,
348: String.valueOf(0));
349: if (templates[FIRST_PAGE_SELECTED_TEMPLATE] != null
350: && !hasPrevious) {
351: templates[FIRST_PAGE_SELECTED_TEMPLATE]
352: .write(cacheWriter, this ,
353: callData);
354: } else if (templates[FIRST_PAGE_TEMPLATE] != null) {
355: templates[FIRST_PAGE_TEMPLATE].write(
356: cacheWriter, this , callData);
357: }
358: callData.setRequestAttribute(
359: PagingAction.TARGET_POSITION,
360: String.valueOf(currentPosition
361: - itemsPerPage));
362: if (hasPrevious) {
363: if (templates[PREVIOUS_PAGE_AVAILABLE_TEMPLATE] != null)
364: templates[PREVIOUS_PAGE_AVAILABLE_TEMPLATE]
365: .write(cacheWriter, this ,
366: callData);
367: } else {
368: if (templates[PREVIOUS_PAGE_UNAVAILABLE_TEMPLATE] != null)
369: templates[PREVIOUS_PAGE_UNAVAILABLE_TEMPLATE]
370: .write(cacheWriter, this ,
371: callData);
372: }
373: int step = itemsPerPage > 0 ? itemsPerPage
374: : 1;
375: boolean first = true;
376: int lastItem = 0;
377: for (int i = 0; i < currentQuery.size(); i += step) {
378: lastItem = i;
379: callData.setRequestAttribute(
380: CURRENT_ITEM_NUMBER, String
381: .valueOf(i
382: / itemsPerPage
383: + 1));
384: int rangeState = IN;
385: if (getShowRange()
386: && (i < currentPosition - range
387: * step || i > currentPosition
388: + range * step))
389: rangeState = OUT;
390: if (getShowRange()
391: && (i == currentPosition
392: - range * step || i == currentPosition
393: + range * step))
394: rangeState = BORDER;
395: try {
396: callData
397: .setRequestAttribute(
398: PagingAction.TARGET_POSITION,
399: String.valueOf(i));
400: boolean pagingTemplate = false;
401: int templateToUse = NO_TEMPLATE;
402: if (i == currentPosition
403: && templates[SELECTED_PAGE_TEMPLATE] != null) {
404: pagingTemplate = true;
405: templateToUse = SELECTED_PAGE_TEMPLATE;
406: } else if (rangeState == IN
407: && i % 2 == 0
408: && templates[GO_TO_PAGE_EVEN_TEMPLATE] != null) {
409: pagingTemplate = true;
410: templateToUse = GO_TO_PAGE_EVEN_TEMPLATE;
411: } else if (rangeState == IN
412: && i % 2 == 1
413: && templates[GO_TO_PAGE_ODD_TEMPLATE] != null) {
414: pagingTemplate = true;
415: templateToUse = GO_TO_PAGE_ODD_TEMPLATE;
416: } else if (rangeState == IN) {
417: pagingTemplate = true;
418: templateToUse = GO_TO_PAGE_TEMPLATE;
419: } else if (rangeState == BORDER) {
420: templateToUse = ETC_TEMPLATE;
421: }
422: if (templateToUse > 0) {
423: if (templates[templateToUse] == null) {
424: if (log.isDebugEnabled()) {
425: log
426: .debug("Not writing template with state "
427: + templateToUse
428: + ", because it's not defined!");
429: }
430: } else {
431: if (pagingTemplate) {
432: if (!first) {
433: if (templates[SEPARATOR_TEMPLATE] != null)
434: templates[SEPARATOR_TEMPLATE]
435: .write(
436: cacheWriter,
437: this ,
438: callData);
439: }
440: first = false;
441: }
442: templates[templateToUse]
443: .write(cacheWriter,
444: this ,
445: callData);
446: }
447: }
448: } catch (ComponentNotFoundException e) {
449: log
450: .error(
451: "Could not resolve component for content!",
452: e);
453: }
454: }
455: callData.setRequestAttribute(
456: PagingAction.TARGET_POSITION,
457: String.valueOf(currentPosition
458: + itemsPerPage));
459: if (hasNext) {
460: if (templates[NEXT_PAGE_AVAILABLE_TEMPLATE] != null)
461: templates[NEXT_PAGE_AVAILABLE_TEMPLATE]
462: .write(cacheWriter, this ,
463: callData);
464: } else {
465: if (templates[NEXT_PAGE_UNAVAILABLE_TEMPLATE] != null)
466: templates[NEXT_PAGE_UNAVAILABLE_TEMPLATE]
467: .write(cacheWriter, this ,
468: callData);
469: }
470: callData.setRequestAttribute(
471: PagingAction.TARGET_POSITION,
472: String.valueOf(lastItem));
473: if (templates[LAST_PAGE_SELECTED_TEMPLATE] != null
474: && !hasNext) {
475: templates[LAST_PAGE_SELECTED_TEMPLATE]
476: .write(cacheWriter, this ,
477: callData);
478: } else if (templates[LAST_PAGE_TEMPLATE] != null) {
479: templates[LAST_PAGE_TEMPLATE].write(
480: cacheWriter, this , callData);
481: }
482: if (templates[POST_TEMPLATE] != null)
483: templates[POST_TEMPLATE].write(
484: cacheWriter, this , callData);
485: } else {
486: // query result is empty
487: if (templates[EMPTY_TEMPLATE] != null)
488: templates[EMPTY_TEMPLATE].write(
489: cacheWriter, this , callData);
490: }
491: log.debug("Output calculated and cached!");
492: cachedOutput = cacheWriter.toString();
493: }
494: // render cached output;
495: writer.write(cachedOutput);
496: }
497: } else {
498: log
499: .error("Could not render component '"
500: + queryRenderer
501: + "', because it is not of type QueryRenderer!");
502: }
503: } catch (ComponentNotFoundException e) {
504: log.error("Could not resolve query result renderer: '"
505: + queryResultRenderer + "'", e);
506: }
507: log.debug("Query composed");
508: }
509:
510: private Template loadTemplate(ComponentManager manager,
511: ComponentPath subcomponent, CallData callData)
512: throws ContelligentException {
513: try {
514: Component component = manager.getSubcomponent(this ,
515: subcomponent, callData);
516: if (component instanceof ContentProvider
517: && ((ContentProvider) component).getContent() instanceof ContelligentStringContent) {
518: Template template = ((ContelligentStringContent) ((ContentProvider) component)
519: .getContent()).getTemplate(callData);
520: if (template == null) {
521: log
522: .error("Template of type string content is null for some reason...");
523: }
524: return template;
525: }
526: log.debug("Only string content is accepted for templates!");
527: } catch (ComponentNotFoundException e) {
528: if (log.isDebugEnabled()) {
529: log.debug("Template for state '"
530: + subcomponent.getName() + "' not defined");
531: }
532: return null;
533: }
534: return null;
535: }
536:
537: public Renderer getRenderer() {
538: return this ;
539: }
540:
541: public ParameterDescription[] getParameterDescription() {
542: return parameters;
543: }
544:
545: public Collection getSensitiveCategories() {
546: return null;
547: }
548:
549: class EventListener implements EventQueueListener {
550: private ComponentPath path;
551:
552: public EventListener(ComponentPath path) {
553: this .path = path;
554: }
555:
556: public void onEvents(List eventList) {
557: for (Iterator i = eventList.iterator(); i.hasNext();) {
558: ContelligentEvent event = (ContelligentEvent) i.next();
559: if (event instanceof ComponentEvent) {
560: ComponentPath targetPath = ((ComponentEvent) event)
561: .getTargetPath();
562: if (targetPath.isSubPathOf(path)) {
563: synchronized (this ) {
564: cachedQueryResult = null;
565: }
566: }
567: }
568: }
569: }
570: }
571:
572: /*
573: * (non-Javadoc)
574: *
575: * @see de.finix.contelligent.ExternalRelationSource#relatedPaths()
576: */
577: public List relatedPaths() {
578: LinkedList list = new LinkedList();
579: list.add(getQueryResultRenderer());
580: return list;
581: }
582:
583: /*
584: * (non-Javadoc)
585: *
586: * @see de.finix.contelligent.ExternalRelationSource#relatedPaths(java.util.List)
587: */
588: public void relatedPaths(List newTargetPaths)
589: throws ModificationVetoException {
590: if (newTargetPaths == null || newTargetPaths.size() == 0
591: || newTargetPaths.size() > 1) {
592: throw new ModificationVetoException(
593: "illegal state - newTargetPaths:'" + newTargetPaths
594: + "'");
595: }
596: setQueryResultRenderer((ComponentPath) newTargetPaths.get(0));
597: }
598: }
|