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.Form;
019: import org.apache.cocoon.forms.formmodel.Repeater;
020: import org.apache.cocoon.forms.FormContext;
021: import org.apache.cocoon.forms.FormsConstants;
022: import org.apache.cocoon.forms.util.I18nMessage;
023: import org.apache.cocoon.environment.Request;
024: import org.apache.avalon.framework.context.Context;
025: import org.apache.excalibur.xml.sax.XMLizable;
026: import org.outerj.daisy.repository.schema.DocumentType;
027: import org.outerj.daisy.repository.Repository;
028:
029: import java.util.*;
030:
031: /**
032: * Object representing the document editor form. It consists of multiple CForms.
033: * This object should be created by the {@link DocumentEditorFormBuilder}.
034: */
035: public class DocumentEditorForm {
036: static enum FormValidState {
037: VALID, NOT_VALID, NOT_VALIDATED
038: }
039:
040: /**
041: * Contains an instance of {@link PartFormInfo} for each part form.
042: */
043: private List<PartFormInfo> partFormInfos = new ArrayList<PartFormInfo>();
044: private Map<String, Form> partForms = new HashMap<String, Form>();
045: private Map<String, String> partFormTemplates = new HashMap<String, String>();
046:
047: private Form linksForm;
048: private Form fieldsForm;
049: private Form miscForm;
050: private Form additionalPartsAndFieldsForm;
051: private String documentName;
052: private XMLizable documentNameValidationError;
053: private boolean validateOnSave = true;
054: private boolean publishImmediately = false;
055: private long syncedWithLanguageId = -1;
056: private long syncedWithVersionId = -1;
057: private boolean isMajorChange = false;
058: private String changeComment = null;
059: private String activeFormName;
060: /** Contains all forms hashed on name. */
061: private Map<String, Form> forms = new HashMap<String, Form>();
062: /**
063: * hasBeenTriedToSave is set to true after the first time the user pressed
064: * the 'save' button. It determines whether validation should be performed
065: * and whether validation error should be shown.
066: */
067: private boolean hasBeenTriedToSave = false;
068: /**
069: * A cache of what forms have already been validated. This is needed to avoid
070: * frequent re-validation, which can be expensive.
071: */
072: private Map<String, FormValidState> formValidState = new HashMap<String, FormValidState>();
073: private final DocumentType documentType;
074: private final Repository repository;
075: private final Context context;
076: private final String documentId;
077: private final long documentBranchId;
078: private final long documentLanguageId;
079: private final String documentBranch;
080: private final String documentLanguage;
081:
082: protected DocumentEditorForm(DocumentType documentType,
083: String documentId, long documentBranchId,
084: String documentBranch, long documentLanguageId,
085: String documentLanguage, Repository repository,
086: Context context) {
087: // document type and repository are only stored in this object so that others
088: // (eg form validators) have access to them
089: this .documentType = documentType;
090: this .repository = repository;
091: this .context = context;
092: this .documentId = documentId;
093: this .documentBranchId = documentBranchId;
094: this .documentBranch = documentBranch;
095: this .documentLanguageId = documentLanguageId;
096: this .documentLanguage = documentLanguage;
097:
098: // Note: if there are parts of fields, then their forms will be set as
099: // initial form to display instead.
100: this .activeFormName = "links";
101: }
102:
103: public DocumentType getDocumentType() {
104: return documentType;
105: }
106:
107: public Repository getRepository() {
108: return repository;
109: }
110:
111: public String getDocumentId() {
112: return documentId;
113: }
114:
115: public long getDocumentBranchId() {
116: return documentBranchId;
117: }
118:
119: public String getDocumentBranch() {
120: return documentBranch;
121: }
122:
123: public long getDocumentLanguageId() {
124: return documentLanguageId;
125: }
126:
127: public String getDocumentLanguage() {
128: return documentLanguage;
129: }
130:
131: public boolean process(Request request, Locale locale,
132: String formName) throws Exception {
133: // Note: the activeForm request parameter contains the name of the form that
134: // should become active, while the formName argument contains the name
135: // of the form currently submitted.
136: String activeForm = request.getParameter("activeForm");
137: if (activeForm == null) {
138: throw new Exception("Missing request parameter: activeForm");
139: }
140: if (!forms.containsKey(activeForm)) {
141: throw new Exception(
142: "Invalid value for activeForm request parameter: "
143: + activeForm);
144: }
145: this .activeFormName = activeForm;
146:
147: Form form = getForm(formName);
148: form.process(new FormContext(request, locale));
149: formValidState.put(formName, FormValidState.NOT_VALIDATED);
150: boolean isSave = form.getSubmitWidget() == null;
151:
152: if (!hasBeenTriedToSave && isSave) {
153: hasBeenTriedToSave = true;
154: }
155:
156: // Handle cross-editor fields (not managed by CForms)
157: this .documentName = request.getParameter("name") != null ? request
158: .getParameter("name").trim()
159: : null;
160: boolean newValidateOnSave = request
161: .getParameter("validateOnSave") != null;
162: if (newValidateOnSave != this .validateOnSave) {
163: this .validateOnSave = newValidateOnSave;
164: // if the 'validate on save' flag changed, all forms should be revalidated
165: for (String someForm : formValidState.keySet()) {
166: formValidState.put(someForm,
167: FormValidState.NOT_VALIDATED);
168: }
169: }
170: this .publishImmediately = request
171: .getParameter("publishImmediately") != null;
172: this .isMajorChange = request.getParameter("majorChange") != null;
173: this .changeComment = request.getParameter("changeComment");
174: String syncedWithLanguageParam = request
175: .getParameter("syncedWithLanguageId");
176: this .syncedWithLanguageId = syncedWithLanguageParam == null ? -1
177: : Long.valueOf(syncedWithLanguageParam);
178: String syncedWithVersionParam = request
179: .getParameter("syncedWithVersionId");
180: this .syncedWithVersionId = syncedWithVersionParam == null ? -1
181: : Long.valueOf(syncedWithVersionParam);
182:
183: if (isSave) {
184: // validate all forms
185: boolean allFormsValid = true;
186: for (String currentFormName : forms.keySet()) {
187: if (!validateForm(currentFormName))
188: allFormsValid = false;
189: }
190: if (!documentNameValid())
191: allFormsValid = false;
192: return allFormsValid;
193: } else {
194: return false;
195: }
196: }
197:
198: public boolean documentNameValid() {
199: if (!hasBeenTriedToSave) {
200: return true;
201: }
202:
203: if (documentName == null || documentName.length() == 0) {
204: documentNameValidationError = new I18nMessage(
205: "general.field-required",
206: FormsConstants.I18N_CATALOGUE);
207: } else if (documentName.length() > 255) {
208: documentNameValidationError = new I18nMessage(
209: "validation.string.max-length",
210: new String[] { "255" },
211: FormsConstants.I18N_CATALOGUE);
212: } else {
213: documentNameValidationError = null;
214: }
215:
216: return documentNameValidationError == null;
217: }
218:
219: public XMLizable getDocumentNameValidationError() {
220: return documentNameValidationError;
221: }
222:
223: private Form getForm(String formName) throws Exception {
224: Form form = forms.get(formName);
225: if (form == null)
226: throw new Exception("Invalid form name: \"" + formName
227: + "\".");
228: return form;
229: }
230:
231: public Form getPartForm(String partName) {
232: return partForms.get("part-" + partName);
233: }
234:
235: public Form[] getPartForms() {
236: return partForms.values().toArray(new Form[0]);
237: }
238:
239: public Form getMiscForm() {
240: return miscForm;
241: }
242:
243: public Form getFieldsForm() {
244: return fieldsForm;
245: }
246:
247: public Form getLinksForm() {
248: return linksForm;
249: }
250:
251: public Form getAdditionalPartsAndFieldsForm() {
252: return additionalPartsAndFieldsForm;
253: }
254:
255: public void setActiveForm(String formName) throws Exception {
256: if (!forms.containsKey(formName)) {
257: throw new Exception("Invalid form name: \"" + formName
258: + "\".");
259: }
260: this .activeFormName = formName;
261: }
262:
263: public Form getActiveForm() {
264: return forms.get(activeFormName);
265: }
266:
267: public String getActiveFormName() {
268: return activeFormName;
269: }
270:
271: public String getActiveFormTemplate() {
272: if (activeFormName.equals("fields")) {
273: return "cocoon:/internal/documentEditor/fieldEditorFormTemplate";
274: } else if (activeFormName.startsWith("part-")) {
275: return partFormTemplates.get(activeFormName);
276: } else {
277: return "resources/form/doceditor_" + activeFormName
278: + "_template.xml";
279: }
280: }
281:
282: public Map<String, Object> getActiveFormTemplateViewData() {
283: Map<String, Object> viewData = (Map<String, Object>) getActiveForm()
284: .getAttribute("customViewData");
285: if (viewData != null) {
286: return viewData;
287: } else {
288: return Collections.emptyMap();
289: }
290: }
291:
292: protected void addPartForm(String partTypeName, Form form,
293: String formTemplate, String partLabel,
294: String partDescription, boolean isRequired) {
295: String formName = "part-" + partTypeName;
296: partForms.put(formName, form);
297: partFormTemplates.put(formName, formTemplate);
298: partFormInfos.add(new PartFormInfo(formName, partLabel,
299: partDescription, isRequired));
300: forms.put(formName, form);
301:
302: if (partForms.size() == 1) {
303: // this was the first part form, set it as initial form to show
304: activeFormName = formName;
305: }
306: }
307:
308: protected void setLinksForm(Form form) {
309: this .linksForm = form;
310: forms.put("links", form);
311: }
312:
313: protected void setFieldsForm(Form form) {
314: this .fieldsForm = form;
315: forms.put("fields", form);
316:
317: if (partForms.size() == 0)
318: activeFormName = "fields";
319: }
320:
321: protected void setMiscForm(Form form) {
322: this .miscForm = form;
323: forms.put("misc", form);
324: }
325:
326: protected void setAdditionalPartsAndFieldsForm(Form form) {
327: this .additionalPartsAndFieldsForm = form;
328: forms.put("additionalPartsAndFields", form);
329: }
330:
331: public boolean hasPartForms() {
332: return partForms.size() > 0;
333: }
334:
335: public boolean hasFieldsForm() {
336: return fieldsForm != null;
337: }
338:
339: public boolean hasAdditionalPartsOrFieldsForm() {
340: boolean hasAdditionalParts = ((Repeater) additionalPartsAndFieldsForm
341: .getChild("additionalParts")).getSize() > 0;
342: boolean hasAdditionalFields = ((Repeater) additionalPartsAndFieldsForm
343: .getChild("additionalFields")).getSize() > 0;
344:
345: return hasAdditionalParts || hasAdditionalFields;
346: }
347:
348: public List getPartFormInfos() {
349: return partFormInfos;
350: }
351:
352: public static class PartFormInfo {
353: private String partFormName;
354: private String partLabel;
355: private String partDescription;
356: private boolean isRequired;
357:
358: public PartFormInfo(String partFormName, String partLabel,
359: String partDescription, boolean isRequired) {
360: this .partFormName = partFormName;
361: this .partLabel = partLabel;
362: this .partDescription = partDescription;
363: this .isRequired = isRequired;
364: }
365:
366: public String getFormName() {
367: return partFormName;
368: }
369:
370: public String getLabel() {
371: return partLabel;
372: }
373:
374: public String getPartDescription() {
375: return partDescription;
376: }
377:
378: public boolean isRequired() {
379: return isRequired;
380: }
381: }
382:
383: public PartFormInfo getCurrentPartFormInfo() {
384: for (PartFormInfo partFormInfo : partFormInfos) {
385: if (partFormInfo.getFormName().equals(activeFormName))
386: return partFormInfo;
387: }
388: return null;
389: }
390:
391: public String getDocumentName() {
392: return documentName;
393: }
394:
395: public void setDocumentName(String name) {
396: this .documentName = name;
397: }
398:
399: public boolean getValidateOnSave() {
400: return validateOnSave;
401: }
402:
403: public void setValidateOnSave(boolean validateOnSave) {
404: this .validateOnSave = validateOnSave;
405: }
406:
407: public boolean getPublishImmediately() {
408: return publishImmediately;
409: }
410:
411: public void setPublishImmediately(boolean publishImmediately) {
412: this .publishImmediately = publishImmediately;
413: }
414:
415: public long getSyncedWithLanguageId() {
416: return syncedWithLanguageId;
417: }
418:
419: public void setSyncedWithLanguageId(long syncedWithLanguageId) {
420: this .syncedWithLanguageId = syncedWithLanguageId;
421: }
422:
423: public long getSyncedWithVersionId() {
424: return syncedWithVersionId;
425: }
426:
427: public void setSyncedWithVersionId(long syncedWithVersionId) {
428: this .syncedWithVersionId = syncedWithVersionId;
429: }
430:
431: public boolean getMajorChange() {
432: return isMajorChange;
433: }
434:
435: public void setMajorChange(boolean isMajorChange) {
436: this .isMajorChange = isMajorChange;
437: }
438:
439: public String getChangeComment() {
440: return changeComment;
441: }
442:
443: public void setChangeComment(String changeComment) {
444: this .changeComment = changeComment;
445: }
446:
447: private boolean validateForm(String formName) throws Exception {
448: FormValidState state = formValidState.get(formName);
449: if (state == null)
450: state = FormValidState.NOT_VALIDATED;
451:
452: if (state == FormValidState.NOT_VALIDATED) {
453: Form form = getForm(formName);
454: form.endProcessing(false);
455: form.validate();
456: formValidState.put(formName,
457: form.isValid() ? FormValidState.VALID
458: : FormValidState.NOT_VALID);
459: return form.isValid();
460: } else {
461: return state == FormValidState.VALID;
462: }
463: }
464:
465: public boolean isValid(String formName) throws Exception {
466: FormValidState state = formValidState.get(formName);
467: if (state == null || state == FormValidState.NOT_VALIDATED) {
468: if (!hasBeenTriedToSave) {
469: return true;
470: } else if (activeFormName.equals(formName)
471: && getActiveForm().getSubmitWidget() != null) {
472: return true;
473: } else {
474: return validateForm(formName);
475: }
476: } else if (state == FormValidState.VALID) {
477: return true;
478: } else if (state == FormValidState.NOT_VALID) {
479: return false;
480: } else {
481: throw new RuntimeException(
482: "Unexpected situation, FormValidState == " + state);
483: }
484: }
485:
486: /**
487: * Returns true if all part forms are valid. Useful to be called from the template.
488: */
489: public boolean arePartFormsValid() throws Exception {
490: for (String formName : partForms.keySet()) {
491: if (!isValid(formName))
492: return false;
493: }
494: return true;
495: }
496: }
|