001/* 002 * (C) Copyright 2011 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * Contributors: 014 * Anahide Tchertchian <at@nuxeo.com> 015 * Thomas Roger <troger@nuxeo.com> 016 */ 017 018package org.nuxeo.ecm.webapp.directory; 019 020import java.io.Serializable; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import javax.faces.context.FacesContext; 028 029import org.jboss.seam.ScopeType; 030import org.jboss.seam.annotations.Begin; 031import org.jboss.seam.annotations.Create; 032import org.jboss.seam.annotations.In; 033import org.jboss.seam.annotations.Name; 034import org.jboss.seam.annotations.Observer; 035import org.jboss.seam.annotations.Scope; 036import org.jboss.seam.annotations.intercept.BypassInterceptors; 037import org.jboss.seam.core.Events; 038import org.jboss.seam.faces.FacesMessages; 039import org.jboss.seam.international.StatusMessage; 040import org.nuxeo.ecm.core.api.DocumentModel; 041import org.nuxeo.ecm.core.api.DocumentModelComparator; 042import org.nuxeo.ecm.core.api.DocumentModelList; 043import org.nuxeo.ecm.core.api.NuxeoPrincipal; 044import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 045import org.nuxeo.ecm.directory.BaseSession; 046import org.nuxeo.ecm.directory.Session; 047import org.nuxeo.ecm.directory.api.DirectoryService; 048import org.nuxeo.ecm.directory.api.ui.DirectoryUI; 049import org.nuxeo.ecm.directory.api.ui.DirectoryUIDeleteConstraint; 050import org.nuxeo.ecm.directory.api.ui.DirectoryUIManager; 051import org.nuxeo.ecm.platform.actions.ActionContext; 052import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 053import org.nuxeo.ecm.platform.actions.jsf.JSFActionContext; 054import org.nuxeo.ecm.platform.ui.web.directory.DirectoryHelper; 055import org.nuxeo.ecm.platform.ui.web.util.SeamContextHelper; 056import org.nuxeo.ecm.webapp.helpers.EventNames; 057 058/** 059 * Manages directories editable by administrators. 060 * 061 * @author Anahide Tchertchian 062 */ 063@Name("directoryUIActions") 064@Scope(ScopeType.CONVERSATION) 065public class DirectoryUIActionsBean implements Serializable { 066 067 private static final long serialVersionUID = 1L; 068 069 public static final String DIRECTORY_DEFAULT_VIEW = "view_directory"; 070 071 @In(create = true) 072 protected transient DirectoryUIManager directoryUIManager; 073 074 // FIXME: use a business delegate 075 protected transient DirectoryService dirService; 076 077 @In(create = true, required = false) 078 protected transient FacesMessages facesMessages; 079 080 @In(create = true) 081 protected Map<String, String> messages; 082 083 @In(create = true, required = false) 084 protected transient ActionManager actionManager; 085 086 @In(create = true) 087 private transient NuxeoPrincipal currentNuxeoPrincipal; 088 089 protected List<String> directoryNames; 090 091 protected DirectoryUI currentDirectoryInfo; 092 093 protected DocumentModelList currentDirectoryEntries; 094 095 protected DocumentModel selectedDirectoryEntry; 096 097 protected boolean showAddForm = false; 098 099 protected DocumentModel creationDirectoryEntry; 100 101 protected String selectedDirectoryName; 102 103 @Begin(join = true) 104 @Create 105 public void initialize() { 106 initDirService(); 107 } 108 109 private void initDirService() { 110 if (dirService == null) { 111 dirService = DirectoryHelper.getDirectoryService(); 112 } 113 } 114 115 public List<String> getDirectoryNames() { 116 if (directoryNames == null) { 117 directoryNames = directoryUIManager.getDirectoryNames(); 118 if (directoryNames.size() > 0) { 119 // preserve selected directory if present 120 if (selectedDirectoryName == null || !directoryNames.contains(selectedDirectoryName)) { 121 selectedDirectoryName = directoryNames.get(0); 122 } 123 selectDirectory(); 124 } 125 } 126 return directoryNames; 127 } 128 129 public String getSelectedDirectoryName() { 130 return selectedDirectoryName; 131 } 132 133 public void setSelectedDirectoryName(String selectedDirectoryName) { 134 this.selectedDirectoryName = selectedDirectoryName; 135 } 136 137 @Deprecated 138 public String selectDirectory(String directoryName) { 139 resetSelectedDirectoryData(); 140 currentDirectoryInfo = directoryUIManager.getDirectoryInfo(directoryName); 141 String view = currentDirectoryInfo.getView(); 142 if (view == null) { 143 view = DIRECTORY_DEFAULT_VIEW; 144 } 145 return view; 146 } 147 148 public void selectDirectory() { 149 resetSelectedDirectoryData(); 150 currentDirectoryInfo = directoryUIManager.getDirectoryInfo(selectedDirectoryName); 151 } 152 153 public DirectoryUI getCurrentDirectory() { 154 return currentDirectoryInfo; 155 } 156 157 public DocumentModelList getCurrentDirectoryEntries() { 158 if (currentDirectoryEntries == null) { 159 currentDirectoryEntries = new DocumentModelListImpl(); 160 String dirName = currentDirectoryInfo.getName(); 161 try (Session dirSession = dirService.open(dirName)) { 162 Map<String, Serializable> emptyMap = Collections.emptyMap(); 163 Set<String> emptySet = Collections.emptySet(); 164 DocumentModelList entries = dirSession.query(emptyMap, emptySet, null, true); 165 if (entries != null && !entries.isEmpty()) { 166 currentDirectoryEntries.addAll(entries); 167 } 168 // sort 169 String sortField = currentDirectoryInfo.getSortField(); 170 if (sortField == null) { 171 sortField = dirService.getDirectoryIdField(dirName); 172 } 173 // sort 174 Map<String, String> orderBy = new HashMap<String, String>(); 175 orderBy.put(sortField, DocumentModelComparator.ORDER_ASC); 176 Collections.sort(currentDirectoryEntries, 177 new DocumentModelComparator(dirService.getDirectorySchema(dirName), orderBy)); 178 } 179 } 180 return currentDirectoryEntries; 181 } 182 183 public void resetSelectedDirectoryData() { 184 currentDirectoryInfo = null; 185 currentDirectoryEntries = null; 186 resetSelectedDirectoryEntry(); 187 resetCreateDirectoryEntry(); 188 } 189 190 public boolean getShowAddForm() { 191 return showAddForm; 192 } 193 194 public void toggleShowAddForm() { 195 showAddForm = !showAddForm; 196 } 197 198 public DocumentModel getCreationDirectoryEntry() { 199 if (creationDirectoryEntry == null) { 200 String dirName = currentDirectoryInfo.getName(); 201 String schema = dirService.getDirectorySchema(dirName); 202 creationDirectoryEntry = BaseSession.createEntryModel(null, schema, null, null); 203 } 204 return creationDirectoryEntry; 205 } 206 207 public void createDirectoryEntry() { 208 String dirName = currentDirectoryInfo.getName(); 209 try (Session dirSession = dirService.open(dirName)) { 210 // check if entry already exists 211 String schema = dirService.getDirectorySchema(dirName); 212 String idField = dirService.getDirectoryIdField(dirName); 213 Object id = creationDirectoryEntry.getProperty(schema, idField); 214 if (id instanceof String && dirSession.hasEntry((String) id)) { 215 facesMessages.add(StatusMessage.Severity.ERROR, 216 messages.get("vocabulary.entry.identifier.already.exists")); 217 return; 218 } 219 dirSession.createEntry(creationDirectoryEntry); 220 221 resetCreateDirectoryEntry(); 222 // invalidate directory entries list 223 currentDirectoryEntries = null; 224 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 225 226 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.added")); 227 } 228 } 229 230 public void resetCreateDirectoryEntry() { 231 creationDirectoryEntry = null; 232 showAddForm = false; 233 } 234 235 public void selectDirectoryEntry(String entryId) { 236 String dirName = currentDirectoryInfo.getName(); 237 try (Session dirSession = dirService.open(dirName)) { 238 selectedDirectoryEntry = dirSession.getEntry(entryId); 239 } 240 } 241 242 public DocumentModel getSelectedDirectoryEntry() { 243 return selectedDirectoryEntry; 244 } 245 246 public void resetSelectedDirectoryEntry() { 247 selectedDirectoryEntry = null; 248 } 249 250 public void editSelectedDirectoryEntry() { 251 String dirName = currentDirectoryInfo.getName(); 252 try (Session dirSession = dirService.open(dirName)) { 253 dirSession.updateEntry(selectedDirectoryEntry); 254 selectedDirectoryEntry = null; 255 // invalidate directory entries list 256 currentDirectoryEntries = null; 257 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 258 259 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.edited")); 260 } 261 } 262 263 public void deleteDirectoryEntry(String entryId) { 264 String dirName = currentDirectoryInfo.getName(); 265 List<DirectoryUIDeleteConstraint> deleteConstraints = currentDirectoryInfo.getDeleteConstraints(); 266 if (deleteConstraints != null && !deleteConstraints.isEmpty()) { 267 for (DirectoryUIDeleteConstraint deleteConstraint : deleteConstraints) { 268 if (!deleteConstraint.canDelete(dirService, entryId)) { 269 facesMessages.add(StatusMessage.Severity.ERROR, 270 messages.get("feedback.directory.deleteEntry.constraintError")); 271 return; 272 } 273 } 274 } 275 try (Session dirSession = dirService.open(dirName)) { 276 dirSession.deleteEntry(entryId); 277 // invalidate directory entries list 278 currentDirectoryEntries = null; 279 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 280 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.deleted")); 281 } 282 } 283 284 public boolean isReadOnly(String directoryName) { 285 boolean isReadOnly; 286 287 try (Session dirSession = dirService.open(directoryName)) { 288 // Check Directory ReadOnly Status 289 boolean dirReadOnly = dirSession.isReadOnly(); 290 291 // Check DirectoryUI ReadOnly Status 292 boolean dirUIReadOnly; 293 DirectoryUI dirInfo = directoryUIManager.getDirectoryInfo(directoryName); 294 if (dirInfo == null) { 295 // assume read-only 296 dirUIReadOnly = true; 297 } else { 298 dirUIReadOnly = Boolean.TRUE.equals(dirInfo.isReadOnly()); 299 } 300 301 isReadOnly = dirReadOnly || dirUIReadOnly; 302 } 303 return isReadOnly; 304 } 305 306 protected ActionContext createDirectoryActionContext() { 307 return createDirectoryActionContext(selectedDirectoryName); 308 } 309 310 protected ActionContext createDirectoryActionContext(String directoryName) { 311 FacesContext faces = FacesContext.getCurrentInstance(); 312 if (faces == null) { 313 throw new IllegalArgumentException("Faces context is null"); 314 } 315 ActionContext ctx = new JSFActionContext(faces); 316 ctx.putLocalVariable("SeamContext", new SeamContextHelper()); 317 ctx.putLocalVariable("directoryName", directoryName); 318 ctx.setCurrentPrincipal(currentNuxeoPrincipal); 319 return ctx; 320 } 321 322 public boolean checkContextualDirectoryFilter(String filterName) { 323 return actionManager.checkFilter(filterName, createDirectoryActionContext()); 324 } 325 326 /** 327 * @since 5.9.1 328 */ 329 public boolean checkContextualDirectoryFilter(String filterName, String directoryName) { 330 return actionManager.checkFilter(filterName, createDirectoryActionContext(directoryName)); 331 } 332 333 @Observer(value = { EventNames.FLUSH_EVENT }, create = false) 334 @BypassInterceptors 335 public void onHotReloadFlush() { 336 directoryNames = null; 337 resetSelectedDirectoryData(); 338 } 339 340}