001: /*
002: * Copyright 2004 Outerthought bvba and Schaubroeck nv
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.outerj.daisy.frontend.editor;
017:
018: import org.apache.cocoon.forms.formmodel.*;
019: import org.apache.cocoon.forms.validation.WidgetValidator;
020: import org.apache.cocoon.forms.validation.ValidationErrorAware;
021: import org.apache.cocoon.forms.validation.ValidationError;
022: import org.apache.cocoon.forms.event.ValueChangedListener;
023: import org.apache.cocoon.forms.event.ValueChangedEvent;
024: import org.apache.cocoon.forms.event.ValueChangedListenerEnabled;
025: import org.apache.cocoon.forms.datatype.StaticSelectionList;
026: import org.apache.cocoon.forms.util.StringMessage;
027: import org.apache.cocoon.xml.IncludeXMLConsumer;
028: import org.apache.cocoon.components.flow.util.PipelineUtil;
029: import org.apache.cocoon.components.LifecycleHelper;
030: import org.apache.avalon.framework.service.ServiceManager;
031: import org.apache.avalon.framework.context.Context;
032: import org.apache.avalon.framework.logger.Logger;
033: import org.apache.xmlbeans.XmlCursor;
034: import org.outerj.daisy.repository.*;
035: import org.outerj.daisy.repository.Field;
036: import org.outerj.daisy.repository.variant.VariantManager;
037: import org.outerj.daisy.repository.schema.*;
038: import org.outerj.daisy.publisher.Publisher;
039: import org.outerj.daisy.frontend.util.XmlObjectXMLizable;
040: import org.outerx.daisy.x10Publisher.PublisherRequestDocument;
041: import org.outerx.daisy.x10Publisher.ResolveDocumentIdsDocument;
042: import org.outerx.daisy.x10.FieldTypeUseDocument;
043: import org.xml.sax.helpers.DefaultHandler;
044: import org.xml.sax.Attributes;
045: import org.xml.sax.SAXException;
046: import org.xml.sax.ContentHandler;
047:
048: import java.util.*;
049:
050: /**
051: * A default, generic, FieldEditor implementation that is suited for any sort of
052: * field. The form template and definition are generated by means of an XSL (via
053: * a Cocoon pipeline).
054: */
055: public class DefaultFieldEditor extends AbstractFieldEditor {
056: private FieldEditorContext fieldEditorContext;
057:
058: private DefaultFieldEditor(FieldTypeUse fieldTypeUse,
059: ServiceManager serviceManager, Context context,
060: Logger logger) {
061: super (fieldTypeUse, serviceManager, context, logger);
062: }
063:
064: public static class Factory implements FieldEditorFactory {
065: public FieldEditor getFieldEditor(FieldTypeUse fieldTypeUse,
066: Map properties, ServiceManager serviceManager,
067: Context context, Logger logger) {
068: return new DefaultFieldEditor(fieldTypeUse, serviceManager,
069: context, logger);
070: }
071: }
072:
073: public void generateFormTemplateFragment(
074: ContentHandler contentHandler, Locale locale)
075: throws Exception {
076: PipelineUtil pipelineUtil = new PipelineUtil();
077: try {
078: LifecycleHelper.setupComponent(pipelineUtil, logger,
079: context, serviceManager, null, false);
080: Map<String, Object> viewData = new HashMap<String, Object>();
081: viewData.put("fieldTypeUseToWidgetTemplateXsl",
082: "resources/xslt/fieldtype_to_widgettemplate.xsl");
083: viewData.put("fieldTypeUse", fieldTypeUse);
084: viewData.put("fieldTypeUseXml", new XmlObjectXMLizable(
085: getAnnotatedFieldTypeUseXml(locale)));
086: pipelineUtil.processToSAX(
087: "internal/documentEditor/fieldToWidgetTemplate",
088: viewData, new IncludeXMLConsumer(contentHandler));
089: } finally {
090: LifecycleHelper.dispose(pipelineUtil);
091: }
092: }
093:
094: public void generateFormDefinitionFragment(
095: ContentHandler contentHandler, Locale locale)
096: throws Exception {
097: if (fieldType.isHierarchical()
098: && fieldType.getValueType() != ValueType.STRING
099: && fieldType.getValueType() != ValueType.LINK)
100: throw new RuntimeException(
101: "The document editor does currently not support hierarchical fields of type "
102: + fieldType.getValueType());
103:
104: PipelineUtil pipelineUtil = new PipelineUtil();
105: try {
106: LifecycleHelper.setupComponent(pipelineUtil, logger,
107: context, serviceManager, null, false);
108: Map<String, Object> viewData = new HashMap<String, Object>();
109: viewData.put("fieldTypeUseToWidgetDefinitionXsl",
110: "resources/xslt/fieldtype_to_widgetdefinition.xsl");
111: viewData.put("fieldTypeUse", fieldTypeUse);
112: viewData.put("fieldTypeUseXml", new XmlObjectXMLizable(
113: getAnnotatedFieldTypeUseXml(locale)));
114: pipelineUtil.processToSAX(
115: "internal/documentEditor/fieldToWidgetDefinition",
116: viewData, new IncludeXMLConsumer(contentHandler));
117: } finally {
118: LifecycleHelper.dispose(pipelineUtil);
119: }
120: }
121:
122: public void init(Widget parentWidget,
123: FieldEditorContext fieldEditorContext) {
124: this .fieldEditorContext = fieldEditorContext;
125: this .repository = fieldEditorContext.getRepository();
126: // Look up and store widget reference
127: this .widget = ((ContainerWidget) parentWidget)
128: .getChild("field");
129:
130: // set selection list
131: SelectionList selectionList = fieldType.getSelectionList();
132: if (selectionList != null && !fieldType.getAllowFreeEntry()
133: && !fieldType.getLoadSelectionListAsync()) {
134: if (widget instanceof org.apache.cocoon.forms.formmodel.Field) {
135: org.apache.cocoon.forms.formmodel.Field field = (org.apache.cocoon.forms.formmodel.Field) widget;
136: field.setSelectionList(new SelectionListAdapter(field
137: .getDatatype(), selectionList, true, fieldType
138: .getValueType(), fieldType.isHierarchical(),
139: fieldEditorContext.getRepository()
140: .getVariantManager(),
141: fieldEditorContext.getDocumentBranchId(),
142: fieldEditorContext.getDocumentLanguageId()));
143: } else if (widget instanceof MultiValueField) {
144: MultiValueField field = (MultiValueField) widget;
145: field.setSelectionList(new SelectionListAdapter(field
146: .getDatatype(), selectionList, false, fieldType
147: .getValueType(), fieldType.isHierarchical(),
148: fieldEditorContext.getRepository()
149: .getVariantManager(),
150: fieldEditorContext.getDocumentBranchId(),
151: fieldEditorContext.getDocumentLanguageId()));
152: }
153: }
154:
155: // for link field types: add validator and value changed listener to
156: // load document name(s)
157: if (fieldType.getValueType() == ValueType.LINK) {
158: widget.addValidator(new LinkValidator(fieldEditorContext
159: .getRepository(), fieldType.isHierarchical()));
160: if (fieldType.getAllowFreeEntry()
161: || fieldType.getSelectionList() == null
162: || fieldType.getLoadSelectionListAsync())
163: ((ValueChangedListenerEnabled) widget)
164: .addValueChangedListener(new LinkLabelLoader(
165: fieldEditorContext, fieldType
166: .isHierarchical()));
167: } else if (fieldType.getLoadSelectionListAsync()
168: && fieldType.getSelectionList() != null) {
169: ((ValueChangedListenerEnabled) widget)
170: .addValueChangedListener(new LabelLoader());
171: }
172:
173: widget.setState(fieldTypeUse.isEditable() ? WidgetState.ACTIVE
174: : WidgetState.DISABLED);
175: }
176:
177: public void load(Document document) throws Exception {
178: Field field = document.getField(fieldType.getId());
179: Object value = field.getValue();
180:
181: if (fieldType.isHierarchical() && fieldType.isMultiValue()) {
182: value = HierarchicalFieldHelper
183: .convertHierarchyPathsToString((Object[]) value,
184: field.getValueType(), repository);
185: } else if (fieldType.isHierarchical()) {
186: value = HierarchicalFieldHelper
187: .convertHierarchyPathToString(
188: (HierarchyPath) value,
189: field.getValueType(), repository);
190: } else if (fieldType.getValueType() == ValueType.LINK) {
191: // special handling for link-field type: convert VariantKey objects to string
192: if (fieldType.isMultiValue()) {
193: Object[] variantKeys = (Object[]) value;
194: String[] values = new String[variantKeys.length];
195: for (int i = 0; i < variantKeys.length; i++) {
196: values[i] = LinkFieldHelper.variantKeyToString(
197: (VariantKey) variantKeys[i], repository
198: .getVariantManager());
199: }
200: value = values;
201: } else {
202: value = LinkFieldHelper.variantKeyToString(
203: (VariantKey) value, repository
204: .getVariantManager());
205: }
206: }
207:
208: widget.setValue(value);
209: }
210:
211: @Override
212: public String getDefinitionStylesheet() {
213: // not used
214: return null;
215: }
216:
217: @Override
218: public String getDefinitionTemplate() {
219: // not used
220: return null;
221: }
222:
223: @Override
224: public String getTemplateStylesheet() {
225: // not used
226: return null;
227: }
228:
229: @Override
230: public String getTemplateTemplate() {
231: // not used
232: return null;
233: }
234:
235: static class LinkValidator implements WidgetValidator {
236: private Repository repository;
237:
238: private boolean isHierarchical;
239:
240: public LinkValidator(Repository repository,
241: boolean isHierarchical) {
242: this .repository = repository;
243: this .isHierarchical = isHierarchical;
244: }
245:
246: public boolean validate(Widget widget) {
247: return LinkFieldHelper.validate(widget, isHierarchical,
248: repository);
249: }
250: }
251:
252: /**
253: * Loads labels for fields or multivalue fields from the Daisy selection
254: * list. Also supports string hierarchical fields (link hierarchical fields
255: * are handled by LinkLabelLoader)
256: */
257: class LabelLoader implements ValueChangedListener {
258:
259: public void valueChanged(ValueChangedEvent valueChangedEvent) {
260: Object value = valueChangedEvent.getNewValue();
261: if (value == null) {
262: if (valueChangedEvent.getSourceWidget() instanceof org.apache.cocoon.forms.formmodel.Field)
263: valueChangedEvent.getSourceWidget().lookupWidget(
264: "../field-label").setValue(null);
265: return;
266: }
267:
268: if (valueChangedEvent.getSourceWidget() instanceof MultiValueField) {
269: MultiValueField field = (MultiValueField) valueChangedEvent
270: .getSourceWidget();
271: Object[] values = (Object[]) field.getValue();
272: String[] labels = new String[values.length];
273: for (int i = 0; i < values.length; i++)
274: labels[i] = getLabel(values[i], field);
275:
276: // Set labels by means of a selection list
277: StaticSelectionList selectionList = new StaticSelectionList(
278: field.getDatatype());
279: for (int i = 0; i < values.length; i++)
280: selectionList.addItem(values[i], new StringMessage(
281: labels[i]));
282: field.setSelectionList(selectionList);
283: } else {
284: org.apache.cocoon.forms.formmodel.Field field = (org.apache.cocoon.forms.formmodel.Field) valueChangedEvent
285: .getSourceWidget();
286: Widget labelWidget = valueChangedEvent
287: .getSourceWidget().lookupWidget(
288: "../field-label");
289: labelWidget.setValue(getLabel(value, field));
290: }
291: }
292:
293: private String getLabel(Object value, DataWidget field) {
294: if (fieldType.isHierarchical()) {
295: return getHierarchicalLabel((String) value);
296: } else {
297: String label = fieldType.getSelectionList()
298: .getItemLabel(value,
299: fieldEditorContext.getLocale());
300: if (label == null)
301: label = field.getDatatype().convertToString(value,
302: fieldEditorContext.getLocale());
303: return label;
304: }
305: }
306:
307: private String getHierarchicalLabel(String value) {
308: if (fieldType.getValueType() != ValueType.STRING) {
309: // Note: link fields are treated in LinkLabelLoader
310: throw new RuntimeException(
311: "Unexpected valuetype for hierarhical field: "
312: + fieldType.getValueType());
313: }
314:
315: String[] elements = HierarchicalFieldHelper
316: .parseHierarchicalInput(value);
317: String[] labels = getLabelsFromList(elements);
318: StringBuilder builder = new StringBuilder();
319: for (int i = 0; i < labels.length; i++) {
320: if (builder.length() > 0)
321: builder.append(" / ");
322: if (labels[i] != null)
323: builder.append(labels[i]);
324: else
325: builder.append(elements[i]);
326: }
327:
328: return builder.toString();
329: }
330:
331: private String[] getLabelsFromList(String[] elements) {
332: String[] labels = new String[elements.length];
333: if (fieldType.getSelectionList() instanceof org.outerj.daisy.repository.schema.StaticSelectionList) {
334: StaticListItemParent item = (org.outerj.daisy.repository.schema.StaticSelectionList) fieldType
335: .getSelectionList();
336: for (int i = 0; i < labels.length; i++) {
337: item = item.getItem(elements[i]);
338: if (item == null)
339: break;
340: labels[i] = ((ListItem) item)
341: .getLabel(fieldEditorContext.getLocale());
342: }
343: }
344: return labels;
345: }
346: }
347:
348: /**
349: * A listener for the link fields that retrieves the document names of the
350: * link targets.
351: */
352: class LinkLabelLoader implements ValueChangedListener {
353: private FieldEditorContext fieldEditorContext;
354:
355: private boolean isHierarchical;
356:
357: public LinkLabelLoader(FieldEditorContext fieldEditorContext,
358: boolean isHierarchical) {
359: this .fieldEditorContext = fieldEditorContext;
360: this .isHierarchical = isHierarchical;
361: }
362:
363: public void valueChanged(ValueChangedEvent valueChangedEvent) {
364: try {
365: Object value = valueChangedEvent.getNewValue();
366: if (value == null) {
367: if (valueChangedEvent.getSourceWidget() instanceof org.apache.cocoon.forms.formmodel.Field)
368: valueChangedEvent.getSourceWidget()
369: .lookupWidget("../field-label")
370: .setValue(null);
371: return;
372: }
373:
374: if (!(value instanceof Object[])) {
375: value = new Object[] { value };
376: }
377: Object[] values = (Object[]) value;
378:
379: // expand hierarchical values
380: Object[] expandResult = expandHierarchicalValues(values);
381: Object[] expandedValues = (Object[]) expandResult[0];
382: int[] valueCounts = (int[]) expandResult[1];
383:
384: // convert strings to VariantKey objects
385: expandedValues = parseValues(expandedValues);
386:
387: // load the document names
388: String[] names = getDocumentNames(expandedValues);
389:
390: // possibly overwrite names with labels from selection list, if
391: // available
392: if (fieldType.getSelectionList() instanceof org.outerj.daisy.repository.schema.StaticSelectionList) {
393: getLabelsFromList(expandedValues, names,
394: valueCounts);
395: }
396:
397: // combine hierarchical values again
398: names = mergeHierarchicalValues(names, valueCounts);
399:
400: if (valueChangedEvent.getSourceWidget() instanceof MultiValueField) {
401: MultiValueField field = (MultiValueField) valueChangedEvent
402: .getSourceWidget();
403: StaticSelectionList selectionList = new StaticSelectionList(
404: field.getDatatype());
405: for (int i = 0; i < values.length; i++)
406: selectionList.addItem(values[i],
407: new StringMessage(names[i]));
408: field.setSelectionList(selectionList);
409: } else {
410: valueChangedEvent.getSourceWidget().lookupWidget(
411: "../field-label").setValue(names[0]);
412: }
413: } catch (Exception e) {
414: // ignore (link in wrong format, ...)
415: }
416: }
417:
418: private String[] getDocumentNames(Object[] values)
419: throws RepositoryException, SAXException {
420: PublisherRequestDocument pubReqDoc = PublisherRequestDocument.Factory
421: .newInstance();
422: PublisherRequestDocument.PublisherRequest pubReq = pubReqDoc
423: .addNewPublisherRequest();
424: ResolveDocumentIdsDocument.ResolveDocumentIds resolveDocIds = pubReq
425: .addNewResolveDocumentIds();
426: resolveDocIds.setBranch(String.valueOf(fieldEditorContext
427: .getDocumentBranchId()));
428: resolveDocIds.setLanguage(String.valueOf(fieldEditorContext
429: .getDocumentLanguageId()));
430:
431: for (Object value : values) {
432: VariantKey variantKey = (VariantKey) value;
433: ResolveDocumentIdsDocument.ResolveDocumentIds.Document doc = resolveDocIds
434: .addNewDocument();
435: doc.setId(variantKey.getDocumentId());
436: if (variantKey.getBranchId() != -1)
437: doc.setBranch(String.valueOf(variantKey
438: .getBranchId()));
439: if (variantKey.getLanguageId() != -1)
440: doc.setLanguage(String.valueOf(variantKey
441: .getLanguageId()));
442: }
443:
444: Publisher publisher = (Publisher) fieldEditorContext
445: .getRepository().getExtension("Publisher");
446: DocNamesReceiver docNamesReceiver = new DocNamesReceiver(
447: values.length);
448: publisher.processRequest(pubReqDoc, docNamesReceiver);
449: return docNamesReceiver.getNames();
450: }
451:
452: private void getLabelsFromList(Object[] values,
453: String[] labels, int[] valueCounts) {
454: SelectionList selectionList = fieldType.getSelectionList();
455: int c = 0;
456: for (int i = 0; i < valueCounts.length; i++) {
457: if (valueCounts[i] == 1) {
458: VariantKey variantKey = (VariantKey) values[i];
459: String label = selectionList.getItemLabel(
460: variantKey, fieldEditorContext.getLocale());
461: if (label != null) {
462: labels[i] = label;
463: }
464: } else {
465: getLabelsFromList(values, labels, c, valueCounts[i]);
466: }
467: c += valueCounts[i];
468: }
469: }
470:
471: /**
472: * Treats the values in the elements array from start to length as a
473: * hierarchical path, loads the labels of these elements and stores them
474: * in the labels array.
475: */
476: private void getLabelsFromList(Object[] elements,
477: String[] labels, int start, int length) {
478: if (fieldType.getSelectionList() instanceof org.outerj.daisy.repository.schema.StaticSelectionList) {
479: StaticListItemParent item = (org.outerj.daisy.repository.schema.StaticSelectionList) fieldType
480: .getSelectionList();
481: for (int i = start; i < start + length; i++) {
482: item = item.getItem(elements[i]);
483: if (item == null)
484: break;
485: String label = ((ListItem) item)
486: .getLabel(fieldEditorContext.getLocale());
487: if (label != null)
488: labels[i] = label;
489: }
490: }
491: }
492:
493: private Object[] parseValues(Object[] values) {
494: Object[] result = new Object[values.length];
495: for (int i = 0; i < values.length; i++) {
496: result[i] = LinkFieldHelper.parseVariantKey(
497: (String) values[i], repository
498: .getVariantManager());
499: }
500: return result;
501: }
502:
503: private Object[] expandHierarchicalValues(Object[] values) {
504: if (isHierarchical) {
505: List<Object> expandedValues = new ArrayList<Object>();
506: int[] valueCounts = new int[values.length];
507: for (int i = 0; i < values.length; i++) {
508: String input = (String) values[i];
509: String[] parts = HierarchicalFieldHelper
510: .parseHierarchicalInput(input);
511: int counter = 0;
512: for (String part : parts) {
513: expandedValues.add(part);
514: counter++;
515: }
516: valueCounts[i] = counter;
517: }
518: return new Object[] { expandedValues.toArray(),
519: valueCounts };
520: } else {
521: int[] valueCounts = new int[values.length];
522: for (int i = 0; i < valueCounts.length; i++)
523: valueCounts[i] = 1;
524: return new Object[] { values, valueCounts };
525: }
526: }
527:
528: private String[] mergeHierarchicalValues(String[] names,
529: int[] valueCounts) {
530: String[] result = new String[valueCounts.length];
531: int counter = 0;
532: for (int i = 0; i < valueCounts.length; i++) {
533: if (valueCounts[i] == 1) {
534: result[i] = names[counter];
535: counter++;
536: } else {
537: StringBuilder builder = new StringBuilder();
538: for (int k = 0; k < valueCounts[i]; k++) {
539: if (k > 0)
540: builder.append(" / ");
541: builder.append(names[counter]);
542: counter++;
543: }
544: result[i] = builder.toString();
545: }
546: }
547: return result;
548: }
549: }
550:
551: static class DocNamesReceiver extends DefaultHandler {
552: private String[] names;
553:
554: private int pos = 0;
555:
556: public DocNamesReceiver(int count) {
557: this .names = new String[count];
558: }
559:
560: public String[] getNames() {
561: return names;
562: }
563:
564: public void startElement(String uri, String localName,
565: String qName, Attributes attributes)
566: throws SAXException {
567: if (uri.equals("http://outerx.org/daisy/1.0#publisher")
568: && localName.equals("document")) {
569: names[pos++] = attributes.getValue("name");
570: }
571: }
572: }
573: }
|