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.outerj.daisy.frontend.util.*;
019: import org.outerj.daisy.frontend.components.siteconf.SiteConf;
020: import org.outerj.daisy.frontend.RequestUtil;
021: import org.outerj.daisy.frontend.FrontEndContext;
022: import org.outerj.daisy.repository.*;
023: import org.outerj.daisy.repository.schema.*;
024: import org.outerj.daisy.navigation.NavigationManager;
025: import org.outerj.daisy.navigation.NavigationLookupResult;
026: import org.outerj.daisy.navigation.LookupAlternative;
027: import org.outerj.daisy.navigation.NavigationVersionMode;
028: import org.outerj.daisy.publisher.Publisher;
029: import org.apache.avalon.framework.service.Serviceable;
030: import org.apache.avalon.framework.service.ServiceManager;
031: import org.apache.avalon.framework.service.ServiceException;
032: import org.apache.avalon.framework.context.Contextualizable;
033: import org.apache.avalon.framework.logger.LogEnabled;
034: import org.apache.avalon.framework.logger.Logger;
035: import org.apache.avalon.framework.configuration.Configuration;
036: import org.apache.cocoon.components.flow.apples.AppleRequest;
037: import org.apache.cocoon.components.flow.apples.AppleResponse;
038: import org.apache.cocoon.environment.Request;
039: import org.apache.cocoon.ResourceNotFoundException;
040: import org.apache.cocoon.xml.SaxBuffer;
041: import org.apache.cocoon.forms.formmodel.DataWidget;
042: import org.apache.cocoon.forms.datatype.Datatype;
043: import org.apache.cocoon.i18n.BundleFactory;
044: import org.apache.cocoon.i18n.Bundle;
045: import org.apache.commons.lang.StringUtils;
046: import org.outerx.daisy.x10Publisher.PublisherRequestDocument;
047: import org.outerx.daisy.x10Publisher.GroupDocument;
048: import org.outerx.daisy.x10Publisher.DocumentDocument;
049: import org.outerx.daisy.x10Publisher.AnnotatedDocumentDocument1;
050:
051: import java.util.*;
052:
053: /**
054: * This is the Apple controlling the document editing screen(s).
055: */
056: public class DocumentEditorApple extends AbstractDaisyApple implements
057: Serviceable, Contextualizable, LogEnabled {
058: private ServiceManager serviceManager;
059: private boolean init = false;
060: private Repository repository;
061: private DocumentType documentType;
062: private Document document;
063: private String lastPathPart;
064: private String navigationPath;
065: private String currentPath;
066: private DocumentEditorForm form;
067: private SiteConf siteConf;
068: private Locale locale;
069: private Map<String, Object> viewDataTemplate;
070: private boolean autoExtendLock = false;
071: private long lockExpires;
072: public static final int HEARTBEAT_INTERVAL = 10 * 60 * 1000; // 10 minutes
073: private Logger logger;
074: private String returnTo;
075: private boolean preSaveInteractionFinished = false; // avoids one could bypass the pre-save interaction
076:
077: public void enableLogging(Logger logger) {
078: this .logger = logger;
079: }
080:
081: public void service(ServiceManager serviceManager)
082: throws ServiceException {
083: this .serviceManager = serviceManager;
084: }
085:
086: protected void processRequest(AppleRequest appleRequest,
087: AppleResponse appleResponse) throws Exception {
088: if (!init) {
089: // start of a new editing session can only be initiated using POST (among other things
090: // because it can make persistent modifications like create locks)
091: if (!request.getMethod().equals("POST")
092: && !"true".equals(request
093: .getParameter("startWithGet"))) {
094: throw new HttpMethodNotAllowedException(request
095: .getMethod());
096: }
097:
098: repository = frontEndContext.getRepository();
099: lastPathPart = appleRequest
100: .getSitemapParameter("lastPathPart");
101: locale = frontEndContext.getLocale();
102: siteConf = frontEndContext.getSiteConf();
103: navigationPath = appleRequest
104: .getSitemapParameter("navigationPath");
105: String basePath = getMountPoint() + "/"
106: + siteConf.getName();
107: currentPath = basePath + "/" + navigationPath;
108: // returnTo = URL to return to after editing, optional.
109: returnTo = request.getParameter("returnTo");
110: init = true;
111: }
112:
113: if (document == null) {
114: if (lastPathPart.equals("new")) {
115: // A new document can be created from scratch, based on the content of another document (a template),
116: // as a new variant of an existing document, or using a Document object in a request attribute.
117: Document document;
118: if (request.getParameter("documentType") != null) {
119: String documentType = RequestUtil
120: .getStringParameter(request, "documentType");
121: long branchId = RequestUtil.getBranchId(request,
122: siteConf.getBranchId(), repository);
123: long languageId = RequestUtil.getLanguageId(
124: request, siteConf.getLanguageId(),
125: repository);
126: document = createNewDocument(documentType,
127: branchId, languageId);
128: document.setReferenceLanguageId(siteConf
129: .getDefaultReferenceLanguageId());
130: } else if (request.getParameter("template") != null) {
131: String template = RequestUtil.getStringParameter(
132: request, "template");
133: long branchId = RequestUtil.getBranchId(request,
134: siteConf.getBranchId(), repository);
135: long languageId = RequestUtil.getLanguageId(
136: request, siteConf.getLanguageId(),
137: repository);
138: document = createNewDocumentFromTemplate(template,
139: branchId, languageId);
140: } else if (request.getParameter("variantOf") != null) {
141: String documentId = RequestUtil.getStringParameter(
142: request, "variantOf");
143: long startBranchId = getBranch(request,
144: "startBranch");
145: long startLanguageId = getLanguage(request,
146: "startLanguage");
147: long newBranchId = getBranch(request, "newBranch");
148: long newLanguageId = getLanguage(request,
149: "newLanguage");
150: document = createNewDocumentVariant(documentId,
151: startBranchId, startLanguageId,
152: newBranchId, newLanguageId);
153: } else if (request.getParameter("templateDocument") != null) {
154: String requestAttrName = RequestUtil
155: .getStringParameter(request,
156: "templateDocument");
157: Object object = request
158: .getAttribute(requestAttrName);
159:
160: if (object == null)
161: throw new Exception(
162: "Nothing found in request attribute specified in templateDocument parameter: "
163: + requestAttrName);
164: if (!(object instanceof Document))
165: throw new Exception(
166: "Object specified in templateDocument parameter is of an incorrect type, got: "
167: + object.getClass().getName());
168:
169: document = (Document) object;
170: documentType = repository
171: .getRepositorySchema()
172: .getDocumentTypeById(
173: document.getDocumentTypeId(), false);
174: } else {
175: throw new Exception(
176: "Either template, documentType, variantOf or templateDocument parameter must be specified for the creation of a new document (variant).");
177: }
178: // assing document only here, indicates successful completion of this initialisation
179: this .document = document;
180: appleResponse.redirectTo(EncodingUtil
181: .encodePath(getPath()));
182: return;
183: } else {
184: long requestedBranchId = RequestUtil.getBranchId(
185: request, -1, repository);
186: long requestedLanguageId = RequestUtil.getLanguageId(
187: request, -1, repository);
188: NavigationManager navigationManager = (NavigationManager) repository
189: .getExtension("NavigationManager");
190: NavigationVersionMode navVersionMode = frontEndContext
191: .getVersionMode().getNavigationVersionMode();
192: NavigationLookupResult lookupResult = navigationManager
193: .lookup(
194: navigationPath,
195: requestedBranchId,
196: requestedLanguageId,
197: new LookupAlternative[] { new LookupAlternative(
198: siteConf.getName(), siteConf
199: .getCollectionId(),
200: siteConf.getNavigationDoc(),
201: navVersionMode) });
202: if (lookupResult.isNotFound()) {
203: throw new ResourceNotFoundException(
204: "Path not found: " + navigationPath);
205: } else if (lookupResult.isRedirect()
206: && lookupResult.getVariantKey() == null) {
207: throw new Exception(
208: "Can't handle redirect navigation nodes.");
209: }
210:
211: VariantKey variantKey = lookupResult.getVariantKey();
212: Document document = repository.getDocument(variantKey,
213: true);
214: documentType = repository.getRepositorySchema()
215: .getDocumentTypeById(
216: document.getDocumentTypeId(), false);
217:
218: boolean showLockWarnPage = false;
219: LockInfo lockInfo = document.getLockInfo(false);
220: if (lockInfo.hasLock()
221: && lockInfo.getUserId() != repository
222: .getUserId()) {
223: showLockWarnPage = true;
224: } else if (siteConf.getAutomaticLocking()) {
225: boolean success = document.lock(siteConf
226: .getDefaultLockTime(), siteConf
227: .getLockType());
228: lockInfo = document.getLockInfo(false);
229: if (!success) {
230: showLockWarnPage = true;
231: } else {
232: autoExtendLock = siteConf.getAutoExtendLock();
233: lockExpires = lockInfo.getTimeAcquired()
234: .getTime()
235: + lockInfo.getDuration();
236: // after locking the document, load it again to be sure that we have the latest version
237: // of it (somebody might have saved it during our last loading and taking the lock). In
238: // case of a pessimistic lock, the user can now be sure that saving the document will
239: // not give concurrent modification exceptions.
240: document = repository.getDocument(variantKey,
241: true);
242: }
243: }
244:
245: // Check if we should revert to an older version...
246: if (request.getParameter("versionId") != null) {
247: long versionId = RequestUtil.getLongParameter(
248: request, "versionId");
249: if (versionId > document.getLastVersionId()
250: || versionId < 1) {
251: throw new Exception(
252: "Specified version ID is out of range: "
253: + versionId);
254: }
255: revertDocument(document, versionId);
256: }
257:
258: // asigning document instance variable confirms init is done
259: this .document = document;
260:
261: if (showLockWarnPage) {
262: Map<String, Object> viewData = new HashMap<String, Object>();
263: viewData.put("lockInfo", lockInfo);
264: String userName = repository.getUserManager()
265: .getUserDisplayName(lockInfo.getUserId());
266: viewData.put("lockUserName", userName);
267: viewData.put("pageContext", frontEndContext
268: .getPageContext());
269: viewData.put("editPath", getPath());
270: viewData.put("documentPath", currentPath + ".html"
271: + getBranchLangQueryString());
272: viewData.put("pipeConf", GenericPipeConfig
273: .templatePipe("resources/xml/locked.xml"));
274:
275: appleResponse.sendPage("internal/genericPipe",
276: viewData);
277: return;
278: } else {
279: appleResponse.redirectTo(EncodingUtil
280: .encodePath(getPath()));
281: return;
282: }
283: }
284: }
285:
286: if (form == null) {
287: final DocumentEditorForm form = DocumentEditorFormBuilder
288: .build(documentType, document.getId(), document
289: .getBranchId(), document.getLanguageId(),
290: document.getReferenceLanguageId(),
291: serviceManager, getContext(), locale,
292: repository, logger);
293:
294: List<AvailableVariant> languageVariants = new ArrayList<AvailableVariant>();
295: for (AvailableVariant variant : document
296: .getAvailableVariants().getArray()) {
297: // availableLanguageVariants us used to determine the available syncedWith values, so it
298: // must be limited to the current branch. The client side js filters out the current document's language if required
299: if (variant.getBranchId() == document.getBranchId()) {
300: languageVariants.add(variant);
301: }
302: }
303:
304: viewDataTemplate = new HashMap<String, Object>();
305: viewDataTemplate.put("submitPath", getPath());
306: viewDataTemplate.put("collectionsArray", repository
307: .getCollectionManager().getCollections(false)
308: .getArray());
309: viewDataTemplate.put("locale", locale);
310: viewDataTemplate.put("htmlareaLang", locale.getLanguage());
311: viewDataTemplate.put("hasEditors", Boolean
312: .valueOf(hasEditors()));
313: viewDataTemplate.put("documentEditorForm", form);
314: viewDataTemplate.put("document", document);
315: viewDataTemplate.put("availableLanguageVariants",
316: languageVariants);
317:
318: DocumentBinding.load(form, document, repository, locale);
319:
320: if (document.isVariantNew()) {
321: form.setPublishImmediately(VersionState.PUBLISH
322: .equals(siteConf.getNewVersionStateDefault()));
323: form.setMajorChange(true);
324: } else {
325: Version lastVersion = document.getLastVersion();
326: // if last version was published, default to published
327: form.setPublishImmediately(VersionState.PUBLISH
328: .equals(lastVersion.getState()));
329: if (lastVersion.getSyncedWith() != null) {
330: form.setSyncedWithLanguageId(lastVersion
331: .getSyncedWith().getLanguageId());
332: form.setSyncedWithVersionId(lastVersion
333: .getSyncedWith().getVersionId());
334: }
335: }
336:
337: // Set form instance variable only here at the end since it is used to check
338: // if this block of code ended successfully
339: this .form = form;
340: }
341:
342: String resource = appleRequest.getSitemapParameter("resource");
343: if ("heartbeat".equals(resource)) {
344: LockInfo lockInfo = null;
345: if (autoExtendLock) {
346: int margin = 3 * 60 * 1000; // 3 minutes
347: if (lockExpires - System.currentTimeMillis()
348: - HEARTBEAT_INTERVAL - margin < 0) {
349: // time to extend the lock
350: boolean success = document.lock(siteConf
351: .getDefaultLockTime(), siteConf
352: .getLockType());
353: if (!success) {
354: lockInfo = document.getLockInfo(false);
355: }
356: }
357: }
358: Map<String, Object> viewData = new HashMap<String, Object>();
359: if (lockInfo != null) {
360: viewData.put("lockInfo", lockInfo);
361: String userName = repository.getUserManager()
362: .getUserDisplayName(lockInfo.getUserId());
363: viewData.put("lockUserName", userName);
364: }
365: GenericPipeConfig pipeConfig = GenericPipeConfig
366: .templateOnlyPipe("resources/xml/heartbeat.xml");
367: pipeConfig.setXmlSerializer();
368: viewData.put("pipeConf", pipeConfig);
369: appleResponse.sendPage("internal/genericPipe", viewData);
370: } else if ("selectionList".equals(resource)) {
371: handleSelectionListRequest(false, request, appleResponse);
372: } else if ("selectionList.xml".equals(resource)) {
373: handleSelectionListRequest(true, request, appleResponse);
374: } else if ("includePreviews".equals(resource)) {
375: handleIncludePreviewsRequest(request, appleResponse);
376: } else if ("saveAndClose".equals(resource)) {
377: if (!preSaveInteractionFinished) {
378: throw new Exception(
379: "Continuing save is not allowed before completing the editor pre-save interaction.");
380: }
381: saveAndCloseEditor(appleResponse);
382: } else if (resource == null) {
383: appleResponse.redirectTo(EncodingUtil.encodePath(getPath()
384: + "/" + form.getActiveFormName()));
385: } else {
386: preSaveInteractionFinished = false; // reset this to false each time the user goes back to the editor
387: String method = request.getMethod();
388: if (method.equals("GET")) {
389: form.setActiveForm(resource);
390: // show the form
391: appleResponse.sendPage(
392: "internal/documentEditor/editDocumentPipe",
393: getEditDocumentViewData(frontEndContext));
394: } else if (method.equals("POST")) {
395: if ("true"
396: .equals(request.getParameter("cancelEditing"))) {
397: document.releaseLock();
398: if (returnTo != null)
399: appleResponse.redirectTo(returnTo);
400: else if (document.getId() != null)
401: appleResponse
402: .redirectTo(EncodingUtil
403: .encodePathQuery(currentPath
404: + "/../"
405: + document.getId()
406: + ".html"
407: + (!document
408: .isVariantNew() ? getBranchLangQueryString()
409: : "")));
410: else
411: appleResponse.redirectTo(EncodingUtil
412: .encodePath(getMountPoint() + "/"
413: + siteConf.getName() + "/"));
414: } else {
415: boolean endProcessing = form.process(request,
416: locale, resource);
417:
418: if (endProcessing) {
419: DocumentBinding
420: .save(form, document, repository);
421: String preSavePath = getPreSavePath();
422: if (preSavePath == null) {
423: // There is no pre-save interaction (the usual case), go on and save.
424: saveAndCloseEditor(appleResponse);
425: } else {
426: if (preSavePath.startsWith("/"))
427: preSavePath = preSavePath.substring(1);
428: request
429: .setAttribute(
430: "daisy.documenteditor.preSaveInteractionContext",
431: new PreSaveInteractionContext());
432: appleResponse.redirectTo("cocoon://"
433: + siteConf.getName() + "/"
434: + preSavePath);
435: }
436: } else {
437: if (!form.getActiveFormName().equals(resource)) {
438: appleResponse
439: .redirectTo(EncodingUtil
440: .encodePath(getPath()
441: + "/"
442: + form
443: .getActiveFormName()));
444: } else {
445: appleResponse
446: .sendPage(
447: "internal/documentEditor/editDocumentPipe",
448: getEditDocumentViewData(frontEndContext));
449: }
450: }
451: }
452: } else {
453: throw new HttpMethodNotAllowedException(method);
454: }
455: }
456:
457: }
458:
459: private void saveAndCloseEditor(AppleResponse appleResponse)
460: throws RepositoryException {
461: long lastVersionId = document.getLastVersionId();
462: document.save(form.getValidateOnSave());
463: if (lastVersionId == document.getLastVersionId()) { // there was no new version, set the version-level properties manually
464: Version lastVersion = document.getLastVersion();
465: VersionState previousState = lastVersion.getState();
466: lastVersion
467: .setState(form.getPublishImmediately() ? VersionState.PUBLISH
468: : VersionState.DRAFT);
469:
470: lastVersion
471: .setChangeType(form.getMajorChange() ? ChangeType.MAJOR
472: : ChangeType.MINOR);
473:
474: // an empty change comment means "do not change the comment if not version is changed",
475: // otherwise, blanks can be used to indicate the comment must be cleared.
476: if (form.getChangeComment() != null
477: && form.getChangeComment().length() != 0) {
478: lastVersion.setChangeComment(StringUtils
479: .trimToNull(form.getChangeComment()));
480: }
481:
482: lastVersion.setSyncedWith(form.getSyncedWithLanguageId(),
483: form.getSyncedWithVersionId());
484:
485: try {
486: lastVersion.save();
487: } catch (AccessException e) {
488: // user is not allowed to publish: ignore
489: lastVersion.setState(previousState);
490: lastVersion.save();
491: }
492:
493: }
494: document.releaseLock();
495: if (returnTo != null)
496: appleResponse.redirectTo(returnTo);
497: else
498: appleResponse.redirectTo(EncodingUtil
499: .encodePathQuery(currentPath + "/../"
500: + document.getId() + ".html"
501: + getBranchLangQueryString()));
502: }
503:
504: private Map getEditDocumentViewData(FrontEndContext frontEndContext) {
505: Map<String, Object> viewData = new HashMap<String, Object>(
506: viewDataTemplate);
507: viewData.putAll(form.getActiveFormTemplateViewData());
508: viewData.put("CocoonFormsInstance", form.getActiveForm());
509: viewData.put("activeFormName", form.getActiveFormName());
510: viewData
511: .put("activeFormTemplate", form.getActiveFormTemplate());
512: viewData.put("locale", locale);
513: viewData.put("heartbeatInterval", String
514: .valueOf(HEARTBEAT_INTERVAL));
515: viewData.put("pageContext", frontEndContext
516: .getPageContext(getLayoutType("plain")));
517: return viewData;
518: }
519:
520: private String getBranchLangQueryString()
521: throws RepositoryException {
522: if (document.getBranchId() != siteConf.getBranchId()
523: || document.getLanguageId() != siteConf.getLanguageId()) {
524: String branch = repository.getVariantManager().getBranch(
525: document.getBranchId(), false).getName();
526: String language = repository.getVariantManager()
527: .getLanguage(document.getLanguageId(), false)
528: .getName();
529: return "?branch=" + branch + "&language=" + language;
530: } else {
531: return "";
532: }
533: }
534:
535: /**
536: * Returns a localized version of "New Document"
537: */
538: private String getNewDocumentName() throws Exception {
539: Bundle bundle = null;
540: BundleFactory bundleFactory = (BundleFactory) serviceManager
541: .lookup(BundleFactory.ROLE);
542: try {
543: bundle = bundleFactory.select("resources/i18n", "messages",
544: locale);
545: return bundle.getString("editdoc.new-document-name");
546: } finally {
547: if (bundle != null)
548: bundleFactory.release(bundle);
549: serviceManager.release(bundleFactory);
550: }
551: }
552:
553: private Document createNewDocument(String documentTypeName,
554: long branchId, long languageId) throws Exception {
555: documentType = repository.getRepositorySchema()
556: .getDocumentType(documentTypeName, false);
557: Document document = repository.createDocument(
558: getNewDocumentName(), documentType.getId(), branchId,
559: languageId);
560: DocumentCollection collection = repository
561: .getCollectionManager().getCollection(
562: siteConf.getCollectionId(), false);
563: document.addToCollection(collection);
564: return document;
565: }
566:
567: private Document createNewDocumentFromTemplate(
568: String templateDocumentId, long branchId, long languageId)
569: throws Exception {
570: Document templateDoc = repository.getDocument(
571: templateDocumentId, branchId, languageId, false);
572:
573: Document document = repository.createDocument(
574: getNewDocumentName(), templateDoc.getDocumentTypeId(),
575: branchId, languageId);
576: this .documentType = repository.getRepositorySchema()
577: .getDocumentTypeById(templateDoc.getDocumentTypeId(),
578: false);
579:
580: // copy everything from the template
581: org.outerj.daisy.repository.Field[] fields = templateDoc
582: .getFields().getArray();
583: for (Field field : fields) {
584: // the template doc might contain fields that are no longer part of the document type,
585: // therefore check with the document type if the field still belongs to it
586: if (documentType.hasFieldType(field.getTypeId()))
587: document.setField(field.getTypeId(), field.getValue());
588: }
589:
590: Part[] parts = templateDoc.getParts().getArray();
591: for (Part part : parts) {
592: if (documentType.hasPartType(part.getTypeId()))
593: document.setPart(part.getTypeId(), part.getMimeType(),
594: new PartPartDataSource(part));
595: }
596:
597: Link[] links = templateDoc.getLinks().getArray();
598: for (Link link : links)
599: document.addLink(link.getTitle(), link.getTarget());
600:
601: DocumentCollection[] collections = templateDoc.getCollections()
602: .getArray();
603: for (DocumentCollection collection : collections)
604: document.addToCollection(collection);
605:
606: for (Map.Entry<String, String> entry : templateDoc
607: .getCustomFields().entrySet()) {
608: document.setCustomField(entry.getKey(), entry.getValue());
609: }
610:
611: return document;
612: }
613:
614: private Document createNewDocumentVariant(String documentId,
615: long startBranchId, long startLanguageId, long newBranchId,
616: long newLanguageId) throws Exception {
617: Document startDocument = repository.getDocument(documentId,
618: startBranchId, startLanguageId, false);
619: Document document = repository.createVariant(documentId,
620: startBranchId, startLanguageId, -1, newBranchId,
621: newLanguageId, false);
622: documentType = repository.getRepositorySchema()
623: .getDocumentTypeById(document.getDocumentTypeId(),
624: false);
625: for (DocumentCollection collection : startDocument
626: .getCollections().getArray()) {
627: document.addToCollection(collection);
628: }
629: return document;
630: }
631:
632: /**
633: * Checks if their are any parts which are edited through an editor.
634: */
635: private boolean hasEditors() {
636: PartTypeUse[] partTypeUses = documentType.getPartTypeUses();
637: for (PartTypeUse partTypeUse : partTypeUses) {
638: if (partTypeUse.getPartType().isDaisyHtml())
639: return true;
640: }
641: return false;
642: }
643:
644: private String getPath() {
645: return currentPath + "/edit/" + getContinuationId();
646: }
647:
648: private void handleSelectionListRequest(boolean asXml,
649: Request request, AppleResponse response) throws Exception {
650: String widgetPath = RequestUtil.getStringParameter(request,
651: "widgetPath");
652: DataWidget widget = (DataWidget) form.getFieldsForm()
653: .lookupWidget(widgetPath.replace('.', '/'));
654: Datatype datatype = widget.getDatatype();
655:
656: long fieldTypeId = RequestUtil.getLongParameter(request,
657: "fieldTypeId");
658: FieldTypeUse fieldTypeUse = documentType
659: .getFieldTypeUse(fieldTypeId);
660: if (fieldTypeUse == null)
661: throw new Exception(
662: "Document type does not have a field type with ID "
663: + fieldTypeId);
664: FieldType fieldType = fieldTypeUse.getFieldType();
665:
666: SelectionList selectionList = fieldType.getSelectionList();
667: if (selectionList == null)
668: throw new Exception("Field type with id " + fieldTypeId
669: + " does not have a selection list.");
670:
671: SelectionListAdapter selectionListAdapter = new SelectionListAdapter(
672: datatype, selectionList, false, fieldType
673: .getValueType(), fieldType.isHierarchical(),
674: repository.getVariantManager(), document.getBranchId(),
675: document.getLanguageId());
676: Map<String, Object> viewData = new HashMap<String, Object>();
677: viewData.put("selectionListAdapter", selectionListAdapter);
678: viewData.put("widgetPath", widgetPath);
679: viewData.put("fieldType", fieldType);
680: viewData.put("locale", locale);
681: viewData.put("hierarchicalList", !asXml);
682: viewData.put("pageContext", FrontEndContext.get(request)
683: .getPageContext());
684:
685: GenericPipeConfig pipeConf = new GenericPipeConfig();
686: pipeConf.setTemplate("resources/xml/selectionlist.xml");
687: pipeConf.setApplyLayout(false);
688: if (asXml) {
689: pipeConf
690: .setStylesheet("resources/xslt/selectionlist_as_options.xsl");
691: pipeConf.setApplyI18n(false);
692: pipeConf.setXmlSerializer();
693: } else {
694: pipeConf.setStylesheet("daisyskin:xslt/selectionlist.xsl");
695: }
696: viewData.put("pipeConf", pipeConf);
697:
698: response.sendPage("internal/genericPipe", viewData);
699: }
700:
701: private void handleIncludePreviewsRequest(Request request,
702: AppleResponse response) throws Exception {
703: PublisherRequestDocument publisherRequestDoc = PublisherRequestDocument.Factory
704: .newInstance();
705: PublisherRequestDocument.PublisherRequest publisherRequest = publisherRequestDoc
706: .addNewPublisherRequest();
707:
708: int c = 1;
709: String documentId;
710: while ((documentId = request.getParameter("preview." + c
711: + ".documentId")) != null) {
712: String branch = request.getParameter("preview." + c
713: + ".branch");
714: String language = request.getParameter("preview." + c
715: + ".language");
716: String version = request.getParameter("preview." + c
717: + ".version");
718:
719: if (branch != null && language != null) {
720: GroupDocument.Group group = publisherRequest
721: .addNewGroup();
722: group.setId("preview" + c);
723: group.setCatchErrors(true);
724:
725: DocumentDocument.Document docReq = group
726: .addNewDocument();
727: docReq.setId(documentId);
728: docReq.setBranch(branch);
729: docReq.setLanguage(language);
730: docReq.setVersion(version);
731:
732: AnnotatedDocumentDocument1.AnnotatedDocument annotatedDocReq = docReq
733: .addNewAnnotatedDocument();
734: annotatedDocReq.setInlineParts("#daisyHtml");
735: }
736: c++;
737: }
738:
739: Publisher publisher = (Publisher) repository
740: .getExtension("Publisher");
741: SaxBuffer publisherResponse = new SaxBuffer();
742: publisher
743: .processRequest(publisherRequestDoc, publisherResponse);
744:
745: Map<String, Object> viewData = new HashMap<String, Object>();
746: viewData.put("pageXml", publisherResponse);
747:
748: GenericPipeConfig pipeConf = new GenericPipeConfig();
749: pipeConf.setStylesheet("daisyskin:xslt/includepreviews.xsl");
750: pipeConf.setApplyLayout(false);
751: pipeConf.setXmlSerializer();
752: viewData.put("pipeConf", pipeConf);
753:
754: response.sendPage("internal/genericPipe", viewData);
755: }
756:
757: /**
758: * Gets a branch id by testing for the request parameter baseName or else baseName + "Id".
759: * This last case is for backwards-compatibility.
760: */
761: private long getBranch(Request request, String baseName)
762: throws Exception {
763: String paramName = baseName;
764: String idName = baseName + "Id";
765: if (request.getParameter(idName) != null)
766: paramName = idName;
767: else if (request.getParameter(paramName) == null)
768: throw new Exception("Missing request parameter: "
769: + baseName + " or " + idName);
770:
771: return RequestUtil.getBranchId(request.getParameter(paramName),
772: -1, repository);
773: }
774:
775: /**
776: * Gets a language id by testing for the request parameter baseName or else baseName + "Id".
777: * This last case is for backwards-compatibility.
778: */
779: private long getLanguage(Request request, String baseName)
780: throws Exception {
781: String paramName = baseName;
782: String idName = baseName + "Id";
783: if (request.getParameter(idName) != null)
784: paramName = idName;
785: else if (request.getParameter(paramName) == null)
786: throw new Exception("Missing request parameter: "
787: + baseName + " or " + idName);
788:
789: return RequestUtil.getLanguageId(request
790: .getParameter(paramName), -1, repository);
791: }
792:
793: /**
794: * Copies data from an older version into the document.
795: */
796: private void revertDocument(Document document, long versionId)
797: throws Exception {
798: // Simple version reversion feature.
799: // Possible improvements:
800: // - only replace parts when they have actually changed (especially
801: // important for bigger parts). We would need some indicator on the
802: // Part objects that tells the last version for which its content changed.
803: try {
804: Version version = document.getVersion(versionId);
805:
806: // revert parts
807: // first delete old parts
808: Part[] oldParts = document.getParts().getArray();
809: for (Part oldPart : oldParts) {
810: if (version.hasPart(oldPart.getTypeId())
811: && oldPart.getDataChangedInVersion() > versionId)
812: document.deletePart(oldPart.getTypeId());
813: }
814: // then add the parts from the version
815: Part[] parts = version.getParts().getArray();
816: for (Part part : parts) {
817: if (!document.hasPart(part.getTypeId()))
818: document.setPart(part.getTypeId(), part
819: .getMimeType(),
820: new PartPartDataSource(part));
821: document.setPartFileName(part.getTypeId(), part
822: .getFileName());
823: }
824:
825: // revert fields
826: // first delete old fields
827: Field[] oldFields = document.getFields().getArray();
828: for (Field oldField : oldFields) {
829: document.deleteField(oldField.getTypeId());
830: }
831: // then add the fields from the version
832: Field[] fields = version.getFields().getArray();
833: for (Field field : fields) {
834: document.setField(field.getTypeId(), field.getValue());
835: }
836:
837: // revert document name
838: document.setName(version.getDocumentName());
839:
840: // revert links
841: document.clearLinks();
842: Link[] links = version.getLinks().getArray();
843: for (Link link : links) {
844: document.addLink(link.getTitle(), link.getTarget());
845: }
846: } catch (DocumentTypeInconsistencyException e) {
847: throw new Exception("Failed to revert to version "
848: + versionId
849: + " because of changes in the document type.", e);
850: } catch (RepositoryException e) {
851: throw new Exception(
852: "Error while reverting document content to version "
853: + versionId + ".", e);
854: }
855: }
856:
857: private String getPreSavePath() {
858: Configuration config = frontEndContext
859: .getConfigurationManager().getConfiguration(
860: "documenteditor");
861: if (config == null)
862: return null;
863:
864: return config.getChild("preSaveInteraction").getValue(null);
865: }
866:
867: /**
868: * Context passed to pre-save interaction hooks.
869: *
870: * <p>When finished, the hook needs first to call {@link #okToContinue()}
871: * and then perform a HTTP redirect to the URL returned by
872: * {@link #getReturnURL()}.
873: */
874: public class PreSaveInteractionContext {
875: public void okToContinue() {
876: preSaveInteractionFinished = true;
877: }
878:
879: public Document getDocument() {
880: return document;
881: }
882:
883: public String getEditorURL() {
884: return getPath();
885: }
886:
887: public String getCancelEditingURL() {
888: return getPath() + "/" + form.getActiveFormName()
889: + "?cancelEditing=true";
890: }
891:
892: public String getReturnURL() {
893: return getPath() + "/saveAndClose";
894: }
895: }
896: }
|