001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2007
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.faces.components.toolkit;
034:
035: import com.flexive.faces.FxJsfComponentUtils;
036: import com.flexive.shared.exceptions.FxApplicationException;
037: import com.flexive.shared.exceptions.FxInvalidParameterException;
038: import com.flexive.shared.exceptions.FxUpdateException;
039: import com.flexive.shared.search.FxResultSet;
040: import com.flexive.shared.search.query.QueryRootNode;
041: import com.flexive.shared.search.query.SqlQueryBuilder;
042: import groovy.lang.GroovyShell;
043: import org.apache.commons.lang.StringUtils;
044:
045: import javax.faces.component.EditableValueHolder;
046: import javax.faces.component.NamingContainer;
047: import javax.faces.component.UIComponent;
048: import javax.faces.component.UIComponentBase;
049: import javax.faces.context.FacesContext;
050: import javax.faces.context.ResponseWriter;
051: import javax.faces.event.AbortProcessingException;
052: import javax.faces.event.FacesEvent;
053: import javax.faces.event.FacesListener;
054: import javax.faces.event.PhaseId;
055: import java.io.IOException;
056: import java.io.Serializable;
057: import java.io.StringWriter;
058: import java.util.HashMap;
059: import java.util.Iterator;
060: import java.util.Map;
061:
062: /**
063: * <p>
064: * Provides a list of content instances matching the given search criterias.
065: * Currently, search conditions can be specified in the "groovyQuery" facet,
066: * which will then be dynamically evaluated with
067: * a {@link com.flexive.shared.scripting.groovy.GroovyQueryBuilder GroovyQueryBuilder}.
068: * </p>
069: * <p/>
070: * <p>
071: * <b>Facets</b>
072: * <ul>
073: * <li><b>groovyQuery</b>: an embedded groovy query that selects the PKs to be displayed.</li>
074: * <li><b>header</b>: defines an optional header to be rendered before the first result row.</li>
075: * <li><b>empty</b>: facet to be rendered instead of header and results when the query returns no results.</li>
076: * </ul>
077: * </p>
078: * <p/>
079: * <p>
080: * <b>FIXME:</b> it turns out that a "render-time" iterator tag is pretty complicated to implement.
081: * Thus this component contains large portions of Facelets' ui:repeat tag, and should be
082: * refactored into either extending UIRepeat or converted to a taghandler that
083: * uses a template with ui:repeat (or a similar tag).
084: * </p>
085: *
086: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
087: * @version $Rev: 1 $
088: */
089: public class FxContentList extends UIComponentBase implements
090: NamingContainer {
091: public static final String COMPONENT_TYPE = "flexive.FxContentList";
092: public static final String COMPONENT_FAMILY = "flexive";
093:
094: private String var;
095: private String indexVar;
096: private SqlQueryBuilder queryBuilder;
097: private FxResultSet result;
098: private int index;
099: private boolean explode = true;
100:
101: /**
102: * {@inheritDoc}
103: */
104: @Override
105: public boolean getRendersChildren() {
106: return true;
107: }
108:
109: @Override
110: public String getFamily() {
111: return COMPONENT_FAMILY;
112: }
113:
114: @Override
115: public void encodeChildren(FacesContext context) throws IOException {
116: process(context, PhaseId.RENDER_RESPONSE);
117: }
118:
119: @Override
120: public void processDecodes(FacesContext context) {
121: this .childState = null;
122: process(context, PhaseId.APPLY_REQUEST_VALUES);
123: }
124:
125: @Override
126: public void processUpdates(FacesContext context) {
127: process(context, PhaseId.UPDATE_MODEL_VALUES);
128: }
129:
130: @Override
131: public void processValidators(FacesContext context) {
132: process(context, PhaseId.PROCESS_VALIDATIONS);
133: }
134:
135: @Override
136: public void queueEvent(FacesEvent event) {
137: super .queueEvent(new IndexedEvent(this , event, index));
138: }
139:
140: @Override
141: public String getClientId(FacesContext facesContext) {
142: return super .getClientId(facesContext)
143: + NamingContainer.SEPARATOR_CHAR + index;
144: }
145:
146: @Override
147: public void broadcast(FacesEvent event)
148: throws AbortProcessingException {
149: if (event instanceof IndexedEvent) {
150: final IndexedEvent indexedEvent = (IndexedEvent) event;
151: final int prevIndex = getIndex();
152: try {
153: this .setIndex(indexedEvent.getIndex());
154: final FacesEvent target = indexedEvent.getTarget();
155: target.getComponent().broadcast(target);
156: } finally {
157: setIndex(prevIndex);
158: }
159: } else {
160: super .broadcast(event);
161: }
162: }
163:
164: private void process(FacesContext context, PhaseId phase) {
165: final Map<String, Object> requestMap = context
166: .getExternalContext().getRequestMap();
167: final String resultVar = getVar() + "_result";
168: try {
169: final FxResultSet result = getResult(context);
170: final FxContentView contentView;
171: if (phase == PhaseId.RENDER_RESPONSE
172: && (getChildCount() == 0 || !(getChildren().get(0) instanceof FxContentView))) {
173: // create content view, attach our children
174: contentView = new FxContentView();
175: contentView.setVar(getVar());
176: contentView.setExplode(isExplode());
177: // attach our children to the content view
178: contentView.getChildren().addAll(getChildren());
179: // set content view as our only child
180: this .getChildren().clear();
181: this .getChildren().add(contentView);
182: } else if (getChildCount() > 1
183: && getChildren().get(0) instanceof FxContentView) {
184: // move all other children that got appended by facelets (TODO: investigate why)
185: contentView = (FxContentView) getChildren().get(0);
186: contentView.getChildren().clear();
187: while (getChildren().size() > 1) {
188: contentView.getChildren().add(
189: getChildren().remove(1));
190: }
191: } else {
192: // get contentview child
193: contentView = (FxContentView) getChildren().get(0);
194: }
195: if (result.getRowCount() == 0) {
196: if (getFacet("empty") != null) {
197: applyPhase(context, getFacet("empty"), phase);
198: }
199: return;
200: }
201: // process result
202: setIndex(0);
203: if (result.getRowCount() > 0) {
204: // initialize data
205: provideContent();
206: }
207: requestMap.put(resultVar, getResult(context));
208: if (getFacet("header") != null) {
209: applyPhase(context, getFacet("header"), phase);
210: }
211: for (int i = 0; i < result.getRowCount(); i++) {
212: setIndex(i);
213: applyPhase(context, contentView, phase);
214: }
215: } catch (FxApplicationException e) {
216: throw e.asRuntimeException();
217: } catch (IOException e) {
218: throw new FxUpdateException("ex.jsf.contentView.render", e)
219: .asRuntimeException();
220: } finally {
221: if (requestMap.containsKey(resultVar)) {
222: requestMap.remove(resultVar);
223: }
224: if (StringUtils.isNotBlank(getIndexVar())
225: && requestMap.containsValue(getIndexVar())) {
226: requestMap.remove(getIndexVar());
227: }
228: }
229: }
230:
231: private void applyPhase(FacesContext context,
232: UIComponent component, PhaseId phase) throws IOException {
233: if (PhaseId.RENDER_RESPONSE.equals(phase)) {
234: component.encodeAll(context);
235: } else if (PhaseId.APPLY_REQUEST_VALUES.equals(phase)) {
236: component.processDecodes(context);
237: } else if (PhaseId.UPDATE_MODEL_VALUES.equals(phase)) {
238: component.processUpdates(context);
239: } else if (PhaseId.PROCESS_VALIDATIONS.equals(phase)) {
240: component.processValidators(context);
241: } else {
242: throw new FxInvalidParameterException("phase",
243: "ex.jsf.contentView.phase.invalid", phase)
244: .asRuntimeException();
245: }
246: }
247:
248: private FxResultSet getResult(FacesContext context)
249: throws IOException, FxApplicationException {
250: if (result != null) {
251: return result;
252: }
253: final UIComponent groovyQuery = getFacet("groovyQuery");
254: this .queryBuilder = this .getQueryBuilder() != null ? this
255: .getQueryBuilder() : new SqlQueryBuilder();
256: if (queryBuilder.getColumnNames().indexOf("@pk") == -1) {
257: queryBuilder.select("@pk");
258: }
259: if (groovyQuery != null) {
260: // get embedded groovy query
261: final ResponseWriter oldWriter = context
262: .getResponseWriter();
263: try {
264: final StringWriter queryWriter = new StringWriter();
265: context.setResponseWriter(context.getResponseWriter()
266: .cloneWithWriter(queryWriter));
267: groovyQuery.encodeAll(context);
268: context.setResponseWriter(oldWriter);
269: final GroovyShell shell = new GroovyShell();
270: shell.setVariable("builder", queryBuilder);
271: final QueryRootNode result = (QueryRootNode) shell
272: .parse(
273: "new com.flexive.shared.scripting.groovy.GroovyQueryBuilder(builder).select([\"@pk\"]) {"
274: + queryWriter + "}").run();
275: result.buildSqlQuery(queryBuilder);
276: } finally {
277: context.setResponseWriter(oldWriter);
278: }
279: }
280: result = queryBuilder.getResult();
281: return result;
282: }
283:
284: public String getVar() {
285: if (var == null) {
286: return FxJsfComponentUtils.getStringValue(this , "var");
287: }
288: return var;
289: }
290:
291: public void setVar(String var) {
292: this .var = var;
293: }
294:
295: public String getIndexVar() {
296: if (indexVar == null) {
297: return FxJsfComponentUtils.getStringValue(this , "indexVar");
298: }
299: return indexVar;
300: }
301:
302: public void setIndexVar(String indexVar) {
303: this .indexVar = indexVar;
304: }
305:
306: public boolean isExplode() {
307: final Boolean value = FxJsfComponentUtils.getBooleanValue(this ,
308: "explode");
309: if (value != null) {
310: this .explode = value;
311: }
312: return explode;
313: }
314:
315: public void setExplode(boolean explode) {
316: this .explode = explode;
317: }
318:
319: public SqlQueryBuilder getQueryBuilder() {
320: if (queryBuilder == null) {
321: return (SqlQueryBuilder) FxJsfComponentUtils.getValue(this ,
322: "queryBuilder");
323: }
324: return queryBuilder;
325: }
326:
327: public void setQueryBuilder(SqlQueryBuilder queryBuilder) {
328: this .queryBuilder = queryBuilder;
329: }
330:
331: public int getIndex() {
332: return index;
333: }
334:
335: public void setIndex(int index) {
336: saveChildState();
337: this .index = index;
338: provideContent();
339:
340: restoreChildState();
341: }
342:
343: private void provideContent() {
344: // store current PK in request
345: final FxContentView contentView = (FxContentView) getChildren()
346: .get(0);
347: contentView.setPk(result.getResultRow(index).getPk(
348: result.getColumnIndex("@pk")));
349: contentView.provideContent(FacesContext.getCurrentInstance());
350: if (StringUtils.isNotBlank(getIndexVar())) {
351: FacesContext.getCurrentInstance().getExternalContext()
352: .getRequestMap().put(getIndexVar(), index);
353: }
354: }
355:
356: @Override
357: public Object saveState(FacesContext facesContext) {
358: final Object[] state = new Object[7];
359: state[0] = super .saveState(facesContext);
360: state[1] = var;
361: state[2] = queryBuilder;
362: state[3] = childState;
363: state[4] = explode;
364: state[5] = result;
365: state[6] = indexVar;
366: return state;
367: }
368:
369: @Override
370: public void restoreState(FacesContext facesContext, Object o) {
371: final Object[] state = (Object[]) o;
372: super .restoreState(facesContext, state[0]);
373: this .var = (String) state[1];
374: this .queryBuilder = (SqlQueryBuilder) state[2];
375: this .childState = (Map) state[3];
376: this .explode = (Boolean) state[4];
377: this .result = (FxResultSet) state[5];
378: this .indexVar = (String) state[6];
379: }
380:
381: // ------------------- All code below copied from com.sun.facelets.component.UIRepeat -------------------
382: private Map childState;
383:
384: private Map getChildState() {
385: if (this .childState == null) {
386: this .childState = new HashMap();
387: }
388: return this .childState;
389: }
390:
391: private void saveChildState() {
392: if (this .getChildCount() > 0) {
393:
394: FacesContext faces = FacesContext.getCurrentInstance();
395:
396: for (UIComponent uiComponent : this .getChildren()) {
397: this .saveChildState(faces, uiComponent);
398: }
399: }
400: }
401:
402: private void saveChildState(FacesContext faces, UIComponent c) {
403: if (c instanceof EditableValueHolder && !c.isTransient()) {
404: String clientId = c.getClientId(faces);
405: SavedState ss = (SavedState) this .getChildState().get(
406: clientId);
407: if (ss == null) {
408: ss = new SavedState();
409: this .getChildState().put(clientId, ss);
410: }
411: ss.populate((EditableValueHolder) c);
412: }
413:
414: // continue hack
415: Iterator itr = c.getFacetsAndChildren();
416: while (itr.hasNext()) {
417: saveChildState(faces, (UIComponent) itr.next());
418: }
419: }
420:
421: private void restoreChildState() {
422: if (this .getChildCount() > 0) {
423: FacesContext faces = FacesContext.getCurrentInstance();
424:
425: for (UIComponent uiComponent : this .getChildren()) {
426: this .restoreChildState(faces, uiComponent);
427: }
428: }
429: }
430:
431: private void restoreChildState(FacesContext faces, UIComponent c) {
432: // reset id
433: String id = c.getId();
434: c.setId(id);
435:
436: // hack
437: if (c instanceof EditableValueHolder) {
438: EditableValueHolder evh = (EditableValueHolder) c;
439: String clientId = c.getClientId(faces);
440: SavedState ss = (SavedState) this .getChildState().get(
441: clientId);
442: if (ss != null) {
443: ss.apply(evh);
444: } else {
445: NullState.apply(evh);
446: }
447: }
448:
449: // continue hack
450: Iterator itr = c.getFacetsAndChildren();
451: while (itr.hasNext()) {
452: restoreChildState(faces, (UIComponent) itr.next());
453: }
454: }
455:
456: /**
457: * Taken from Facelets' UIRepeat component
458: */
459: private static class IndexedEvent extends FacesEvent {
460: private static final long serialVersionUID = 5274895541939738723L;
461: private final FacesEvent target;
462: private final int index;
463:
464: public IndexedEvent(FxContentList owner, FacesEvent target,
465: int index) {
466: super (owner);
467: this .target = target;
468: this .index = index;
469: }
470:
471: @Override
472: public PhaseId getPhaseId() {
473: return (this .target.getPhaseId());
474: }
475:
476: @Override
477: public void setPhaseId(PhaseId phaseId) {
478: this .target.setPhaseId(phaseId);
479: }
480:
481: @Override
482: public boolean isAppropriateListener(FacesListener listener) {
483: return this .target.isAppropriateListener(listener);
484: }
485:
486: @Override
487: public void processListener(FacesListener listener) {
488: FxContentList owner = (FxContentList) this .getComponent();
489: int prevIndex = owner.index;
490: try {
491: owner.setIndex(this .index);
492: this .target.processListener(listener);
493: } finally {
494: owner.setIndex(prevIndex);
495: }
496: }
497:
498: public int getIndex() {
499: return index;
500: }
501:
502: public FacesEvent getTarget() {
503: return target;
504: }
505:
506: }
507:
508: private final static SavedState NullState = new SavedState();
509:
510: // from RI
511: private final static class SavedState implements Serializable {
512:
513: private Object submittedValue;
514:
515: private static final long serialVersionUID = 2920252657338389849L;
516:
517: Object getSubmittedValue() {
518: return (this .submittedValue);
519: }
520:
521: void setSubmittedValue(Object submittedValue) {
522: this .submittedValue = submittedValue;
523: }
524:
525: private boolean valid = true;
526:
527: boolean isValid() {
528: return (this .valid);
529: }
530:
531: void setValid(boolean valid) {
532: this .valid = valid;
533: }
534:
535: private Object value;
536:
537: Object getValue() {
538: return (this .value);
539: }
540:
541: public void setValue(Object value) {
542: this .value = value;
543: }
544:
545: private boolean localValueSet;
546:
547: boolean isLocalValueSet() {
548: return (this .localValueSet);
549: }
550:
551: public void setLocalValueSet(boolean localValueSet) {
552: this .localValueSet = localValueSet;
553: }
554:
555: public String toString() {
556: return ("submittedValue: " + submittedValue + " value: "
557: + value + " localValueSet: " + localValueSet);
558: }
559:
560: public void populate(EditableValueHolder evh) {
561: this .value = evh.getValue();
562: this .valid = evh.isValid();
563: this .submittedValue = evh.getSubmittedValue();
564: this .localValueSet = evh.isLocalValueSet();
565: }
566:
567: public void apply(EditableValueHolder evh) {
568: evh.setValue(this.value);
569: evh.setValid(this.valid);
570: evh.setSubmittedValue(this.submittedValue);
571: evh.setLocalValueSet(this.localValueSet);
572: }
573: }
574:
575: }
|