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.ArrayList;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.Iterator;
027: import java.util.LinkedList;
028: import java.util.List;
029: import java.util.Map;
030:
031: import de.finix.contelligent.CallData;
032: import de.finix.contelligent.Component;
033: import de.finix.contelligent.ComponentManager;
034: import de.finix.contelligent.ComponentNotFoundException;
035: import de.finix.contelligent.ComponentPath;
036: import de.finix.contelligent.ExternalRelationSource;
037: import de.finix.contelligent.ModificationVetoException;
038: import de.finix.contelligent.components.Folder;
039: import de.finix.contelligent.content.ContelligentStringContent;
040: import de.finix.contelligent.content.ContentProvider;
041: import de.finix.contelligent.event.ComponentEvent;
042: import de.finix.contelligent.event.ContelligentEvent;
043: import de.finix.contelligent.event.EventQueue;
044: import de.finix.contelligent.event.EventQueueListener;
045: import de.finix.contelligent.exception.ContelligentException;
046: import de.finix.contelligent.logging.LoggingService;
047: import de.finix.contelligent.render.DefaultRenderer;
048: import de.finix.contelligent.render.ParameterDescription;
049: import de.finix.contelligent.render.Renderable;
050: import de.finix.contelligent.render.Renderer;
051: import de.finix.contelligent.render.Template;
052:
053: public class QueryResultRenderer extends Folder implements Renderable,
054: Renderer, ExternalRelationSource {
055:
056: private final static org.apache.log4j.Logger log = LoggingService
057: .getLogger(QueryResultRenderer.class);
058:
059: private ComponentPath query;
060:
061: private List cachedQueryResult = null;
062:
063: private int itemsPerPage;
064:
065: private boolean cacheOutput;
066:
067: private String cachedOutput = null;
068:
069: private EventQueueListener listener;
070:
071: final private static int PRE_TEMPLATE = 0;
072:
073: final private static int POST_TEMPLATE = 1;
074:
075: final private static int FIRST_TEMPLATE = 2;
076:
077: final private static int LAST_TEMPLATE = 3;
078:
079: final private static int EVEN_TEMPLATE = 4;
080:
081: final private static int ODD_TEMPLATE = 5;
082:
083: final private static int DEFAULT_TEMPLATE = 6;
084:
085: final private static int SELECTED_TEMPLATE = 7;
086:
087: final private static int EMPTY_TEMPLATE = 8;
088:
089: final private static int numberOfTemplates = 9;
090:
091: final private static ComponentPath PRE = new ComponentPath("pre");
092:
093: final private static ComponentPath POST = new ComponentPath("post");
094:
095: final private static ComponentPath FIRST = new ComponentPath(
096: "first");
097:
098: final private static ComponentPath LAST = new ComponentPath("last");
099:
100: final private static ComponentPath EVEN = new ComponentPath("even");
101:
102: final private static ComponentPath ODD = new ComponentPath("odd");
103:
104: final private static ComponentPath DEFAULT = new ComponentPath(
105: "default");
106:
107: final private static ComponentPath SELECTED = new ComponentPath(
108: "selected");
109:
110: final private static ComponentPath EMPTY = new ComponentPath(
111: "empty");
112:
113: final private static String CURRENT_ITEM_NUMBER = "currentItemNumber";
114:
115: final private static String CURRENT_ITEM_PATH = "currentItemPath";
116:
117: final private static String CURRENT_ITEM_DIR = "currentItemDir";
118:
119: final private static String CURRENT_ITEM_NAME = "currentItemName";
120:
121: final static String CURRENT_POSITION = "currentPosition";
122:
123: final static String INFO = "info";
124:
125: final static String ITEMS_PER_PAGE = "itemsPerPage";
126:
127: final static String FIRST_VISIBLE_ITEM = "firstVisibleItem";
128:
129: final static String LAST_VISIBLE_ITEM = "lastVisibleItem";
130:
131: final static String TOTAL_ITEMS = "totalItems";
132:
133: final private static ParameterDescription[] parameters = new ParameterDescription[] {
134: new ParameterDescription(ITEMS_PER_PAGE,
135: "Items per page (-1 = unlimited)", false),
136: new ParameterDescription(INFO, "Render Informations",
137: false, true, new String[] { ITEMS_PER_PAGE,
138: FIRST_VISIBLE_ITEM, LAST_VISIBLE_ITEM,
139: TOTAL_ITEMS }) };
140:
141: public void postCreate() throws Exception {
142: listener = new EventListener(this .getComponentContext()
143: .getPath());
144: EventQueue.getInstance().addListener(listener);
145: }
146:
147: public void finalize() {
148: EventQueue.getInstance().removeListener(listener);
149: }
150:
151: public void setQuery(ComponentPath query) {
152: this .query = query;
153: }
154:
155: public ComponentPath getQuery() {
156: return this .query;
157: }
158:
159: public boolean getCacheOutput() {
160: return cacheOutput;
161: }
162:
163: public void setCacheOutput(boolean cacheOutput) {
164: this .cacheOutput = cacheOutput;
165: }
166:
167: public int getItemsPerPage() {
168: return itemsPerPage;
169: }
170:
171: public void setItemsPerPage(int itemsPerPage) {
172: this .itemsPerPage = itemsPerPage;
173: }
174:
175: public String getCachedOutput() {
176: return cachedOutput;
177: }
178:
179: public void setCachedOutput(String cachedOutput) {
180: this .cachedOutput = cachedOutput;
181: }
182:
183: public void render(Writer writer, Map parameterMap,
184: CallData callData) throws ContelligentException,
185: IOException {
186: final boolean production = callData.getActualManager().isRoot();
187: int itemsPerPage = this .itemsPerPage;
188: if (parameterMap != null
189: && parameterMap.containsKey(ITEMS_PER_PAGE)) {
190: itemsPerPage = Integer.valueOf(
191: ((String[]) parameterMap.get(ITEMS_PER_PAGE))[0])
192: .intValue();
193: }
194: int currentPosition = 0;
195: String sessionStore = getComponentContext().getPath()
196: .toString();
197: if (callData
198: .getRequestAttribute(CURRENT_POSITION, sessionStore) != null) {
199: currentPosition = ((Integer) callData.getRequestAttribute(
200: CURRENT_POSITION, sessionStore)).intValue();
201: }
202: log.debug("Current position is: " + currentPosition);
203: log.debug("Items per Page: " + itemsPerPage);
204: // collect fragments
205: final boolean debugEnabled = log.isDebugEnabled();
206: if (debugEnabled) {
207: log.debug("Start composing query");
208: }
209:
210: ComponentManager manager = callData.getActualManager();
211: try {
212: Component content = manager.getSubcomponent(this , query,
213: callData);
214: if (content instanceof Query) {
215:
216: if (parameterMap != null
217: && parameterMap.containsKey(INFO)) {
218: String key = ((String[]) parameterMap.get(INFO))[0];
219: if (key != null) {
220: int totalItems = ((Query) content)
221: .getQueryResult(callData).size();
222: if (key.equals(ITEMS_PER_PAGE)) {
223: if (itemsPerPage > 0) {
224: writer.write("" + itemsPerPage);
225: }
226: } else if (key.equals(FIRST_VISIBLE_ITEM)) {
227: writer.write("" + (currentPosition + 1));
228: } else if (key.equals(LAST_VISIBLE_ITEM)) {
229: int lastVisibleItem = Math.min(
230: currentPosition + itemsPerPage + 1,
231: totalItems + 1);
232: writer.write("" + lastVisibleItem);
233: } else if (key.equals(TOTAL_ITEMS)) {
234: writer.write("" + (totalItems));
235: }
236: }
237: return;
238: }
239:
240: List currentQuery = ((Query) content)
241: .getQueryResult(callData);
242: // check if query result has changed
243: synchronized (this ) {
244: if (!cacheOutput
245: || currentQuery != cachedQueryResult
246: || !production || cachedOutput == null
247: || cachedQueryResult == null) {
248: StringWriter cacheWriter = new StringWriter(256);
249: // load QueryResultEntry-Components
250: List queryResultEntries = new ArrayList();
251: for (Iterator i = getSubcomponentNames(); i
252: .hasNext();) {
253: String childName = (String) i.next();
254: ComponentPath childPath = getComponentContext()
255: .getPath().append(childName);
256: try {
257: Component child = manager.getComponent(
258: childPath, callData);
259: if (child instanceof QueryResultEntry) {
260: queryResultEntries.add(child);
261: } else if (child instanceof Folder) {
262: for (Iterator j = ((Folder) child)
263: .getSubcomponentNames(); j
264: .hasNext();) {
265: ComponentPath folderChildPath = new ComponentPath(
266: (String) j.next());
267: try {
268: Component folderChild = manager
269: .getSubcomponent(
270: (Folder) child,
271: folderChildPath,
272: callData);
273: if (folderChild instanceof QueryResultEntry) {
274: queryResultEntries
275: .add(folderChild);
276: }
277: } catch (ComponentNotFoundException e) {
278: log.error(
279: "Could not resolve child with path '"
280: + childPath
281: + "'", e);
282: }
283: }
284: }
285: } catch (ComponentNotFoundException e) {
286: log.error(
287: "Could not resolve child with path '"
288: + childPath + "'", e);
289: }
290: }
291: TemplateWriter[] templates = new TemplateWriter[numberOfTemplates];
292: templates[EMPTY_TEMPLATE] = loadTemplate(
293: manager, EMPTY, callData);
294: templates[SELECTED_TEMPLATE] = loadTemplate(
295: manager, SELECTED, callData);
296: templates[PRE_TEMPLATE] = loadTemplate(manager,
297: PRE, callData);
298: templates[POST_TEMPLATE] = loadTemplate(
299: manager, POST, callData);
300: templates[FIRST_TEMPLATE] = loadTemplate(
301: manager, FIRST, callData);
302: templates[LAST_TEMPLATE] = loadTemplate(
303: manager, LAST, callData);
304: templates[EVEN_TEMPLATE] = loadTemplate(
305: manager, EVEN, callData);
306: templates[ODD_TEMPLATE] = loadTemplate(manager,
307: ODD, callData);
308: templates[DEFAULT_TEMPLATE] = loadTemplate(
309: manager, DEFAULT, callData);
310: String environment = callData.getEnvironment();
311: ComponentPath currentPage = callData
312: .getContelligentSession()
313: .getHistoryPage(environment, 0);
314:
315: boolean empty = true;
316: int end = currentQuery.size();
317: if (itemsPerPage > 0)
318: end = currentPosition + itemsPerPage;
319: if (end > currentQuery.size())
320: end = currentQuery.size();
321: for (int i = currentPosition; i < end; i++) {
322: callData.setRequestAttribute(
323: CURRENT_ITEM_NUMBER, String
324: .valueOf(i + 1));
325: QueryResult result = (QueryResult) currentQuery
326: .get(i);
327: ComponentPath childPath = null;
328: if (result instanceof ComponentQueryResult) {
329: childPath = ((ComponentQueryResult) result)
330: .getComponentPath();
331: callData.setRequestAttribute(
332: CURRENT_ITEM_PATH, childPath
333: .toString());
334: callData.setRequestAttribute(
335: CURRENT_ITEM_DIR, childPath
336: .getDir().toString());
337: callData.setRequestAttribute(
338: CURRENT_ITEM_NAME, childPath
339: .getName().toString());
340: }
341: try {
342: // evaluate QueryResultEntries
343: for (Iterator qit = queryResultEntries
344: .iterator(); qit.hasNext();) {
345: QueryResultEntry entry = (QueryResultEntry) qit
346: .next();
347: String key = entry.getKey();
348: String renderedEntry = entry
349: .evaluate(result, callData);
350: callData.setRequestAttribute(key,
351: renderedEntry);
352: }
353: int templateToUse = DEFAULT_TEMPLATE;
354: if (templates[SELECTED_TEMPLATE] != null
355: && result instanceof ComponentQueryResult
356: && currentPage != null
357: && currentPage
358: .isSubPathOf(childPath)) {
359: // check if component path to render equals
360: // current page and render 'selected'
361: // template if true
362: templateToUse = SELECTED_TEMPLATE;
363: } else if (i == 0
364: && templates[FIRST_TEMPLATE] != null) {
365: templateToUse = FIRST_TEMPLATE;
366: } else if (i == currentQuery.size() - 1
367: && templates[LAST_TEMPLATE] != null) {
368: templateToUse = LAST_TEMPLATE;
369: } else if ((i - currentPosition) % 2 == 1
370: && templates[EVEN_TEMPLATE] != null) {
371: templateToUse = EVEN_TEMPLATE;
372: } else if ((i - currentPosition) % 2 == 0
373: && templates[ODD_TEMPLATE] != null) {
374: templateToUse = ODD_TEMPLATE;
375: }
376: if (templates[templateToUse] == null) {
377: log
378: .error("Could not write template with state "
379: + templateToUse
380: + ", because it's not defined!");
381: } else {
382: if (templateToUse != EMPTY_TEMPLATE
383: && empty
384: && templates[PRE_TEMPLATE] != null)
385: templates[PRE_TEMPLATE].write(
386: cacheWriter, callData);
387: templates[templateToUse].write(
388: cacheWriter, callData);
389: empty = false;
390: }
391: } catch (ComponentNotFoundException e) {
392: log
393: .error(
394: "Could not resolve component for content!",
395: e);
396: }
397: }
398: if (empty && templates[EMPTY_TEMPLATE] != null) {
399: templates[EMPTY_TEMPLATE].write(
400: cacheWriter, callData);
401: } else if (templates[POST_TEMPLATE] != null) {
402: templates[POST_TEMPLATE].write(cacheWriter,
403: callData);
404: }
405: log.debug("Output calculated and cached!");
406: String output = cacheWriter.toString();
407: writer.write(output);
408: if (production) {
409: cachedQueryResult = currentQuery;
410: cachedOutput = output;
411: }
412: } else {
413: // render cached output;
414: writer.write(cachedOutput);
415: }
416: }
417: } else {
418: log.error("Could not render component '" + query
419: + "', because it is not of type selection!");
420: }
421: } catch (ComponentNotFoundException e) {
422: log.error("Could not resolve content folder: '"
423: + query.toPath() + "'", e);
424: }
425: if (debugEnabled) {
426: log.debug("Query composed");
427: }
428: }
429:
430: private TemplateWriter loadTemplate(ComponentManager manager,
431: ComponentPath subcomponent, CallData callData)
432: throws ContelligentException {
433: try {
434: Component component = manager.getSubcomponent(this ,
435: subcomponent, callData);
436: if (component instanceof ContentProvider
437: && ((ContentProvider) component).getContent() instanceof ContelligentStringContent) {
438: Template template = ((ContelligentStringContent) ((ContentProvider) component)
439: .getContent()).getTemplate(callData);
440: if (template == null) {
441: log
442: .error("Template of type string content is null for some reason...");
443: }
444: return new TemplateWriter(template, component);
445: }
446: log.info("Only string content is accepted for templates!");
447: } catch (ComponentNotFoundException e) {
448: log.debug("Template for state '" + subcomponent.getName()
449: + "' not defined");
450: return null;
451: }
452: return null;
453: }
454:
455: public Renderer getRenderer() {
456: return this ;
457: }
458:
459: public ParameterDescription[] getParameterDescription() {
460: return parameters;
461: }
462:
463: public Collection getSensitiveCategories() {
464: return null;
465: }
466:
467: class TemplateWriter {
468: private Template template;
469:
470: private Component component;
471:
472: public TemplateWriter(Template template, Component component) {
473: this .template = template;
474: this .component = component;
475: }
476:
477: public void write(Writer writer, CallData callData)
478: throws ContelligentException, IOException {
479: template.write(writer, component, callData);
480: }
481: }
482:
483: class EventListener implements EventQueueListener {
484: private ComponentPath path;
485:
486: public EventListener(ComponentPath path) {
487: this .path = path;
488: }
489:
490: public void onEvents(List eventList) {
491: for (Iterator i = eventList.iterator(); i.hasNext();) {
492: ContelligentEvent event = (ContelligentEvent) i.next();
493: if (event instanceof ComponentEvent) {
494: ComponentPath targetPath = ((ComponentEvent) event)
495: .getTargetPath();
496: if (targetPath.isSubPathOf(path)) {
497: synchronized (this ) {
498: cachedQueryResult = null;
499: }
500: }
501: }
502: }
503: }
504: }
505:
506: /*
507: * (non-Javadoc)
508: *
509: * @see de.finix.contelligent.ExternalRelationSource#relatedPaths()
510: */
511: public List relatedPaths() {
512: LinkedList list = new LinkedList();
513: list.addLast(query);
514: return list;
515: }
516:
517: /*
518: * (non-Javadoc)
519: *
520: * @see de.finix.contelligent.ExternalRelationSource#relatedPaths(java.util.List)
521: */
522: public void relatedPaths(List newTargetPaths)
523: throws ModificationVetoException {
524: if (newTargetPaths == null || newTargetPaths.size() == 0
525: || newTargetPaths.size() > 1) {
526: throw new ModificationVetoException(
527: "illegal state - newTargetPaths: '"
528: + newTargetPaths + "'");
529: }
530: setQuery((ComponentPath) newTargetPaths.get(0));
531: }
532: }
|