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.CacheAdmin;
037: import com.flexive.shared.EJBLookup;
038: import com.flexive.shared.content.FxContent;
039: import com.flexive.shared.content.FxData;
040: import com.flexive.shared.content.FxGroupData;
041: import com.flexive.shared.content.FxPK;
042: import com.flexive.shared.exceptions.FxApplicationException;
043: import com.flexive.shared.exceptions.FxInvalidParameterException;
044: import com.flexive.shared.exceptions.FxNotFoundException;
045: import com.flexive.shared.interfaces.ContentEngine;
046: import com.flexive.shared.structure.FxAssignment;
047: import com.flexive.shared.structure.FxPropertyAssignment;
048: import com.flexive.shared.structure.FxType;
049: import com.flexive.shared.value.FxReference;
050: import com.flexive.shared.value.FxString;
051: import com.flexive.shared.value.FxValue;
052: import org.apache.commons.lang.StringUtils;
053: import org.apache.commons.logging.Log;
054: import org.apache.commons.logging.LogFactory;
055:
056: import javax.faces.component.UIOutput;
057: import javax.faces.context.FacesContext;
058: import javax.faces.event.FacesEvent;
059: import javax.faces.event.FacesListener;
060: import javax.faces.event.PhaseId;
061: import javax.faces.event.AbortProcessingException;
062: import java.io.IOException;
063: import java.util.ArrayList;
064: import java.util.HashMap;
065: import java.util.List;
066: import java.util.Map;
067:
068: /**
069: * <p/>
070: * Provides an editable view of an FxContent instance. A hashmap for accessing
071: * the properties is provided by the variable <code>#{var}</code>, the content instance
072: * itself can be obtained using <code>#{var}_content</code>.
073: * </p>
074: * <p/>
075: * For example:
076: * <pre>
077: * <fx:content pk="#{myBean.articlePk}" var="article">
078: * Title: <fx:value property="title"/>
079: * <h:commandLink action="#{myBean.save}">
080: * <f:setPropertyActionListener target="#{myBean.content}" value="#{article_content}"/>
081: * Save article
082: * </h:commandLink>
083: * </fx:content>
084: * </pre>
085: * </p>
086: *
087: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
088: * @version $Rev: 191 $
089: */
090: public class FxContentView extends UIOutput {
091: private static final Log LOG = LogFactory
092: .getLog(FxContentView.class);
093:
094: private FxPK pk;
095: private long type = -1;
096: private String typeName;
097: private String var;
098: private FxContent content;
099: private Map<FxPK, FxContent> contentMap = new HashMap<FxPK, FxContent>();
100: private boolean preserveContent = false;
101: private boolean explode = true;
102:
103: public FxContentView() {
104: setRendererType(null);
105: }
106:
107: public static String getExpression(String var, String xpath,
108: String suffix) {
109: return "#{" + var + "['" + xpath
110: + (StringUtils.isNotBlank(suffix) ? "$" + suffix : "")
111: + "']}";
112: }
113:
114: /**
115: * {@inheritDoc}
116: */
117: @Override
118: public void encodeBegin(FacesContext context) throws IOException {
119: provideContent(context);
120: }
121:
122: public void provideContent(FacesContext context) {
123: final Map requestMap = context.getExternalContext()
124: .getRequestMap();
125: try {
126: final FxPK pk = (FxPK) getPk();
127: getContent();
128: if (content == null
129: || (pk != null && !content.matchesPk(pk))) {
130: final ContentEngine contentInterface = EJBLookup
131: .getContentEngine();
132: if (pk == null || pk.isNew()) {
133: setContent(contentInterface
134: .initialize(getSelectedType().getId()));
135: } else {
136: setContent(contentInterface.load(pk));
137: if (explode) {
138: content.getRootGroup().explode(true);
139: }
140: }
141: contentMap.put(content.getPk(), content);
142: }
143: //noinspection unchecked
144: requestMap.put(getVar(), new ContentMap(content));
145: //noinspection unchecked
146: requestMap.put(getVar() + "_content", content);
147: } catch (FxApplicationException e) {
148: requestMap.remove(getVar());
149: LOG
150: .error("Failed to provide content: "
151: + e.getMessage(), e);
152: throw e.asRuntimeException();
153: }
154: }
155:
156: /**
157: * {@inheritDoc}
158: */
159: @Override
160: public void encodeEnd(FacesContext context) throws IOException {
161: removeContent(context);
162: }
163:
164: private void removeContent(FacesContext context) {
165: //context.getELContext().getVariableMapper().setVariable(getVar(), null);
166: context.getExternalContext().getRequestMap().remove(getVar());
167: // context.getExternalContext().getRequestMap().remove(getVar() + "_content");
168: }
169:
170: /**
171: * {@inheritDoc}
172: */
173: @Override
174: public void processDecodes(FacesContext context) {
175: provideContent(context);
176: super .processDecodes(context);
177: removeContent(context);
178: }
179:
180: /**
181: * {@inheritDoc}
182: */
183: @Override
184: public void processValidators(FacesContext context) {
185: provideContent(context);
186: super .processValidators(context);
187: removeContent(context);
188: }
189:
190: /**
191: * {@inheritDoc}
192: */
193: @Override
194: public void processUpdates(FacesContext context) {
195: provideContent(context);
196: super .processUpdates(context);
197: removeContent(context);
198: }
199:
200: /**
201: * {@inheritDoc}
202: */
203: @Override
204: public void broadcast(FacesEvent event)
205: throws AbortProcessingException {
206: if (event instanceof WrappedEvent) {
207: // unwrap and provide content variable in the event context
208: final FacesEvent target = ((WrappedEvent) event).event;
209: provideContent(FacesContext.getCurrentInstance());
210: target.getComponent().broadcast(target);
211: removeContent(FacesContext.getCurrentInstance());
212: } else {
213: super .broadcast(event);
214: }
215: }
216:
217: /**
218: * {@inheritDoc}
219: */
220: @Override
221: public void queueEvent(FacesEvent event) {
222: super .queueEvent(new WrappedEvent(this , event));
223: }
224:
225: public Object getPk() {
226: final Object pkValue = FxJsfComponentUtils.getValue(this , "pk");
227: if (pkValue != null) {
228: return FxPK.fromObject(pkValue);
229: }
230: return pk;
231: }
232:
233: public void setPk(Object pk) {
234: this .pk = pk != null ? FxPK.fromObject(pk) : null;
235: }
236:
237: public String getVar() {
238: if (var == null) {
239: var = FxJsfComponentUtils.getStringValue(this , "var");
240: }
241: return var;
242: }
243:
244: public void setVar(String var) {
245: this .var = var;
246: }
247:
248: public long getType() {
249: if (type == -1) {
250: type = FxJsfComponentUtils.getLongValue(this , "type", -1);
251: }
252: return type;
253: }
254:
255: public void setType(long type) {
256: this .type = type;
257: }
258:
259: public boolean isExplode() {
260: final Boolean value = FxJsfComponentUtils.getBooleanValue(this ,
261: "explode");
262: if (value != null) {
263: this .explode = value;
264: }
265: return explode;
266: }
267:
268: public void setExplode(boolean explode) {
269: this .explode = explode;
270: }
271:
272: public String getTypeName() {
273: if (typeName == null) {
274: typeName = FxJsfComponentUtils.getStringValue(this ,
275: "typeName");
276: }
277: return typeName;
278: }
279:
280: public void setTypeName(String typeName) {
281: this .typeName = typeName;
282: }
283:
284: private FxType getSelectedType() {
285: if (getType() != -1) {
286: return CacheAdmin.getEnvironment().getType(getType());
287: } else if (StringUtils.isNotBlank(getTypeName())) {
288: return CacheAdmin.getEnvironment().getType(getTypeName());
289: } else {
290: throw new FxNotFoundException(
291: "ex.jsf.contentView.type.required")
292: .asRuntimeException();
293: }
294: }
295:
296: public FxContent getContent() {
297: if (content == null) {
298: setContent((FxContent) FxJsfComponentUtils.getValue(this ,
299: "content"));
300: if (content != null) {
301: // we need to make sure the content instance still exists at
302: // the next request, so we store it in the component
303: preserveContent = true;
304: // initialize empty fields
305: content.getRootGroup().explode(true);
306: }
307: }
308: return content;
309: }
310:
311: public void setContent(FxContent content) {
312: this .content = content;
313: }
314:
315: public boolean isPreserveContent() {
316: final Boolean value = FxJsfComponentUtils.getBooleanValue(this ,
317: "preserveContent");
318: return value != null ? value : preserveContent;
319: }
320:
321: public void setPreserveContent(boolean preserveContent) {
322: this .preserveContent = preserveContent;
323: }
324:
325: @Override
326: public Object saveState(FacesContext facesContext) {
327: Object[] state = new Object[8];
328: state[0] = super .saveState(facesContext);
329: state[1] = pk; // note: don't call getPk() here - rely on the caller to provide a valid pk next time
330: state[2] = var;
331: state[3] = getType();
332: state[4] = typeName;
333: state[5] = preserveContent;
334: state[6] = preserveContent ? content : null;
335: state[7] = explode;
336: return state;
337: }
338:
339: @Override
340: public void restoreState(FacesContext facesContext, Object o) {
341: Object[] state = (Object[]) o;
342: super .restoreState(facesContext, state[0]);
343: pk = (FxPK) state[1];
344: var = (String) state[2];
345: type = (Long) state[3];
346: typeName = (String) state[4];
347: preserveContent = (Boolean) state[5];
348: content = preserveContent ? (FxContent) state[6] : null;
349: explode = (Boolean) state[7];
350: }
351:
352: public class ContentMap extends HashMap<String, Object> {
353: private static final long serialVersionUID = -6423903577633591618L;
354: private final String prefix;
355: private final FxContent content;
356:
357: public ContentMap(FxContent content) {
358: this (content, "");
359: }
360:
361: public ContentMap(FxContent content, String prefix) {
362: this .content = content;
363: this .prefix = prefix;
364: }
365:
366: @Override
367: public Object put(String key, Object value) {
368: final Object oldValue = get(key);
369: try {
370: content.setValue(((FxValue) value).getXPath(),
371: (FxValue) value);
372: } catch (FxApplicationException e) {
373: throw e.asRuntimeException();
374: }
375: return oldValue;
376: }
377:
378: @Override
379: public Object get(Object key) {
380: try {
381: String path = getCanonicalPath((String) key);
382: if (isListRequest(path)) {
383: return getList(path, false);
384: } else if (isListAllRequest(path)) {
385: return getList(path, true);
386: } else if (isLabelRequest(path)) {
387: return getLabel(path);
388: } else if (isNewValueRequest(path)) {
389: return getNewValue(path);
390: } else if (isResolveReferenceRequest(path)) {
391: return getResolvedReference(path);
392: } else if (isXPathRequest(path)) {
393: return getXPath(path);
394: } else {
395: return content.getValue(path);
396: }
397: } catch (FxNotFoundException e) {
398: return null;
399: } catch (FxInvalidParameterException e) {
400: return new FxString("?" + key + "?");
401: } catch (FxApplicationException e) {
402: throw e.asRuntimeException();
403: }
404: }
405:
406: private String getCanonicalPath(String path) {
407: return prefix + (path.startsWith("/") ? path : "/" + path);
408: }
409:
410: private boolean isLabelRequest(String path) {
411: return path.endsWith("$label");
412: }
413:
414: private boolean isListRequest(String path) {
415: return path.endsWith("$list");
416: }
417:
418: private boolean isListAllRequest(String path) {
419: return path.endsWith("$listAll");
420: }
421:
422: private boolean isNewValueRequest(String path) {
423: return path.endsWith("$new");
424: }
425:
426: private boolean isResolveReferenceRequest(String path) {
427: return path.endsWith("$");
428: //return path.indexOf(".@") > 0 && path.indexOf(".@") == path.lastIndexOf(".");
429: }
430:
431: private boolean isXPathRequest(String path) {
432: return path.endsWith("$xpath");
433: }
434:
435: private FxString getLabel(String path)
436: throws FxNotFoundException, FxInvalidParameterException {
437: final long assignmentId = content.getPropertyData(
438: StringUtils.replace(path, "$label", ""))
439: .getAssignmentId();
440: return CacheAdmin.getEnvironment().getAssignment(
441: assignmentId).getDisplayLabel();
442: }
443:
444: private List<?> getList(String path, boolean includeEmpty) {
445: path = StringUtils.replace(StringUtils.replace(path,
446: "$listAll", ""), "$list", "");
447: try {
448: final FxAssignment assignment = CacheAdmin
449: .getEnvironment().getType(content.getTypeId())
450: .getAssignment(path);
451: if (assignment instanceof FxPropertyAssignment) {
452: // iterate over property values
453: return content.getPropertyData(path).getValues(
454: false);
455: } else {
456: // create a content map for each child
457: List<ContentMap> rowMaps = new ArrayList<ContentMap>();
458: for (FxData row : content.getGroupData(path)
459: .getElements()) {
460: if (includeEmpty || !row.isEmpty()) {
461: rowMaps.add(new ContentMap(content, path
462: + "[" + row.getIndex() + "]"));
463: }
464: }
465: return rowMaps;
466: }
467: } catch (FxNotFoundException e) {
468: // return null object
469: return new ArrayList<Object>(0);
470: } catch (FxInvalidParameterException e) {
471: throw e.asRuntimeException();
472: }
473: }
474:
475: private FxValue getNewValue(String path) {
476: path = StringUtils.replace(path, "$new", "");
477: try {
478: /*final FxPropertyData data = content.getPropertyData(path);
479: if (data.getAssignmentMultiplicity().getMax() > 1 && data.getCreateableElements() > 0) {
480: return ((FxPropertyData) data.createNew(FxData.POSITION_BOTTOM)).getValue();
481: } */
482: // TODO use content API when available
483: final String[] groups = StringUtils.split(path, '/');
484: if (groups.length == 0) {
485: throw new FxInvalidParameterException("PATH",
486: "No new values may be created for " + path)
487: .asRuntimeException();
488: }
489: final StringBuilder newPath = new StringBuilder();
490: final StringBuilder xPathSoFar = new StringBuilder();
491: for (int i = 0; i < groups.length - 1; i++) {
492: final String group = groups[i];
493: xPathSoFar.append("/").append(group);
494: final FxGroupData data = content
495: .getGroupData(xPathSoFar.toString());
496: newPath.append("/").append(group).append('[')
497: .append(data.getOccurances() + 1).append(
498: "]");
499: }
500: final FxType type = CacheAdmin.getEnvironment()
501: .getType(content.getTypeId());
502: final FxValue value = ((FxPropertyAssignment) type
503: .getAssignment(path)).getEmptyValue();
504: // add property
505: newPath.append("/").append(groups[groups.length - 1]);
506: content.setValue(newPath.toString(), value);
507: return content.getValue(newPath.toString());
508: } catch (FxNotFoundException e) {
509: return new FxString("");
510: } catch (FxApplicationException e) {
511: throw e.asRuntimeException();
512: }
513: }
514:
515: private ContentMap getResolvedReference(String path) {
516: path = path.substring(0, path.length() - 1); // stip trailing $
517: // resolve referenced object
518: try {
519: final FxPK pk = ((FxReference) content.getValue(path))
520: .getDefaultTranslation();
521: if (contentMap.containsKey(pk)) {
522: return new ContentMap(contentMap.get(pk));
523: } else if (pk.isNew()) {
524: return null; // don't resolve empty references
525: }
526: final FxContent referencedContent = EJBLookup
527: .getContentEngine().load(pk);
528: contentMap.put(pk, referencedContent);
529: return new ContentMap(referencedContent);
530: } catch (FxApplicationException e) {
531: throw e.asRuntimeException();
532: }
533: }
534:
535: private String getXPath(String path) {
536: final String xpath = StringUtils
537: .replace(path, "$xpath", "").toUpperCase();
538: return xpath.endsWith("/") ? xpath.substring(0, xpath
539: .length() - 1) : xpath;
540: }
541: }
542:
543: private static class WrappedEvent extends FacesEvent {
544: private static final long serialVersionUID = 3341414461609445709L;
545:
546: private final FacesEvent event;
547:
548: public WrappedEvent(FxContentView component, FacesEvent event) {
549: super (component);
550: this .event = event;
551: }
552:
553: @Override
554: public PhaseId getPhaseId() {
555: return event.getPhaseId();
556: }
557:
558: @Override
559: public void setPhaseId(PhaseId phaseId) {
560: this .event.setPhaseId(phaseId);
561: }
562:
563: @Override
564: public boolean isAppropriateListener(FacesListener listener) {
565: return false;
566: }
567:
568: @Override
569: public void processListener(FacesListener listener) {
570: throw new IllegalStateException();
571: }
572:
573: @Override
574: public void queue() {
575: event.queue();
576: }
577: }
578: }
|