/* * Copyright (C) 2003-2017 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.management.uiextension; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.exoplatform.commons.utils.LazyPageList; import org.exoplatform.commons.utils.ListAccessImpl; import org.exoplatform.container.ExoContainerContext; import org.exoplatform.container.PortalContainer; import org.exoplatform.container.component.RequestLifeCycle; import org.exoplatform.management.service.api.Resource; import org.exoplatform.management.service.api.ResourceHandler; import org.exoplatform.management.service.api.StagingService; import org.exoplatform.management.service.api.SynchronizationService; import org.exoplatform.management.service.api.TargetServer; import org.exoplatform.management.service.handler.ResourceHandlerLocator; import org.exoplatform.management.uiextension.comparison.NodeComparison; import org.exoplatform.management.uiextension.comparison.NodeComparisonState; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.web.application.ApplicationMessage; import org.exoplatform.webui.application.WebuiRequestContext; import org.exoplatform.webui.config.annotation.ComponentConfig; import org.exoplatform.webui.config.annotation.ComponentConfigs; import org.exoplatform.webui.config.annotation.EventConfig; import org.exoplatform.webui.core.UIApplication; import org.exoplatform.webui.core.UIGrid; import org.exoplatform.webui.core.UIPopupComponent; import org.exoplatform.webui.core.UIPopupContainer; import org.exoplatform.webui.core.lifecycle.UIFormLifecycle; import org.exoplatform.webui.core.model.SelectItemOption; import org.exoplatform.webui.event.Event; import org.exoplatform.webui.event.EventListener; import org.exoplatform.webui.form.UIForm; import org.exoplatform.webui.form.UIFormInputInfo; import org.exoplatform.webui.form.UIFormSelectBox; import java.net.ConnectException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; /** * The Class PushContentPopupComponent. * * @author <a href="mailto:bkhanfir@exoplatform.com">Boubaker Khanfir</a> * @version $Revision$ */ @ComponentConfigs({ @ComponentConfig(type = UIGrid.class, id = "selectedNodesGrid", template = "classpath:groovy/webui/component/explorer/popup/staging/UISelectedNodesGrid.gtmpl"), @ComponentConfig(lifecycle = UIFormLifecycle.class, template = "classpath:groovy/webui/component/explorer/popup/staging/PushContent.gtmpl", events = { @EventConfig(listeners = PushContentPopupComponent.CloseActionListener.class), @EventConfig(listeners = PushContentPopupComponent.PushActionListener.class), @EventConfig(listeners = PushContentPopupComponent.SelectActionListener.class) }) }) public class PushContentPopupComponent extends UIForm implements UIPopupComponent { /** The Constant LOG. */ private static final Log LOG = ExoLogger.getLogger(PushContentPopupComponent.class.getName()); /** The contents handler. */ protected static ResourceHandler CONTENTS_HANDLER = ResourceHandlerLocator.getResourceHandler(StagingService.CONTENT_SITES_PATH);; /** The synchronization started. */ protected boolean synchronizationStarted = false; /** The synchronization finished. */ protected boolean synchronizationFinished = true; /** The synchronization error. */ protected Throwable synchronizationError = null; /** The Constant TARGET_SERVER_NAME_FIELD_NAME. */ public static final String TARGET_SERVER_NAME_FIELD_NAME = "targetServer"; // public static final String PUBLISH_FIELD_NAME = "publishOnTarget"; /** The Constant INFO_FIELD_NAME. */ public static final String INFO_FIELD_NAME = "info"; /** The Constant CLEANUP_PUBLICATION. */ private static final String CLEANUP_PUBLICATION = "exo.staging.explorer.content.noVersion"; /** The default selection. */ private List<NodeComparison> defaultSelection = new ArrayList<NodeComparison>(); /** The synchronization service. */ private SynchronizationService synchronizationService_; /** The message type. */ String messageType = "info"; /** The state string. */ String stateString = NodeComparisonState.MODIFIED_ON_SOURCE.getKey(); /** The modified date filter. */ Calendar modifiedDateFilter = null; /** The filter string. */ String filterString = null; /** The published content only. */ boolean publishedContentOnly = true; /** The target server input. */ private final UIFormSelectBox targetServerInput; /** The info field. */ private final UIFormInputInfo infoField; /** The select nodes component. */ private final SelectNodesComponent selectNodesComponent; /** The target servers. */ private List<TargetServer> targetServers; /** The current path. */ private String currentPath; /** The workspace. */ private String workspace; /** The selected nodes. */ List<NodeComparison> selectedNodes = new ArrayList<NodeComparison>(); /** * Instantiates a new push content popup component. * * @throws Exception the exception */ public PushContentPopupComponent() throws Exception { infoField = new UIFormInputInfo(INFO_FIELD_NAME, INFO_FIELD_NAME, ""); addUIFormInput(infoField); targetServerInput = new UIFormSelectBox(TARGET_SERVER_NAME_FIELD_NAME, TARGET_SERVER_NAME_FIELD_NAME, new ArrayList<SelectItemOption<String>>()); addUIFormInput(targetServerInput); // addUIFormInput(new UICheckBoxInput(PUBLISH_FIELD_NAME, // PUBLISH_FIELD_NAME, false)); selectNodesComponent = addChild(SelectNodesComponent.class, null, "SelectNodesComponent"); selectNodesComponent.setPushContentPopupComponent(this); selectNodesComponent.setRendered(false); } /** * Inits the. * * @throws Exception the exception */ public void init() throws Exception { List<SelectItemOption<String>> itemOptions = getChild(UIFormSelectBox.class).getOptions(); itemOptions.clear(); try { targetServers = synchronizationService_.getSynchonizationServers(); } catch (Exception e) { LOG.warn(e); targetServers = new ArrayList<TargetServer>(); } for (TargetServer targetServer : targetServers) { SelectItemOption<String> selectItemOption = new SelectItemOption<String>(targetServer.getName(), targetServer.getId()); itemOptions.add(selectItemOption); } NodeComparison nodeComparison = new NodeComparison(); nodeComparison.setTitle(getResourceBundle().getString("PushContentPopupComponent.label.currentContent")); nodeComparison.setPath(getCurrentPath()); nodeComparison.setState(NodeComparisonState.UNKNOWN); defaultSelection.add(nodeComparison); selectNodesComponent.getSelectedNodesGrid().getUIPageIterator().setPageList(new LazyPageList<NodeComparison>(new ListAccessImpl<NodeComparison>(NodeComparison.class, defaultSelection), 5)); } /** * Adds the selection. * * @param nodeComparison the node comparison */ public void addSelection(NodeComparison nodeComparison) { selectedNodes.add(nodeComparison); } /** * Checks if is synchronization started. * * @return true, if is synchronization started */ public boolean isSynchronizationStarted() { return synchronizationStarted; } /** * Checks if is default entry. * * @param path the path * @return true, if is default entry */ public boolean isDefaultEntry(String path) { for (NodeComparison comparison : defaultSelection) { if (path.equals(comparison.getPath())) { return true; } } return false; } /** * Sets the message. * * @param message the message * @param type the type */ public void setMessage(String message, String type) { getUIFormInputInfo(INFO_FIELD_NAME).setValue(message); messageType = type; } /** * The listener interface for receiving selectAction events. * The class that is interested in processing a selectAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addSelectActionListener</code> method. When * the selectAction event occurs, that object's appropriate * method is invoked. * */ static public class SelectActionListener extends EventListener<UIForm> { /** * {@inheritDoc} */ public void execute(Event<UIForm> event) throws Exception { UIForm uiForm = event.getSource(); PushContentPopupComponent pushContentPopupComponent = null; if (uiForm instanceof PushContentPopupComponent) { pushContentPopupComponent = (PushContentPopupComponent) uiForm; } else if (uiForm instanceof SelectNodesComponent) { pushContentPopupComponent = ((SelectNodesComponent) uiForm).getPushContentPopupComponent(); } pushContentPopupComponent.setMessage(null, "info"); try { // get target server TargetServer targetServer = null; String targetServerId = pushContentPopupComponent.getUIFormSelectBox(TARGET_SERVER_NAME_FIELD_NAME).getValue(); Iterator<TargetServer> iterator = pushContentPopupComponent.getTargetServers().iterator(); while (iterator.hasNext() && targetServer == null) { TargetServer itTargetServer = iterator.next(); if (itTargetServer.getId().equals(targetServerId)) { targetServer = itTargetServer; } } if (targetServer == null) { ApplicationMessage message = new ApplicationMessage("PushContent.msg.targetServerMandatory", null, ApplicationMessage.ERROR); message.setResourceBundle(getResourceBundle()); pushContentPopupComponent.setMessage(message.getMessage(), "error"); event.getRequestContext().addUIComponentToUpdateByAjax(pushContentPopupComponent.getParent()); return; } List<NodeComparison> comparisons = Utils.compareLocalNodesWithTargetServer(pushContentPopupComponent.getWorkspace(), pushContentPopupComponent.getCurrentPath(), targetServer); pushContentPopupComponent.getSelectNodesComponent().init(); pushContentPopupComponent.getSelectNodesComponent().setComparisons(comparisons); pushContentPopupComponent.getSelectNodesComponent().setRendered(true); event.getRequestContext().addUIComponentToUpdateByAjax(pushContentPopupComponent.getParent()); } catch (Exception ex) { ApplicationMessage message; if (isConnectionException(ex)) { message = new ApplicationMessage("PushContent.msg.unableToConnect", null, ApplicationMessage.ERROR); } else { message = new ApplicationMessage("PushContent.msg.synchronizationError", null, ApplicationMessage.ERROR); } message.setResourceBundle(getResourceBundle()); pushContentPopupComponent.setMessage(message.getMessage(), "error"); LOG.error("Comparison of '" + pushContentPopupComponent.getCurrentPath() + "' failed:", ex); } } } /** * The listener interface for receiving pushAction events. * The class that is interested in processing a pushAction * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addPushActionListener</code> method. When * the pushAction event occurs, that object's appropriate * method is invoked. * */ static public class PushActionListener extends EventListener<PushContentPopupComponent> { /** * {@inheritDoc} */ public void execute(Event<PushContentPopupComponent> event) throws Exception { final PushContentPopupComponent pushContentPopupComponent = event.getSource(); pushContentPopupComponent.setMessage(null, "info"); UIPopupContainer uiPopupContainer = (UIPopupContainer) pushContentPopupComponent.getAncestorOfType(UIPopupContainer.class); UIApplication uiApp = pushContentPopupComponent.getAncestorOfType(UIApplication.class); try { // get cleanupPublication checkbox value // boolean cleanupPublication = // pushContentPopupComponent.getUICheckBoxInput(PUBLISH_FIELD_NAME).getValue(); final boolean cleanupPublication = false; // get target server TargetServer targetServer = null; String targetServerId = pushContentPopupComponent.getUIFormSelectBox(TARGET_SERVER_NAME_FIELD_NAME).getValue(); Iterator<TargetServer> iterator = pushContentPopupComponent.getTargetServers().iterator(); while (iterator.hasNext() && targetServer == null) { TargetServer itTargetServer = iterator.next(); if (itTargetServer.getId().equals(targetServerId)) { targetServer = itTargetServer; } } final TargetServer selectedServer = targetServer; if (targetServer == null) { ApplicationMessage message = new ApplicationMessage("PushContent.msg.targetServerMandatory", null, ApplicationMessage.ERROR); message.setResourceBundle(getResourceBundle()); pushContentPopupComponent.setMessage(message.getMessage(), "error"); event.getRequestContext().addUIComponentToUpdateByAjax(uiPopupContainer); return; } ResourceBundle resourceBundle = PushContentPopupComponent.getResourceBundle(); pushContentPopupComponent.setMessage(resourceBundle.getString("PushNavigation.msg.synchronizationInProgress"), "info"); if (pushContentPopupComponent.synchronizationFinished && !pushContentPopupComponent.synchronizationStarted) { pushContentPopupComponent.synchronizationStarted = true; pushContentPopupComponent.synchronizationFinished = false; Thread synchronizeThread = new Thread(new Runnable() { @Override public void run() { // Make sure that current container is of type // "PortalContainer" ExoContainerContext.setCurrentContainer(PortalContainer.getInstance()); // Use "PortalContainer" in current transaction RequestLifeCycle.begin(ExoContainerContext.getCurrentContainer()); try { List<NodeComparison> selectedComparisons = (List<NodeComparison>) pushContentPopupComponent.getSelectedNodes(); // If default selection if (selectedComparisons.isEmpty()) { List<Resource> resources = new ArrayList<Resource>(); resources.add(new Resource(StagingService.CONTENT_SITES_PATH + "/shared", "shared", "shared")); Map<String, String> exportOptions = new HashMap<String, String>(); Map<String, String> importOptions = new HashMap<String, String>(); String sqlQueryFilter = "query:select * from nt:base where jcr:path like '" + pushContentPopupComponent.getCurrentPath() + "'"; exportOptions.put("filter/query", StringEscapeUtils.unescapeHtml(URLDecoder.decode(sqlQueryFilter, "UTF-8"))); exportOptions.put("filter/taxonomy", "false"); exportOptions.put("filter/no-history", "" + cleanupPublication); exportOptions.put("filter/workspace", pushContentPopupComponent.getWorkspace()); boolean noVersion = false; String noVersionString = System.getProperty(CLEANUP_PUBLICATION, null); if (!StringUtils.isEmpty(noVersionString)) { noVersion = noVersionString.trim().equals("true"); } if (noVersion) { exportOptions.put("filter/no-history", "true"); importOptions.put("filter/cleanPublication", "true"); } // importOptions.put("filter/cleanPublication", // "" + // cleanupPublication); CONTENTS_HANDLER.synchronize(resources, exportOptions, importOptions, selectedServer); } else { // Multiple contents was selected, // synchronize one by one for (NodeComparison nodeComparison : selectedComparisons) { List<Resource> resources = new ArrayList<Resource>(); resources.add(new Resource(StagingService.CONTENT_SITES_PATH + "/shared/contents", "shared", "shared")); Map<String, String> exportOptions = new HashMap<String, String>(); Map<String, String> importOptions = new HashMap<String, String>(); if (nodeComparison.getState().equals(NodeComparisonState.NOT_FOUND_ON_SOURCE)) { exportOptions.put("filter/removeNodes", nodeComparison.getPath()); } else { boolean noVersion = false; String noVersionString = System.getProperty(CLEANUP_PUBLICATION, null); if (!StringUtils.isEmpty(noVersionString)) { noVersion = noVersionString.trim().equals("true"); } if (noVersion && nodeComparison.isPublished()) { exportOptions.put("filter/no-history", "true"); importOptions.put("filter/cleanPublication", "true"); } String sqlQueryFilter = "query:select * from nt:base where jcr:path like '" + nodeComparison.getPath() + "'"; exportOptions.put("filter/query", StringEscapeUtils.unescapeHtml(URLDecoder.decode(sqlQueryFilter, "UTF-8"))); exportOptions.put("filter/taxonomy", "false"); exportOptions.put("filter/no-history", "" + cleanupPublication); exportOptions.put("filter/workspace", pushContentPopupComponent.getWorkspace()); } CONTENTS_HANDLER.synchronize(resources, exportOptions, importOptions, selectedServer); } } LOG.info("Synchronization of '" + pushContentPopupComponent.getCurrentPath() + "' done."); } catch (Exception e) { pushContentPopupComponent.synchronizationError = e; } finally { pushContentPopupComponent.synchronizationFinished = true; RequestLifeCycle.end(); } } }); synchronizeThread.start(); } else { if (pushContentPopupComponent.synchronizationStarted) { if (pushContentPopupComponent.synchronizationFinished) { if (pushContentPopupComponent.synchronizationError == null) { pushContentPopupComponent.synchronizationStarted = false; // Update UI uiPopupContainer.deActivate(); uiApp.addMessage(new ApplicationMessage("PushContent.msg.synchronizationDone", null, ApplicationMessage.INFO)); } else { Throwable tempException = pushContentPopupComponent.synchronizationError; pushContentPopupComponent.synchronizationStarted = false; pushContentPopupComponent.synchronizationError = null; throw tempException; } } } } } catch (Throwable ex) { ApplicationMessage message = null; if (isConnectionException(ex)) { message = new ApplicationMessage("PushContent.msg.unableToConnect", null, ApplicationMessage.ERROR); } else { message = new ApplicationMessage("PushContent.msg.synchronizationError", null, ApplicationMessage.ERROR); } message.setResourceBundle(getResourceBundle()); uiPopupContainer.deActivate(); uiApp.addMessage(message); } } } /** * Check if the exception has a ConnectionException cause. * * @param ex the ex * @return true, if is connection exception */ private static boolean isConnectionException(Throwable ex) { boolean connectionException = false; Throwable throwable = ex; do { if (throwable instanceof ConnectException) { connectionException = true; } else { throwable = throwable.getCause(); } } while (!connectionException && throwable != null); return connectionException; } /** * 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. * */ static public class CloseActionListener extends EventListener<PushContentPopupComponent> { /** * {@inheritDoc} */ public void execute(Event<PushContentPopupComponent> event) throws Exception { PushContentPopupComponent pushContentPopupComponent = event.getSource(); UIPopupContainer uiPopupContainer = (UIPopupContainer) pushContentPopupComponent.getAncestorOfType(UIPopupContainer.class); uiPopupContainer.deActivate(); event.getRequestContext().addUIComponentToUpdateByAjax(uiPopupContainer); } } /** * {@inheritDoc} */ public void activate() {} /** * {@inheritDoc} */ public void deActivate() {} /** * {@inheritDoc} */ public String[] getActions() { return new String[] { "Push", "Close", "Select" }; } /** * Gets the selected nodes. * * @return the selected nodes */ public List<NodeComparison> getSelectedNodes() { return selectedNodes; } /** * Gets the resource bundle. * * @return the resource bundle */ public static ResourceBundle getResourceBundle() { return WebuiRequestContext.getCurrentInstance().getApplicationResourceBundle(); } /** * Gets the target servers. * * @return the target servers */ public List<TargetServer> getTargetServers() { return targetServers; } /** * Sets the synchronization service. * * @param synchronizationService the new synchronization service */ public void setSynchronizationService(SynchronizationService synchronizationService) { synchronizationService_ = synchronizationService; } /** * Sets the current path. * * @param currentPath the new current path */ public void setCurrentPath(String currentPath) { this.currentPath = currentPath; } /** * Gets the current path. * * @return the current path */ public String getCurrentPath() { return this.currentPath; } /** * Gets the workspace. * * @return the workspace */ public String getWorkspace() { return workspace; } /** * Sets the workspace. * * @param workspace the new workspace */ public void setWorkspace(String workspace) { this.workspace = workspace; } /** * Gets the default selection. * * @return the default selection */ public List<NodeComparison> getDefaultSelection() { return defaultSelection; } /** * Gets the select nodes component. * * @return the select nodes component */ public SelectNodesComponent getSelectNodesComponent() { return selectNodesComponent; } }