/* * Copyright (C) 2003-2009 eXo Platform SAS. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see<http://www.gnu.org/licenses/>. */ package org.exoplatform.services.wcm.publication.lifecycle.stageversion.ui; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import javax.jcr.version.Version; import javax.jcr.version.VersionIterator; import org.exoplatform.portal.webui.util.Util; import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService; import org.exoplatform.services.listener.ListenerService; import org.exoplatform.services.wcm.core.NodeLocation; import org.exoplatform.services.wcm.publication.PublicationDefaultStates; import org.exoplatform.services.wcm.publication.WCMPublicationService; import org.exoplatform.services.wcm.publication.lifecycle.stageversion.StageAndVersionPublicationConstant; import org.exoplatform.services.wcm.publication.lifecycle.stageversion.config.VersionData; import org.exoplatform.services.wcm.publication.lifecycle.stageversion.config.VersionLog; import org.exoplatform.services.wcm.utils.WCMCoreUtils; import org.exoplatform.web.application.ApplicationMessage; import org.exoplatform.webui.config.annotation.ComponentConfig; import org.exoplatform.webui.config.annotation.EventConfig; import org.exoplatform.webui.core.UIApplication; import org.exoplatform.webui.core.UIPopupContainer; import org.exoplatform.webui.core.lifecycle.UIFormLifecycle; import org.exoplatform.webui.event.Event; import org.exoplatform.webui.event.EventListener; import org.exoplatform.webui.form.UIForm; /** * Created by The eXo Platform SAS * Author : eXoPlatform * chuong_phan@exoplatform.com * Mar 2, 2009 */ @ComponentConfig( lifecycle = UIFormLifecycle.class, template = "classpath:groovy/wcm/webui/publication/lifecycle/stageversion/ui/UIPublicationPanel.gtmpl", events = { @EventConfig(listeners=UIPublicationPanel.DraftActionListener.class), @EventConfig(listeners=UIPublicationPanel.LiveActionListener.class), @EventConfig(name= "obsolete", listeners= UIPublicationPanel.ObsoleteActionListener.class), @EventConfig(listeners=UIPublicationPanel.ChangeVersionActionListener.class), @EventConfig(listeners=UIPublicationPanel.PreviewVersionActionListener.class), @EventConfig(listeners=UIPublicationPanel.RestoreVersionActionListener.class), @EventConfig(listeners=UIPublicationPanel.SeeAllVersionActionListener.class), @EventConfig(listeners=UIPublicationPanel.CloseActionListener.class) } ) public class UIPublicationPanel extends UIForm { /** The current node. */ private NodeLocation currentNodeLocation; /** The current revision. */ private NodeLocation currentRevisionLocation; /** The revisions data map. */ private Map<String,VersionData> revisionsDataMap = new HashMap<String,VersionData>(); /** The viewed revisions. */ private List<NodeLocation> viewedRevisions = new ArrayList<NodeLocation>(3); private WCMPublicationService wcmPublicationService; private String sitename; private String remoteuser; /** * Instantiates a new uI publication panel. * * @throws Exception the exception */ public UIPublicationPanel() throws Exception { wcmPublicationService = WCMCoreUtils.getService(WCMPublicationService.class); sitename = Util.getPortalRequestContext().getPortalOwner(); remoteuser = Util.getPortalRequestContext().getRemoteUser(); } /** * Inits the. * * @param node the node * * @throws Exception the exception */ public void init(Node node) throws Exception { currentNodeLocation = NodeLocation.getNodeLocationByNode(node); currentRevisionLocation = NodeLocation.getNodeLocationByNode(node); this.viewedRevisions = NodeLocation.getLocationsByNodeList(getLatestRevisions(3,node)); this.revisionsDataMap = getRevisionData(node); //In some cases as copy a a node, we will lost all version of the node //So we will clean all publication data cleanPublicationData(node); } /** * Clean publication data. * * @param node the node * * @throws Exception the exception */ private void cleanPublicationData(Node node) throws Exception { if(viewedRevisions.size() == 1 && revisionsDataMap.size()>1) { node.setProperty(StageAndVersionPublicationConstant.REVISION_DATA_PROP,new Value[] {}); node.setProperty(StageAndVersionPublicationConstant.HISTORY,new Value[] {}); node.setProperty(StageAndVersionPublicationConstant.LIVE_REVISION_PROP,(javax.jcr.Value)null); node.save(); this.revisionsDataMap = getRevisionData(node); } } /** * Gets the all revisions. * * @param node the node * * @return the all revisions * * @throws Exception the exception */ public List<Node> getAllRevisions(Node node) throws Exception { List<Node> allversions = new ArrayList<Node>(); VersionIterator iterator = node.getVersionHistory().getAllVersions(); for(;iterator.hasNext();) { Version version = iterator.nextVersion(); if (version.getName().equals("jcr:rootVersion")) continue; allversions.add(version); } Collections.sort(allversions, new NodeNameComparator()); //current node is a revision allversions.add(node); Collections.reverse(allversions); return allversions; } /** * Gets the current node. * * @return the current node */ public Node getCurrentNode() { return NodeLocation.getNodeByLocation(currentNodeLocation); } /** * Gets the current revision. * * @return the current revision */ public Node getCurrentRevision() { return NodeLocation.getNodeByLocation(currentRevisionLocation); } /** * Gets the revision author. * * @param revision the revision * * @return the revision author * * @throws Exception the exception */ public String getRevisionAuthor(Node revision) throws Exception{ VersionData revisionData = revisionsDataMap.get(revision.getUUID()); Node currentNode = getCurrentNode(); if(revisionData!= null) return revisionData.getAuthor(); if(revision.getUUID().equalsIgnoreCase(currentNode.getUUID())) { return currentNode.getProperty("exo:owner").getString(); } return null; } /** * Gets the revision by uuid. * * @param revisionUUID the revision uuid * * @return the revision by uuid * * @throws Exception the exception */ public Node getRevisionByUUID(String revisionUUID) throws Exception { Session session = getCurrentNode().getSession(); return session.getNodeByUUID(revisionUUID); } /** * Gets the revision created date. * * @param revision the revision * * @return the revision created date * * @throws Exception the exception */ public String getRevisionCreatedDate(Node revision) throws Exception { UIPublicationContainer container = getAncestorOfType(UIPublicationContainer.class); DateFormat dateFormater = container.getDateTimeFormater(); Calendar calendar = null; if(revision instanceof Version) { calendar= ((Version)revision).getCreated(); }else { if(revision.hasProperty("exo:dateCreated")){ calendar = revision.getProperty("exo:dateCreated").getDate(); }else{ calendar = revision.getProperty("jcr:created").getDate(); } } return dateFormater.format(calendar.getTime()); } /** * Gets the revisions. * * @return the revisions */ public List<Node> getRevisions() { return NodeLocation.getNodeListByLocationList(viewedRevisions); } /** * Gets the revision state. * * @param revision the revision * * @return the revision state * * @throws Exception the exception */ public String getRevisionState(Node revision) throws Exception{ VersionData revisionData = revisionsDataMap.get(revision.getUUID()); Node currentNode = getCurrentNode(); if(revisionData!= null) return revisionData.getState(); if(revision.getUUID().equalsIgnoreCase(currentNode.getUUID())) { return currentNode.getProperty(StageAndVersionPublicationConstant.CURRENT_STATE).getString(); } return null; } /** * Sets the current revision. * * @param revision the new current revision */ public void setCurrentRevision(Node revision) { currentRevisionLocation = NodeLocation.getNodeLocationByNode(revision); } /** * Sets the revisions. * * @param revisions the new revisions */ public void setRevisions(List<Node> revisions) { this.viewedRevisions = NodeLocation.getLocationsByNodeList(revisions); } /** * Update panel. * * @throws Exception the exception */ public void updatePanel() throws Exception{ UIPublicationContainer publicationContainer = getAncestorOfType(UIPublicationContainer.class); UIPublicationHistory publicationHistory = publicationContainer.getChild(UIPublicationHistory.class); publicationHistory.updateGrid(); Node currentNode = getCurrentNode(); this.revisionsDataMap = getRevisionData(currentNode); this.viewedRevisions = NodeLocation.getLocationsByNodeList(getLatestRevisions(3,currentNode)); } /** * Gets the latest revisions. * * @param limit the limit * @param node the node * * @return the latest revisions * * @throws Exception the exception */ private List<Node> getLatestRevisions(int limit, Node node) throws Exception { List<Node> allversions = getAllRevisions(node); List<Node> latestVersions = new ArrayList<Node>(); if(allversions.size() > limit) { latestVersions = allversions.subList(0, limit); } else { latestVersions = allversions; } return latestVersions; } /** * Gets the revision data. * * @param node the node * * @return the revision data * * @throws Exception the exception */ private Map<String, VersionData> getRevisionData(Node node) throws Exception{ Map<String,VersionData> map = new HashMap<String,VersionData>(); try { for(Value v: node.getProperty(StageAndVersionPublicationConstant.REVISION_DATA_PROP).getValues()) { VersionData versionData = VersionData.toVersionData(v.getString()); map.put(versionData.getUUID(),versionData);; } } catch (Exception e) { return map; } return map; } /** * The listener interface for receiving changeVersionAction events. * The class that is interested in processing a changeVersionAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addChangeVersionActionListener</code> method. When * the changeVersionAction event occurs, that object's appropriate * method is invoked. */ public static class ChangeVersionActionListener extends EventListener<UIPublicationPanel> { /* (non-Javadoc) * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event) */ public void execute(Event<UIPublicationPanel> event) throws Exception { UIPublicationPanel publicationPanel = event.getSource(); String versionUUID = event.getRequestContext().getRequestParameter(OBJECTID); Node revision = publicationPanel.getRevisionByUUID(versionUUID); publicationPanel.setCurrentRevision(revision); UIPublicationContainer publicationContainer = publicationPanel.getAncestorOfType(UIPublicationContainer.class); publicationContainer.setActiveTab(publicationPanel, event.getRequestContext()); } } /** * The listener interface for receiving closeAction events. * The class that is interested in processing a closeAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addCloseActionListener</code> method. When * the closeAction event occurs, that object's appropriate * method is invoked. */ public static class CloseActionListener extends EventListener<UIPublicationPanel> { /* (non-Javadoc) * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event) */ public void execute(Event<UIPublicationPanel> event) throws Exception { UIPublicationPanel publicationPanel = event.getSource(); UIPopupContainer uiPopupContainer = (UIPopupContainer) publicationPanel.getAncestorOfType(UIPopupContainer.class); uiPopupContainer.deActivate(); event.getRequestContext().addUIComponentToUpdateByAjax(uiPopupContainer); } } /** * The listener interface for receiving draftAction events. * The class that is interested in processing a draftAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addDraftActionListener</code> method. When * the draftAction event occurs, that object's appropriate * method is invoked. */ public static class DraftActionListener extends EventListener<UIPublicationPanel> { /* (non-Javadoc) * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event) */ public void execute(Event<UIPublicationPanel> event) throws Exception { UIPublicationPanel publicationPanel = event.getSource(); Node currentNode = publicationPanel.getCurrentNode(); publicationPanel.wcmPublicationService.updateLifecyleOnChangeContent(currentNode, publicationPanel.sitename, publicationPanel.remoteuser, PublicationDefaultStates.DRAFT); try { publicationPanel.updatePanel(); } catch (Exception e) { UIApplication uiApp = publicationPanel.getAncestorOfType(UIApplication.class); // JCRExceptionManager.process(uiApp,e); } UIPublicationContainer publicationContainer = publicationPanel.getAncestorOfType(UIPublicationContainer.class); publicationContainer.setActiveTab(publicationPanel, event.getRequestContext()); } } /** * The listener interface for receiving liveAction events. * The class that is interested in processing a liveAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addLiveActionListener</code> method. When * the liveAction event occurs, that object's appropriate * method is invoked. */ public static class LiveActionListener extends EventListener<UIPublicationPanel> { /* (non-Javadoc) * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event) */ public void execute(Event<UIPublicationPanel> event) throws Exception { UIPublicationPanel publicationPanel = event.getSource(); Node currentNode = publicationPanel.getCurrentNode(); publicationPanel.wcmPublicationService.updateLifecyleOnChangeContent(currentNode, publicationPanel.sitename, publicationPanel.remoteuser, PublicationDefaultStates.PUBLISHED); try { publicationPanel.updatePanel(); } catch (Exception e) { UIApplication uiApp = publicationPanel.getAncestorOfType(UIApplication.class); // JCRExceptionManager.process(uiApp,e); } UIPublicationContainer publicationContainer = publicationPanel.getAncestorOfType(UIPublicationContainer.class); publicationContainer.setActiveTab(publicationPanel, event.getRequestContext()); } } /** * The listener interface for receiving obsoleteAction events. * The class that is interested in processing a obsoleteAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addObsoleteActionListener</code> method. When * the obsoleteAction event occurs, that object's appropriate * method is invoked. */ public static class ObsoleteActionListener extends EventListener<UIPublicationPanel> { /* (non-Javadoc) * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event) */ public void execute(Event<UIPublicationPanel> event) throws Exception { UIPublicationPanel publicationPanel = event.getSource(); Node currentNode = publicationPanel.getCurrentNode(); publicationPanel.wcmPublicationService.updateLifecyleOnChangeContent(currentNode, publicationPanel.sitename, publicationPanel.remoteuser, PublicationDefaultStates.OBSOLETE); try { publicationPanel.updatePanel(); } catch (Exception e) { UIApplication uiApp = publicationPanel.getAncestorOfType(UIApplication.class); // JCRExceptionManager.process(uiApp,e); } UIPublicationContainer publicationContainer = publicationPanel.getAncestorOfType(UIPublicationContainer.class); publicationContainer.setActiveTab(publicationPanel, event.getRequestContext()); } } /** * The listener interface for receiving previewVersionAction events. * The class that is interested in processing a previewVersionAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addPreviewVersionActionListener</code> method. When * the previewVersionAction event occurs, that object's appropriate * method is invoked. */ public static class PreviewVersionActionListener extends EventListener<UIPublicationPanel> { /* (non-Javadoc) * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event) */ public void execute(Event<UIPublicationPanel> event) throws Exception { UIPublicationPanel publicationPanel = event.getSource(); UIPublicationContainer publicationContainer = publicationPanel.getAncestorOfType(UIPublicationContainer.class); UIVersionViewer versionViewer = publicationContainer.createUIComponent(UIVersionViewer.class, null, "UIVersionViewer"); String versionUUID = event.getRequestContext().getRequestParameter(OBJECTID); Node revision = publicationPanel.getRevisionByUUID(versionUUID); Node frozenNode = revision; if(revision instanceof Version) { frozenNode = revision.getNode("jcr:frozenNode") ; } versionViewer.setOriginalNode(publicationPanel.getCurrentNode()); versionViewer.setNode(frozenNode); if(versionViewer.getTemplate() == null || versionViewer.getTemplate().trim().length() == 0) { UIApplication uiApp = publicationPanel.getAncestorOfType(UIApplication.class) ; uiApp.addMessage(new ApplicationMessage("UIPublicationPanel.msg.have-no-view-template", null)) ; return ; } if (publicationContainer.getChildById("UIVersionViewer") == null) publicationContainer.addChild(versionViewer); else publicationContainer.replaceChild("UIVersionViewer", versionViewer); publicationContainer.setActiveTab(versionViewer, event.getRequestContext()); } } /** * The listener interface for receiving restoreVersionAction events. * The class that is interested in processing a restoreVersionAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addRestoreVersionActionListener</code> method. When * the restoreVersionAction event occurs, that object's appropriate * method is invoked. */ public static class RestoreVersionActionListener extends EventListener<UIPublicationPanel> { /* (non-Javadoc) * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event) */ public void execute(Event<UIPublicationPanel> event) throws Exception { UIPublicationPanel publicationPanel = event.getSource(); Node currentNode = publicationPanel.getCurrentNode(); String versionUUID = event.getRequestContext().getRequestParameter(OBJECTID); Version version = (Version)publicationPanel.getRevisionByUUID(versionUUID); String userId = ""; try { userId = Util.getPortalRequestContext().getRemoteUser(); } catch (Exception ex) { userId = currentNode.getSession().getUserID(); } //restore the version try { currentNode.restore(version,true); ListenerService listenerService = WCMCoreUtils.getService(ListenerService.class); ActivityCommonService activityService = WCMCoreUtils.getService(ActivityCommonService.class); if (activityService.isAcceptedNode(currentNode)) { listenerService.broadcast(ActivityCommonService.NODE_REVISION_CHANGED, currentNode, version.getName()); } if(!currentNode.isCheckedOut()) currentNode.checkout(); Value[] values = currentNode.getProperty("publication:revisionData").getValues(); String currentState = ""; for(Value value : values) { String revisionData = value.getString(); if(revisionData.indexOf(PublicationDefaultStates.PUBLISHED) > 0) { currentState = PublicationDefaultStates.PUBLISHED; break; } else { currentState = PublicationDefaultStates.OBSOLETE; } } //set current state currentNode.setProperty("publication:currentState", PublicationDefaultStates.DRAFT); //set revision data Map<String, VersionData> revisionsDataMap = publicationPanel.getRevisionData(currentNode); revisionsDataMap.get(currentNode.getUUID()).setState(PublicationDefaultStates.DRAFT); List<Value> valueList = new ArrayList<Value>(); ValueFactory factory = currentNode.getSession().getValueFactory(); for(VersionData versionData: revisionsDataMap.values()) { valueList.add(factory.createValue(versionData.toStringValue())); } currentNode.setProperty(StageAndVersionPublicationConstant.REVISION_DATA_PROP,valueList.toArray(new Value[]{})); //log the history VersionLog versionLog = new VersionLog(currentNode.getName(), currentNode.getProperty(StageAndVersionPublicationConstant.CURRENT_STATE).getString(), userId, GregorianCalendar.getInstance(), StageAndVersionPublicationConstant.PUBLICATION_LOG_RESTORE_VERSION + " from " + version.getName()); values = currentNode.getProperty(StageAndVersionPublicationConstant.HISTORY).getValues(); ValueFactory valueFactory = currentNode.getSession().getValueFactory(); List<Value> list = new ArrayList<Value>(Arrays.asList(values)); list.add(valueFactory.createValue(versionLog.toString())); currentNode.setProperty(StageAndVersionPublicationConstant.HISTORY, list.toArray(new Value[] {})); //save data and update ui currentNode.getSession().save(); publicationPanel.setCurrentRevision(currentNode); publicationPanel.updatePanel(); } catch (Exception e) { UIApplication uiApp = publicationPanel.getAncestorOfType(UIApplication.class); // JCRExceptionManager.process(uiApp,e); } UIApplication uiApp = publicationPanel.getAncestorOfType(UIApplication.class) ; uiApp.addMessage(new ApplicationMessage("UIPublicationPanel.msg.restore-complete", null)) ; event.getRequestContext().addUIComponentToUpdateByAjax(uiApp) ; UIPublicationContainer publicationContainer = publicationPanel.getAncestorOfType(UIPublicationContainer.class); publicationContainer.setActiveTab(publicationPanel, event.getRequestContext()); } } /** * The listener interface for receiving seeAllVersionAction events. * The class that is interested in processing a seeAllVersionAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addSeeAllVersionActionListener</code> method. When * the seeAllVersionAction event occurs, that object's appropriate * method is invoked. */ public static class SeeAllVersionActionListener extends EventListener<UIPublicationPanel> { /* (non-Javadoc) * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event) */ public void execute(Event<UIPublicationPanel> event) throws Exception { UIPublicationPanel publicationPanel = event.getSource(); publicationPanel.setRevisions(publicationPanel.getAllRevisions(publicationPanel.getCurrentNode())); UIPublicationContainer publicationContainer = publicationPanel.getAncestorOfType(UIPublicationContainer.class); publicationContainer.setActiveTab(publicationPanel, event.getRequestContext()); } } private class NodeNameComparator implements Comparator<Node> { @Override public int compare(Node node0, Node node1) { try { String name0 = node0.getName(); String name1 = node1.getName(); try { int name0Int = Integer.parseInt(name0); int name1Int = Integer.parseInt(name1); return name0Int - name1Int; } catch (NumberFormatException e) { return name0.compareTo(name1); } } catch (RepositoryException e) { return 0; } } } }