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