diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 088f7238db..bf61efd79f 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -1158,6 +1158,7 @@ sandbox_browse=Browse Website sandbox_revert=Undo sandbox_revertall=Undo All sandbox_submitall=Submit All +count_conflicted_items={0} file(s) conflict with changes made by other users sandbox_submitselected=Submit Selected sandbox_revertselected=Undo Selected sandbox_icon=Browse Website @@ -1295,6 +1296,7 @@ revert_selected_title=Undo Selected Items revert_selected_desc=To undo the changes to the selected files in the sandbox, click OK. revert_selected_confirm=Are you sure you want to undo the changes to the selected files in from the sandbox? revert_all_title=Undo All Items +revert_all_conflicts=Revert all Conflicts revert_all_desc=To undo the changes to all the files in the sandbox, click OK. revert_all_confirm=Are you sure you want to undo the changes to all files in the sandbox? deploy_snapshot_title=Deploy Snapshot diff --git a/config/alfresco/web-client-config-wcm-actions.xml b/config/alfresco/web-client-config-wcm-actions.xml index e05e2331fd..114e00a8c0 100644 --- a/config/alfresco/web-client-config-wcm-actions.xml +++ b/config/alfresco/web-client-config-wcm-actions.xml @@ -97,7 +97,7 @@ Write - org.alfresco.web.action.evaluator.WCMWorkflowDeletedEvaluator + org.alfresco.web.action.evaluator.WCMConflictEvaluator submit /images/icons/submit.gif #{AVMBrowseBean.setupContentAction} diff --git a/source/java/org/alfresco/web/action/evaluator/WCMConflictEvaluator.java b/source/java/org/alfresco/web/action/evaluator/WCMConflictEvaluator.java new file mode 100644 index 0000000000..6a105debed --- /dev/null +++ b/source/java/org/alfresco/web/action/evaluator/WCMConflictEvaluator.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.action.evaluator; + +import org.alfresco.service.cmr.avmsync.AVMDifference; +import org.alfresco.web.bean.repository.Node; + +public class WCMConflictEvaluator extends WCMWorkflowDeletedEvaluator +{ + + /* (non-Javadoc) + * @see org.alfresco.web.action.evaluator.WCMWorkflowDeletedEvaluator#evaluate(org.alfresco.web.bean.repository.Node) + */ + public boolean evaluate(final Node node) + { + if (!super.evaluate(node)) + { + return false; + } + Integer diff = (Integer)node.getProperties().get("avmDiff"); + if (diff == null) + { + return true; + } + if (diff == AVMDifference.CONFLICT) + { + return false; + } + return true; + } +} diff --git a/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java b/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java index 5640fc79fb..fefb6f06a4 100644 --- a/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java +++ b/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -36,6 +36,8 @@ import java.util.Map; import java.util.ResourceBundle; import javax.faces.application.FacesMessage; +import javax.faces.component.UIParameter; +import javax.faces.component.html.HtmlCommandButton; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import javax.faces.model.SelectItem; @@ -56,6 +58,8 @@ import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avmsync.AVMDifference; +import org.alfresco.service.cmr.avmsync.AVMSyncService; import org.alfresco.service.cmr.repository.FileTypeImageSize; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -70,6 +74,8 @@ import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.util.NameMatcher; import org.alfresco.util.Pair; import org.alfresco.util.VirtServerUtils; import org.alfresco.wcm.sandbox.SandboxInfo; @@ -226,6 +232,9 @@ public class AVMBrowseBean implements IContextListener /** AVM service bean reference */ transient protected AVMService avmService; + /** AVM sync service bean reference */ + transient protected AVMSyncService avmSyncService; + /** Action service bean reference */ transient protected ActionService actionService; @@ -306,6 +315,23 @@ public class AVMBrowseBean implements IContextListener return avmService; } + /** + * @param avmSyncService The AVMSyncService to set. + */ + public void setAvmSyncService(AVMSyncService avmSyncService) + { + this.avmSyncService = avmSyncService; + } + + protected AVMSyncService getAvmSyncService() + { + if (avmSyncService == null) + { + avmSyncService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getAVMSyncService(); + } + return avmSyncService; + } + /** * @param nodeService The NodeService to set. */ @@ -2125,4 +2151,57 @@ public class AVMBrowseBean implements IContextListener this.formName)); } } + + /** + * Revert All Conflicts + * + * @param event + */ + public void revertAllConflict(ActionEvent event) + { + final HtmlCommandButton button = (HtmlCommandButton) event.getComponent(); + + List params = button.getChildren(); + String userStore = null; + String stagingStore = null; + for (Object obj : params) + { + UIParameter uip = (UIParameter) obj; + if (uip.getName().equals("userStorePath")) + { + userStore = (String) uip.getValue(); + } + if (uip.getName().equals("stagingStorePath")) + { + stagingStore = (String) uip.getValue(); + } + } + NameMatcher matcher = (NameMatcher) FacesContextUtils.getRequiredWebApplicationContext(FacesContext.getCurrentInstance()).getBean("globalPathExcluder"); + + // calcluate the list of differences between the user store and the staging area + List diffs = this.getAvmSyncService().compare(-1, userStore, -1, stagingStore, matcher); + List> versionPaths = new ArrayList>(); + List tasks = null; + for (AVMDifference diff : diffs) + { + if (diff.getDifferenceCode() == AVMDifference.CONFLICT) + { + AVMNodeDescriptor node = getAvmService().lookup(-1, diff.getSourcePath(), true); + if (tasks == null) + { + tasks = AVMWorkflowUtil.getAssociatedTasksForSandbox(AVMUtil.getStoreName(diff.getSourcePath())); + } + if (AVMWorkflowUtil.getAssociatedTasksForNode(node, tasks).size() == 0) + { + String revertPath = diff.getSourcePath(); + versionPaths.add(new Pair(-1, revertPath)); + } + } + } + + Map args = new HashMap(1, 1.0f); + args.put(AVMUndoSandboxListAction.PARAM_NODE_LIST, (Serializable) versionPaths); + Action action = this.getActionService().createAction(AVMUndoSandboxListAction.NAME, args); + this.getActionService().executeAction(action, null); + } } diff --git a/source/java/org/alfresco/web/forms/xforms/XFormsBean.java b/source/java/org/alfresco/web/forms/xforms/XFormsBean.java index 9f0348cd4e..f203e6d7a3 100644 --- a/source/java/org/alfresco/web/forms/xforms/XFormsBean.java +++ b/source/java/org/alfresco/web/forms/xforms/XFormsBean.java @@ -574,7 +574,12 @@ public class XFormsBean implements Serializable request.getContextPath() + "/wcservice"); String rewrittenURI = uri; - if (uri.contains("${storeid}")) + if (uri.contains("{storeid}")) + { + final String storeId = AVMUtil.getStoreName(cwdAvmPath); + rewrittenURI = uri.replace("{storeid}", storeId); + } + else if (uri.contains("${storeid}")) { final String storeId = AVMUtil.getStoreName(cwdAvmPath); rewrittenURI = uri.replace("${storeid}", storeId); @@ -585,11 +590,17 @@ public class XFormsBean implements Serializable LOGGER.debug("no store id specified in webscript URI " + uri); } - if (uri.contains("${ticket}")) + if (uri.contains("{ticket}")) { AuthenticationService authenticationService = Repository.getServiceRegistry(facesContext).getAuthenticationService(); final String ticket = authenticationService.getCurrentTicket(); - rewrittenURI = rewrittenURI.replace("${ticket}", ticket); + rewrittenURI = rewrittenURI.replace("{ticket}", ticket); + } + else if (uri.contains("${ticket}")) + { + AuthenticationService authenticationService = Repository.getServiceRegistry(facesContext).getAuthenticationService(); + final String ticket = authenticationService.getCurrentTicket(); + rewrittenURI = rewrittenURI.replace("${ticket}", ticket); } else { diff --git a/source/java/org/alfresco/web/ui/wcm/component/UIUserSandboxes.java b/source/java/org/alfresco/web/ui/wcm/component/UIUserSandboxes.java index 9343df8d84..8d84210ee3 100644 --- a/source/java/org/alfresco/web/ui/wcm/component/UIUserSandboxes.java +++ b/source/java/org/alfresco/web/ui/wcm/component/UIUserSandboxes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -27,9 +27,12 @@ package org.alfresco.web.ui.wcm.component; import java.io.IOException; import java.io.Serializable; import java.text.DateFormat; +import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -38,6 +41,7 @@ import java.util.Set; import javax.faces.component.UIComponent; import javax.faces.component.UIParameter; +import javax.faces.component.html.HtmlCommandButton; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.el.ValueBinding; @@ -48,6 +52,7 @@ import org.alfresco.model.WCMAppModel; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.web.scripts.FileTypeImageUtils; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avmsync.AVMDifference; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AccessStatus; @@ -145,12 +150,15 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa private static final String MSG_NO_MODIFIED_ITEMS = "sandbox_no_modified_items"; private static final String MSG_NO_WEB_FORMS = "sandbox_no_web_forms"; private static final String MSG_MY_SANDBOX = "sandbox_my_sandbox"; + private static final String MSG_COUNT_CONFLICTED_ITEMS="count_conflicted_items"; + private static final String MSG_REVERT_ALL_CONFLICTS="revert_all_conflicts"; private static final String REQUEST_FORM_REF = "formref"; private static final String REQUEST_PREVIEW_REF = "prevhref"; private static final String REQUEST_UPDATE_TEST_SERVER = "updatetestserver"; private static final String SPACE_ICON = "/images/icons/" + BrowseBean.SPACE_SMALL_DEFAULT + ".gif"; + private static final String CONFLICTED_ICON = "/images/icons/conflict-16.gif"; public static final String PARAM_FORM_NAME = "form-name"; @@ -576,6 +584,7 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa out.write(" "); out.write(bundle.getString(MSG_MODIFIED_ITEMS)); out.write(""); + if (this.expandedPanels.contains(username + PANEL_MODIFIED)) { out.write("
"); @@ -721,10 +730,45 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa // compare user sandbox to staging sandbox - filter by current webapp, include deleted items String userStore = AVMUtil.buildUserMainStoreName(storeRoot, username); + String userStorePath = AVMUtil.buildStoreWebappPath(userStore, getWebapp()); + String stagingStore = AVMUtil.buildStagingStoreName(storeRoot); + String stagingStorePath = AVMUtil.buildStoreWebappPath(stagingStore, getWebapp()); + List assets = sandboxService.listChangedWebApp(userStore, getWebapp(), true); if (assets.size() != 0) { + // output confict header, only if conflicts exist + int diffCount = 0; + for (AssetInfo asset : assets) + { + if (asset.getDiffCode() == AVMDifference.CONFLICT) + { + diffCount++; + } + } + + if (diffCount > 0) + { + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write("
"); + + out.write(Utils.buildImageTag(fc, CONFLICTED_ICON, 16, 16, "", null, "-25%")); + out.write(" "); + out.write(MessageFormat.format(bundle.getString(MSG_COUNT_CONFLICTED_ITEMS), diffCount)); + + out.write(""); + Utils.encodeRecursive(fc, createRevertAllItemsButton(fc, bundle, username, userStorePath, stagingStorePath)); + out.write("
"); + out.write("
"); + } + // info we need to calculate preview paths for assets int rootPathIndex = AVMUtil.buildSandboxRootPath(userStore).length(); @@ -748,7 +792,7 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa // output the table of modified items // TODO: apply tag style - removed hardcoded - out.write(""); + out.write("
"); // output multi-select actions for this user out.write(""); + // move conflicts to top of list + if (diffCount > 0) + { + List conflicted = new ArrayList(); + + for(Iterator it = assets.iterator(); it.hasNext();) + { + AssetInfo diff = (AssetInfo) it.next(); + if (diff.getDiffCode() == AVMDifference.CONFLICT) + { + conflicted.add(diff); + it.remove(); + } + } + assets.addAll(0, conflicted); + } + // output each of the modified files as a row in the table int rowIndex = 0; for (AssetInfo node : assets) @@ -796,7 +857,12 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa String sourcePath = node.getAvmPath(); // output multi-select checkbox - out.write(""); + out.write(""); + out.write(""); + out.write(""); out.write("
"); @@ -775,7 +819,7 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa out.write(Integer.toString(index)); out.write("' onclick='"); out.write("javascript:_sb_select(this);"); - out.write("'>"); + out.write("'>"); out.write(bundle.getString(MSG_NAME)); out.write(""); out.write(bundle.getString(MSG_CREATED)); @@ -787,6 +831,23 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa out.write(bundle.getString(MSG_ACTIONS)); out.write("
"; - out.write(""); + if (node.isFile()) { - out.write(linkPrefix); - out.write(Utils.buildImageTag(fc, FileTypeImageUtils.getFileTypeImage(fc, name, true), "")); - out.write(""); - out.write(linkPrefix); - out.write(Utils.encode(name)); - UIAVMLockIcon lockIcon = (UIAVMLockIcon)fc.getApplication().createComponent( - UIAVMLockIcon.ALFRESCO_FACES_AVMLOCKICON); - lockIcon.setId("avmlock_" + Integer.toString(rowIndex)); - lockIcon.setValue(sourcePath); - Utils.encodeRecursive(fc, lockIcon); - out.write(""); + out.write(""); + if (node.getDiffCode() == AVMDifference.CONFLICT) + { + out.write(Utils.buildImageTag(fc, CONFLICTED_ICON, 16, 16, "")); + } + out.write(""); + UIAVMLockIcon lockIcon = (UIAVMLockIcon)fc.getApplication().createComponent(UIAVMLockIcon.ALFRESCO_FACES_AVMLOCKICON); + lockIcon.setId("avmlock_" + Integer.toString(rowIndex)); + lockIcon.setValue(sourcePath); + Utils.encodeRecursive(fc, lockIcon); + out.write(""); + out.write(linkPrefix); + out.write(Utils.buildImageTag(fc, FileTypeImageUtils.getFileTypeImage(fc, name, true), "")); + out.write(""); + out.write(""); + out.write(linkPrefix); + out.write(Utils.encode(name)); + out.write(""); } else { + out.write(""); out.write(Utils.buildImageTag(fc, SPACE_ICON, 16, 16, "")); out.write(""); out.write(Utils.encode(name)); @@ -850,6 +926,7 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa String assetPath = sourcePath.substring(rootPathIndex); String previewUrl = AVMUtil.getPreviewURI(userStore, JNDIConstants.DIR_DEFAULT_WWW_APPBASE + assetPath); avmNode.getProperties().put("previewUrl", previewUrl); + avmNode.getProperties().put("avmDiff", node.getDiffCode()); // size of files if (node.isFile()) @@ -874,6 +951,8 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa { // must have been deleted from this sandbox - show as ghosted String name = node.getName(); + out.write(""); if (node.isFile() && node.isDeleted()) { @@ -1107,6 +1186,31 @@ public class UIUserSandboxes extends SelfRenderingComponent implements Serializa return aquireAction(fc, store, username, name, icon, actionListener, outcome, null, null); } + private HtmlCommandButton createRevertAllItemsButton(FacesContext fc, ResourceBundle bundle, String name, String userStorePath, String stagingStorePath) + { + javax.faces.application.Application facesApp = fc.getApplication(); + String id = "revert_all_conflict" + new Date().getTime() + FacesHelper.makeLegalId(name); + HtmlCommandButton cb = (HtmlCommandButton) facesApp.createComponent(HtmlCommandButton.COMPONENT_TYPE); + cb.setId(id); + cb.setValue(bundle.getString(MSG_REVERT_ALL_CONFLICTS)); + cb.setActionListener(facesApp.createMethodBinding( + "#{AVMBrowseBean.revertAllConflict}", UIActions.ACTION_CLASS_ARGS)); + + UIParameter param = (UIParameter)facesApp.createComponent(ComponentConstants.JAVAX_FACES_PARAMETER); + param.setId(id + "_1"); + param.setName("userStorePath"); + param.setValue(userStorePath); + cb.getChildren().add(param); + param = (UIParameter)facesApp.createComponent(ComponentConstants.JAVAX_FACES_PARAMETER); + param.setId(id + "_2"); + param.setName("stagingStorePath"); + param.setValue(stagingStorePath); + cb.getChildren().add(param); + + this.getChildren().add(cb); + return cb; + } + /** * Aquire a UIActionLink component for the specified action * diff --git a/source/web/css/main.css b/source/web/css/main.css index 350ef268d1..216960546b 100644 --- a/source/web/css/main.css +++ b/source/web/css/main.css @@ -602,6 +602,23 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl border-color: #DDDDDD; } +.conflictItem +{ + background-color: #f7e0e1; +} + +.conflictItemsList +{ + background-color: #f7e0e1; + border-width: 1px; + border-style: solid; + border-color: #e39295; + padding-left: 5px; + padding-right: 5px; + padding-top: 3px; + padding-bottom: 3px; +} + .snapshotItemsList { background-color: #f5f5f5; diff --git a/source/web/images/icons/conflict-16.gif b/source/web/images/icons/conflict-16.gif new file mode 100644 index 0000000000..a92bda7644 Binary files /dev/null and b/source/web/images/icons/conflict-16.gif differ