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.Session; 049import org.nuxeo.ecm.directory.api.DirectoryService; 050import org.nuxeo.ecm.directory.api.ui.DirectoryUI; 051import org.nuxeo.ecm.directory.api.ui.DirectoryUIDeleteConstraint; 052import org.nuxeo.ecm.directory.api.ui.DirectoryUIManager; 053import org.nuxeo.ecm.platform.actions.ActionContext; 054import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 055import org.nuxeo.ecm.platform.actions.jsf.JSFActionContext; 056import org.nuxeo.ecm.platform.ui.web.directory.DirectoryHelper; 057import org.nuxeo.ecm.platform.ui.web.util.SeamContextHelper; 058import org.nuxeo.ecm.webapp.helpers.EventNames; 059 060/** 061 * Manages directories editable by administrators. 062 * 063 * @author Anahide Tchertchian 064 */ 065@Name("directoryUIActions") 066@Scope(ScopeType.CONVERSATION) 067public class DirectoryUIActionsBean implements Serializable { 068 069 private static final long serialVersionUID = 1L; 070 071 public static final String DIRECTORY_DEFAULT_VIEW = "view_directory"; 072 073 @In(create = true) 074 protected transient DirectoryUIManager directoryUIManager; 075 076 // FIXME: use a business delegate 077 protected transient DirectoryService dirService; 078 079 @In(create = true, required = false) 080 protected transient FacesMessages facesMessages; 081 082 @In(create = true) 083 protected Map<String, String> messages; 084 085 @In(create = true, required = false) 086 protected transient ActionManager actionManager; 087 088 @In(create = true) 089 private transient NuxeoPrincipal currentNuxeoPrincipal; 090 091 protected List<String> directoryNames; 092 093 protected DirectoryUI currentDirectoryInfo; 094 095 protected DocumentModelList currentDirectoryEntries; 096 097 protected DocumentModel selectedDirectoryEntry; 098 099 protected boolean showAddForm = false; 100 101 protected DocumentModel creationDirectoryEntry; 102 103 protected String selectedDirectoryName; 104 105 @Begin(join = true) 106 @Create 107 public void initialize() { 108 initDirService(); 109 } 110 111 private void initDirService() { 112 if (dirService == null) { 113 dirService = DirectoryHelper.getDirectoryService(); 114 } 115 } 116 117 public List<String> getDirectoryNames() { 118 if (directoryNames == null) { 119 directoryNames = directoryUIManager.getDirectoryNames(); 120 if (directoryNames.size() > 0) { 121 // preserve selected directory if present 122 if (selectedDirectoryName == null || !directoryNames.contains(selectedDirectoryName)) { 123 selectedDirectoryName = directoryNames.get(0); 124 } 125 selectDirectory(); 126 } 127 } 128 return directoryNames; 129 } 130 131 public String getSelectedDirectoryName() { 132 return selectedDirectoryName; 133 } 134 135 public void setSelectedDirectoryName(String selectedDirectoryName) { 136 this.selectedDirectoryName = selectedDirectoryName; 137 } 138 139 @Deprecated 140 public String selectDirectory(String directoryName) { 141 resetSelectedDirectoryData(); 142 currentDirectoryInfo = directoryUIManager.getDirectoryInfo(directoryName); 143 String view = currentDirectoryInfo.getView(); 144 if (view == null) { 145 view = DIRECTORY_DEFAULT_VIEW; 146 } 147 return view; 148 } 149 150 public void selectDirectory() { 151 resetSelectedDirectoryData(); 152 currentDirectoryInfo = directoryUIManager.getDirectoryInfo(selectedDirectoryName); 153 } 154 155 public DirectoryUI getCurrentDirectory() { 156 return currentDirectoryInfo; 157 } 158 159 public DocumentModelList getCurrentDirectoryEntries() { 160 if (currentDirectoryEntries == null) { 161 currentDirectoryEntries = new DocumentModelListImpl(); 162 String dirName = currentDirectoryInfo.getName(); 163 try (Session dirSession = dirService.open(dirName)) { 164 Map<String, Serializable> emptyMap = Collections.emptyMap(); 165 Set<String> emptySet = Collections.emptySet(); 166 DocumentModelList entries = dirSession.query(emptyMap, emptySet, null, true); 167 if (entries != null && !entries.isEmpty()) { 168 currentDirectoryEntries.addAll(entries); 169 } 170 // sort 171 String sortField = currentDirectoryInfo.getSortField(); 172 if (sortField == null) { 173 sortField = dirService.getDirectoryIdField(dirName); 174 } 175 // sort 176 Map<String, String> orderBy = new HashMap<String, String>(); 177 orderBy.put(sortField, DocumentModelComparator.ORDER_ASC); 178 Collections.sort(currentDirectoryEntries, 179 new DocumentModelComparator(dirService.getDirectorySchema(dirName), orderBy)); 180 } 181 } 182 return currentDirectoryEntries; 183 } 184 185 public void resetSelectedDirectoryData() { 186 currentDirectoryInfo = null; 187 currentDirectoryEntries = null; 188 resetSelectedDirectoryEntry(); 189 resetCreateDirectoryEntry(); 190 } 191 192 public boolean getShowAddForm() { 193 return showAddForm; 194 } 195 196 public void toggleShowAddForm() { 197 showAddForm = !showAddForm; 198 } 199 200 public DocumentModel getCreationDirectoryEntry() { 201 if (creationDirectoryEntry == null) { 202 String dirName = currentDirectoryInfo.getName(); 203 String schema = dirService.getDirectorySchema(dirName); 204 creationDirectoryEntry = BaseSession.createEntryModel(null, schema, null, null); 205 } 206 return creationDirectoryEntry; 207 } 208 209 public void createDirectoryEntry() { 210 String dirName = currentDirectoryInfo.getName(); 211 try (Session dirSession = dirService.open(dirName)) { 212 // check if entry already exists 213 String schema = dirService.getDirectorySchema(dirName); 214 String idField = dirService.getDirectoryIdField(dirName); 215 Object id = creationDirectoryEntry.getProperty(schema, idField); 216 if (id instanceof String && dirSession.hasEntry((String) id)) { 217 facesMessages.add(StatusMessage.Severity.ERROR, 218 messages.get("vocabulary.entry.identifier.already.exists")); 219 return; 220 } 221 dirSession.createEntry(creationDirectoryEntry); 222 223 resetCreateDirectoryEntry(); 224 // invalidate directory entries list 225 currentDirectoryEntries = null; 226 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 227 228 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.added")); 229 } 230 } 231 232 public void resetCreateDirectoryEntry() { 233 creationDirectoryEntry = null; 234 showAddForm = false; 235 } 236 237 public void selectDirectoryEntry(String entryId) { 238 String dirName = currentDirectoryInfo.getName(); 239 try (Session dirSession = dirService.open(dirName)) { 240 selectedDirectoryEntry = dirSession.getEntry(entryId); 241 } 242 } 243 244 public DocumentModel getSelectedDirectoryEntry() { 245 return selectedDirectoryEntry; 246 } 247 248 public void resetSelectedDirectoryEntry() { 249 selectedDirectoryEntry = null; 250 } 251 252 public void editSelectedDirectoryEntry() { 253 String dirName = currentDirectoryInfo.getName(); 254 try (Session dirSession = dirService.open(dirName)) { 255 dirSession.updateEntry(selectedDirectoryEntry); 256 selectedDirectoryEntry = null; 257 // invalidate directory entries list 258 currentDirectoryEntries = null; 259 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 260 261 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.edited")); 262 } 263 } 264 265 public void deleteDirectoryEntry(String entryId) { 266 String dirName = currentDirectoryInfo.getName(); 267 List<DirectoryUIDeleteConstraint> deleteConstraints = currentDirectoryInfo.getDeleteConstraints(); 268 if (deleteConstraints != null && !deleteConstraints.isEmpty()) { 269 for (DirectoryUIDeleteConstraint deleteConstraint : deleteConstraints) { 270 if (!deleteConstraint.canDelete(dirService, entryId)) { 271 facesMessages.add(StatusMessage.Severity.ERROR, 272 messages.get("feedback.directory.deleteEntry.constraintError")); 273 return; 274 } 275 } 276 } 277 try (Session dirSession = dirService.open(dirName)) { 278 dirSession.deleteEntry(entryId); 279 // invalidate directory entries list 280 currentDirectoryEntries = null; 281 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 282 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.deleted")); 283 } 284 } 285 286 public boolean isReadOnly(String directoryName) { 287 boolean isReadOnly; 288 289 try (Session dirSession = dirService.open(directoryName)) { 290 // Check Directory ReadOnly Status 291 boolean dirReadOnly = dirSession.isReadOnly(); 292 293 // Check DirectoryUI ReadOnly Status 294 boolean dirUIReadOnly; 295 DirectoryUI dirInfo = directoryUIManager.getDirectoryInfo(directoryName); 296 if (dirInfo == null) { 297 // assume read-only 298 dirUIReadOnly = true; 299 } else { 300 dirUIReadOnly = Boolean.TRUE.equals(dirInfo.isReadOnly()); 301 } 302 303 isReadOnly = dirReadOnly || dirUIReadOnly; 304 } 305 return isReadOnly; 306 } 307 308 protected ActionContext createDirectoryActionContext() { 309 return createDirectoryActionContext(selectedDirectoryName); 310 } 311 312 protected ActionContext createDirectoryActionContext(String directoryName) { 313 FacesContext faces = FacesContext.getCurrentInstance(); 314 if (faces == null) { 315 throw new IllegalArgumentException("Faces context is null"); 316 } 317 ActionContext ctx = new JSFActionContext(faces); 318 ctx.putLocalVariable("SeamContext", new SeamContextHelper()); 319 ctx.putLocalVariable("directoryName", directoryName); 320 ctx.setCurrentPrincipal(currentNuxeoPrincipal); 321 return ctx; 322 } 323 324 public boolean checkContextualDirectoryFilter(String filterName) { 325 return actionManager.checkFilter(filterName, createDirectoryActionContext()); 326 } 327 328 /** 329 * @since 5.9.1 330 */ 331 public boolean checkContextualDirectoryFilter(String filterName, String directoryName) { 332 return actionManager.checkFilter(filterName, createDirectoryActionContext(directoryName)); 333 } 334 335 @Observer(value = { EventNames.FLUSH_EVENT }, create = false) 336 @BypassInterceptors 337 public void onHotReloadFlush() { 338 directoryNames = null; 339 resetSelectedDirectoryData(); 340 } 341 342}