| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| package org.apache.lenya.cms.workflow.usecases; |
| |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.GregorianCalendar; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.avalon.framework.service.ServiceSelector; |
| import org.apache.cocoon.components.ContextHelper; |
| import org.apache.cocoon.environment.Request; |
| import org.apache.lenya.ac.AccessControlException; |
| import org.apache.lenya.ac.Identifiable; |
| import org.apache.lenya.ac.User; |
| import org.apache.lenya.cms.ac.PolicyUtil; |
| import org.apache.lenya.cms.linking.LinkManager; |
| import org.apache.lenya.cms.linking.LinkResolver; |
| import org.apache.lenya.cms.linking.LinkTarget; |
| import org.apache.lenya.cms.metadata.dublincore.DublinCoreHelper; |
| import org.apache.lenya.cms.observation.RepositoryEvent; |
| import org.apache.lenya.cms.observation.RepositoryEventFactory; |
| import org.apache.lenya.cms.publication.Document; |
| import org.apache.lenya.cms.publication.DocumentException; |
| import org.apache.lenya.cms.publication.DocumentFactory; |
| import org.apache.lenya.cms.publication.DocumentLocator; |
| import org.apache.lenya.cms.publication.DocumentManager; |
| import org.apache.lenya.cms.publication.Proxy; |
| import org.apache.lenya.cms.publication.Publication; |
| import org.apache.lenya.cms.publication.PublicationException; |
| import org.apache.lenya.cms.site.Link; |
| import org.apache.lenya.cms.site.SiteException; |
| import org.apache.lenya.cms.site.SiteManager; |
| import org.apache.lenya.cms.site.SiteNode; |
| import org.apache.lenya.cms.site.SiteStructure; |
| import org.apache.lenya.cms.usecase.UsecaseException; |
| import org.apache.lenya.cms.usecase.scheduling.UsecaseScheduler; |
| import org.apache.lenya.cms.workflow.WorkflowUtil; |
| import org.apache.lenya.cms.workflow.usecases.InvokeWorkflow; |
| import org.apache.lenya.notification.Message; |
| import org.apache.lenya.notification.NotificationEventDescriptor; |
| import org.apache.lenya.notification.NotificationException; |
| import org.apache.lenya.workflow.Version; |
| import org.apache.lenya.workflow.Workflowable; |
| |
| /** |
| * Publish usecase handler. |
| * |
| * @version $Id$ |
| */ |
| public class Publish extends InvokeWorkflow { |
| |
| protected static final String MESSAGE_SUBJECT = "notification-message"; |
| protected static final String MESSAGE_DOCUMENT_PUBLISHED = "document-published"; |
| protected static final String SCHEDULE = "schedule"; |
| protected static final String SCHEDULE_TIME = "schedule.time"; |
| protected static final String SEND_NOTIFICATION = "sendNotification"; |
| protected static final String UNPUBLISHED_LINKS = "unpublishedLinks"; |
| |
| /** |
| * @see org.apache.lenya.cms.usecase.AbstractUsecase#initParameters() |
| */ |
| protected void initParameters() { |
| super.initParameters(); |
| |
| if (hasErrors() || getSourceDocument() == null) { |
| return; |
| } |
| |
| Date now = new GregorianCalendar().getTime(); |
| DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| setParameter(SCHEDULE_TIME, format.format(now)); |
| |
| setParameter(SEND_NOTIFICATION, Boolean.TRUE); |
| |
| setParameter(UNPUBLISHED_LINKS, new LinkList(this.manager, getSourceDocument())); |
| |
| } |
| |
| protected boolean hasBrokenLinks() { |
| LinkManager linkMgr = null; |
| LinkResolver resolver = null; |
| try { |
| linkMgr = (LinkManager) this.manager.lookup(LinkManager.ROLE); |
| resolver = (LinkResolver) this.manager.lookup(LinkResolver.ROLE); |
| org.apache.lenya.cms.linking.Link[] links = linkMgr.getLinksFrom(getSourceDocument()); |
| for (int i = 0; i < links.length; i++) { |
| LinkTarget target = resolver.resolve(getSourceDocument(), links[i].getUri()); |
| if (!target.exists()) { |
| return true; |
| } |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } finally { |
| if (linkMgr != null) { |
| this.manager.release(linkMgr); |
| } |
| if (resolver != null) { |
| this.manager.release(resolver); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @see org.apache.lenya.cms.usecase.AbstractUsecase#getNodesToLock() |
| */ |
| protected org.apache.lenya.cms.repository.Node[] getNodesToLock() throws UsecaseException { |
| try { |
| List nodes = new ArrayList(); |
| |
| Document doc = getSourceDocument(); |
| if(doc != null) { |
| nodes.add(doc.getRepositoryNode()); |
| |
| // lock the authoring site to avoid having live nodes for which no corresponding |
| // authoring node exists |
| nodes.add(doc.area().getSite().getRepositoryNode()); |
| |
| // lock the live site to avoid overriding changes made by others |
| SiteStructure liveSite = doc.getPublication().getArea(Publication.LIVE_AREA).getSite(); |
| nodes.add(liveSite.getRepositoryNode()); |
| } |
| |
| return (org.apache.lenya.cms.repository.Node[]) nodes |
| .toArray(new org.apache.lenya.cms.repository.Node[nodes.size()]); |
| |
| } catch (Exception e) { |
| throw new UsecaseException(e); |
| } |
| } |
| |
| /** |
| * Checks if the workflow event is supported and the parent of the document |
| * exists in the live area. |
| * |
| * @see org.apache.lenya.cms.usecase.AbstractUsecase#doCheckPreconditions() |
| */ |
| protected void doCheckPreconditions() throws Exception { |
| super.doCheckPreconditions(); |
| if (!hasErrors()) { |
| |
| Document document = getSourceDocument(); |
| |
| if (!document.getArea().equals(Publication.AUTHORING_AREA)) { |
| addErrorMessage("This usecase can only be invoked from the authoring area."); |
| return; |
| } |
| |
| Publication publication = document.getPublication(); |
| DocumentFactory map = document.getFactory(); |
| SiteStructure liveSite = publication.getArea(Publication.LIVE_AREA).getSite(); |
| |
| List missingDocuments = new ArrayList(); |
| |
| ServiceSelector selector = null; |
| SiteManager siteManager = null; |
| try { |
| selector = (ServiceSelector) this.manager.lookup(SiteManager.ROLE + "Selector"); |
| siteManager = (SiteManager) selector.select(publication.getSiteManagerHint()); |
| |
| if (!liveSite.contains(document.getPath())) { |
| DocumentLocator liveLoc = document.getLocator().getAreaVersion( |
| Publication.LIVE_AREA); |
| DocumentLocator[] requiredNodes = siteManager |
| .getRequiredResources(map, liveLoc); |
| for (int i = 0; i < requiredNodes.length; i++) { |
| String path = requiredNodes[i].getPath(); |
| if (!liveSite.contains(path)) { |
| Link link = getExistingLink(path, document); |
| if (link != null) { |
| missingDocuments.add(link.getDocument()); |
| } |
| } |
| |
| } |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } finally { |
| if (selector != null) { |
| if (siteManager != null) { |
| selector.release(siteManager); |
| } |
| this.manager.release(selector); |
| } |
| } |
| |
| if (!missingDocuments.isEmpty()) { |
| addErrorMessage("publish-missing-documents"); |
| for (Iterator i = missingDocuments.iterator(); i.hasNext();) { |
| Document doc = (Document) i.next(); |
| /* |
| * This doesn't work yet, see |
| * https://issues.apache.org/jira/browse/COCOON-2057 |
| * String[] params = { doc.getCanonicalWebappURL(), |
| * doc.getPath() + " (" + doc.getLanguage() + ")" }; |
| */ |
| String[] params = { doc.getPath() + ":" + doc.getLanguage(), |
| DublinCoreHelper.getTitle(doc, true) }; |
| addErrorMessage("missing-document", params); |
| } |
| } |
| |
| if (hasBrokenLinks()) { |
| addInfoMessage("publish-broken-links"); |
| } |
| } |
| } |
| |
| /** |
| * Returns a link of a certain node, preferably in the document's language, |
| * or <code>null</code> if the node has no links. |
| * @param path The path of the node. |
| * @param document The document. |
| * @return A link or <code>null</code>. |
| * @throws SiteException if an error occurs. |
| */ |
| protected Link getExistingLink(String path, Document document) throws SiteException { |
| SiteNode node = document.area().getSite().getNode(path); |
| Link link = null; |
| String uuid = node.getUuid(); |
| if (uuid != null && uuid.equals(document.getUUID())) { |
| if (node.hasLink(document.getLanguage())) { |
| link = node.getLink(document.getLanguage()); |
| } else if (node.getLanguages().length > 0) { |
| link = node.getLink(node.getLanguages()[0]); |
| } |
| } |
| return link; |
| } |
| |
| protected void doCheckExecutionConditions() throws Exception { |
| super.doCheckExecutionConditions(); |
| boolean schedule = Boolean.valueOf(getBooleanCheckboxParameter(SCHEDULE)).booleanValue(); |
| if (schedule) { |
| String dateString = getParameterAsString(SCHEDULE_TIME); |
| DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| try { |
| format.parse(dateString); |
| } catch (ParseException e) { |
| addErrorMessage("scheduler-date-format-invalid"); |
| } |
| } |
| } |
| |
| /** |
| * @see org.apache.lenya.cms.usecase.AbstractUsecase#doExecute() |
| */ |
| protected void doExecute() throws Exception { |
| |
| boolean schedule = Boolean.valueOf(getBooleanCheckboxParameter(SCHEDULE)).booleanValue(); |
| if (schedule) { |
| deleteParameter(SCHEDULE); |
| String dateString = getParameterAsString(SCHEDULE_TIME); |
| DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| UsecaseScheduler scheduler = null; |
| try { |
| Date date = format.parse(dateString); |
| scheduler = (UsecaseScheduler) this.manager.lookup(UsecaseScheduler.ROLE); |
| scheduler.schedule(this, date); |
| } catch (ParseException e) { |
| addErrorMessage("scheduler-date-format-invalid"); |
| } finally { |
| if (scheduler != null) { |
| this.manager.release(scheduler); |
| } |
| } |
| } else { |
| super.doExecute(); |
| publish(getSourceDocument()); |
| } |
| } |
| |
| protected void publish(Document authoringDocument) throws DocumentException, SiteException, |
| PublicationException { |
| |
| createAncestorNodes(authoringDocument); |
| |
| DocumentManager documentManager = null; |
| |
| try { |
| documentManager = (DocumentManager) this.manager.lookup(DocumentManager.ROLE); |
| documentManager.copyToArea(authoringDocument, Publication.LIVE_AREA); |
| |
| boolean notify = Boolean.valueOf(getBooleanCheckboxParameter(SEND_NOTIFICATION)) |
| .booleanValue(); |
| if (notify) { |
| sendNotification(authoringDocument); |
| } |
| |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } finally { |
| if (documentManager != null) { |
| this.manager.release(documentManager); |
| } |
| } |
| } |
| |
| protected void sendNotification(Document authoringDocument) throws NotificationException, |
| DocumentException, AccessControlException { |
| User sender = getSession().getIdentity().getUser(); |
| |
| Workflowable workflowable = WorkflowUtil.getWorkflowable(this.manager, getSession(), |
| getLogger(), authoringDocument); |
| Version versions[] = workflowable.getVersions(); |
| Version version = versions[versions.length - 2]; |
| |
| // we assume that the document has been submitted, otherwise we do |
| // nothing |
| if (version.getEvent().equals("submit")) { |
| |
| String userId = version.getUserId(); |
| User user = PolicyUtil.getUser(this.manager, authoringDocument.getCanonicalWebappURL(), |
| userId, getLogger()); |
| |
| Identifiable[] recipients = { user }; |
| |
| Document liveVersion = authoringDocument.getAreaVersion(Publication.LIVE_AREA); |
| String url; |
| |
| Proxy proxy = liveVersion.getPublication().getProxy(liveVersion, false); |
| if (proxy != null) { |
| url = proxy.getURL(liveVersion); |
| } else { |
| Request request = ContextHelper.getRequest(this.context); |
| final String serverUrl = "http://" + request.getServerName() + ":" |
| + request.getServerPort(); |
| final String webappUrl = liveVersion.getCanonicalWebappURL(); |
| url = serverUrl + request.getContextPath() + webappUrl; |
| } |
| String[] params = { url }; |
| Message message = new Message(MESSAGE_SUBJECT, new String[0], |
| MESSAGE_DOCUMENT_PUBLISHED, params, sender, recipients); |
| |
| NotificationEventDescriptor descriptor = new NotificationEventDescriptor(message); |
| RepositoryEvent event = RepositoryEventFactory.createEvent(this.manager, getSession(), |
| getLogger(), descriptor); |
| getSession().enqueueEvent(event); |
| } |
| } |
| |
| protected void createAncestorNodes(Document document) throws PublicationException, |
| DocumentException, SiteException { |
| SiteStructure liveSite = document.getPublication().getArea(Publication.LIVE_AREA).getSite(); |
| String[] steps = document.getPath().substring(1).split("/"); |
| int s = 0; |
| String path = ""; |
| while (s < steps.length) { |
| if (!liveSite.contains(path)) { |
| liveSite.add(path); |
| } |
| path += "/" + steps[s]; |
| s++; |
| } |
| } |
| |
| /** |
| * A list of links originating from a document. Allows lazy loading rom the usecase view. |
| */ |
| public static class LinkList { |
| |
| private Document document; |
| private Document[] documents; |
| private ServiceManager manager; |
| |
| /** |
| * @param manager The manager. |
| * @param doc The document to resolve the links from. |
| */ |
| public LinkList(ServiceManager manager, Document doc) { |
| this.manager = manager; |
| this.document = doc; |
| } |
| |
| /** |
| * @return The link documents. |
| */ |
| public Document[] getDocuments() { |
| if (this.documents == null) { |
| this.documents = getUnpublishedLinks(); |
| } |
| return this.documents; |
| } |
| |
| protected Document[] getUnpublishedLinks() { |
| Set docs = new HashSet(); |
| LinkManager linkMgr = null; |
| LinkResolver resolver = null; |
| try { |
| linkMgr = (LinkManager) this.manager.lookup(LinkManager.ROLE); |
| resolver = (LinkResolver) this.manager.lookup(LinkResolver.ROLE); |
| org.apache.lenya.cms.linking.Link[] links = linkMgr.getLinksFrom(this.document); |
| for (int i = 0; i < links.length; i++) { |
| LinkTarget target = resolver.resolve(this.document, links[i].getUri()); |
| if (target.exists()) { |
| Document doc = target.getDocument(); |
| if (!doc.existsAreaVersion(Publication.LIVE_AREA)) { |
| docs.add(doc); |
| } |
| } |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } finally { |
| if (linkMgr != null) { |
| this.manager.release(linkMgr); |
| } |
| if (resolver != null) { |
| this.manager.release(resolver); |
| } |
| } |
| return (Document[]) docs.toArray(new Document[docs.size()]); |
| } |
| |
| } |
| |
| } |