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 public void selectDirectory() { 141 resetSelectedDirectoryData(); 142 currentDirectoryInfo = directoryUIManager.getDirectoryInfo(selectedDirectoryName); 143 } 144 145 public DirectoryUI getCurrentDirectory() { 146 return currentDirectoryInfo; 147 } 148 149 public DocumentModelList getCurrentDirectoryEntries() { 150 if (currentDirectoryEntries == null) { 151 currentDirectoryEntries = new DocumentModelListImpl(); 152 String dirName = currentDirectoryInfo.getName(); 153 try (Session dirSession = dirService.open(dirName)) { 154 Map<String, Serializable> emptyMap = Collections.emptyMap(); 155 Set<String> emptySet = Collections.emptySet(); 156 DocumentModelList entries = dirSession.query(emptyMap, emptySet, null, true); 157 if (entries != null && !entries.isEmpty()) { 158 currentDirectoryEntries.addAll(entries); 159 } 160 // sort 161 String sortField = currentDirectoryInfo.getSortField(); 162 if (sortField == null) { 163 sortField = dirService.getDirectoryIdField(dirName); 164 } 165 // sort 166 Map<String, String> orderBy = new HashMap<String, String>(); 167 orderBy.put(sortField, DocumentModelComparator.ORDER_ASC); 168 Collections.sort(currentDirectoryEntries, 169 new DocumentModelComparator(dirService.getDirectorySchema(dirName), orderBy)); 170 } 171 } 172 return currentDirectoryEntries; 173 } 174 175 public void resetSelectedDirectoryData() { 176 currentDirectoryInfo = null; 177 currentDirectoryEntries = null; 178 resetSelectedDirectoryEntry(); 179 resetCreateDirectoryEntry(); 180 } 181 182 public boolean getShowAddForm() { 183 return showAddForm; 184 } 185 186 public void toggleShowAddForm() { 187 showAddForm = !showAddForm; 188 } 189 190 public DocumentModel getCreationDirectoryEntry() { 191 if (creationDirectoryEntry == null) { 192 String dirName = currentDirectoryInfo.getName(); 193 String schema = dirService.getDirectorySchema(dirName); 194 creationDirectoryEntry = BaseSession.createEntryModel(null, schema, null, null); 195 } 196 return creationDirectoryEntry; 197 } 198 199 public void createDirectoryEntry() { 200 String dirName = currentDirectoryInfo.getName(); 201 try (Session dirSession = dirService.open(dirName)) { 202 // check if entry already exists 203 String schema = dirService.getDirectorySchema(dirName); 204 String idField = dirService.getDirectoryIdField(dirName); 205 Object id = creationDirectoryEntry.getProperty(schema, idField); 206 if (id instanceof String && dirSession.hasEntry((String) id)) { 207 facesMessages.add(StatusMessage.Severity.ERROR, 208 messages.get("vocabulary.entry.identifier.already.exists")); 209 return; 210 } 211 dirSession.createEntry(creationDirectoryEntry); 212 213 resetCreateDirectoryEntry(); 214 // invalidate directory entries list 215 currentDirectoryEntries = null; 216 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 217 218 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.added")); 219 } 220 } 221 222 public void resetCreateDirectoryEntry() { 223 creationDirectoryEntry = null; 224 showAddForm = false; 225 } 226 227 public void selectDirectoryEntry(String entryId) { 228 String dirName = currentDirectoryInfo.getName(); 229 try (Session dirSession = dirService.open(dirName)) { 230 selectedDirectoryEntry = dirSession.getEntry(entryId); 231 } 232 } 233 234 public DocumentModel getSelectedDirectoryEntry() { 235 return selectedDirectoryEntry; 236 } 237 238 public void resetSelectedDirectoryEntry() { 239 selectedDirectoryEntry = null; 240 } 241 242 public void editSelectedDirectoryEntry() { 243 String dirName = currentDirectoryInfo.getName(); 244 try (Session dirSession = dirService.open(dirName)) { 245 dirSession.updateEntry(selectedDirectoryEntry); 246 selectedDirectoryEntry = null; 247 // invalidate directory entries list 248 currentDirectoryEntries = null; 249 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 250 251 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.edited")); 252 } 253 } 254 255 public void deleteDirectoryEntry(String entryId) { 256 String dirName = currentDirectoryInfo.getName(); 257 List<DirectoryDeleteConstraint> deleteConstraints = currentDirectoryInfo.getDeleteConstraints(); 258 if (deleteConstraints != null && !deleteConstraints.isEmpty()) { 259 for (DirectoryDeleteConstraint deleteConstraint : deleteConstraints) { 260 if (!deleteConstraint.canDelete(dirService, entryId)) { 261 facesMessages.add(StatusMessage.Severity.ERROR, 262 messages.get("feedback.directory.deleteEntry.constraintError")); 263 return; 264 } 265 } 266 } 267 try (Session dirSession = dirService.open(dirName)) { 268 try { 269 dirSession.deleteEntry(entryId); 270 // invalidate directory entries list 271 currentDirectoryEntries = null; 272 Events.instance().raiseEvent(EventNames.DIRECTORY_CHANGED, dirName); 273 facesMessages.add(StatusMessage.Severity.INFO, messages.get("vocabulary.entry.deleted")); 274 } catch (DirectoryDeleteConstraintException e) { 275 facesMessages.add(StatusMessage.Severity.ERROR, 276 messages.get("feedback.directory.deleteEntry.constraintError")); 277 } 278 } 279 } 280 281 public boolean isReadOnly(String directoryName) { 282 boolean isReadOnly; 283 284 try (Session dirSession = dirService.open(directoryName)) { 285 // Check Directory ReadOnly Status 286 boolean dirReadOnly = dirSession.isReadOnly(); 287 288 // Check DirectoryUI ReadOnly Status 289 boolean dirUIReadOnly; 290 DirectoryUI dirInfo = directoryUIManager.getDirectoryInfo(directoryName); 291 if (dirInfo == null) { 292 // assume read-only 293 dirUIReadOnly = true; 294 } else { 295 dirUIReadOnly = Boolean.TRUE.equals(dirInfo.isReadOnly()); 296 } 297 298 isReadOnly = dirReadOnly || dirUIReadOnly; 299 } 300 return isReadOnly; 301 } 302 303 protected ActionContext createDirectoryActionContext() { 304 return createDirectoryActionContext(selectedDirectoryName); 305 } 306 307 protected ActionContext createDirectoryActionContext(String directoryName) { 308 FacesContext faces = FacesContext.getCurrentInstance(); 309 if (faces == null) { 310 throw new IllegalArgumentException("Faces context is null"); 311 } 312 ActionContext ctx = new JSFActionContext(faces); 313 ctx.putLocalVariable("SeamContext", new SeamContextHelper()); 314 ctx.putLocalVariable("directoryName", directoryName); 315 ctx.setCurrentPrincipal(currentNuxeoPrincipal); 316 return ctx; 317 } 318 319 public boolean checkContextualDirectoryFilter(String filterName) { 320 return actionManager.checkFilter(filterName, createDirectoryActionContext()); 321 } 322 323 /** 324 * @since 5.9.1 325 */ 326 public boolean checkContextualDirectoryFilter(String filterName, String directoryName) { 327 return actionManager.checkFilter(filterName, createDirectoryActionContext(directoryName)); 328 } 329 330 @Observer(value = { EventNames.FLUSH_EVENT }, create = false) 331 @BypassInterceptors 332 public void onHotReloadFlush() { 333 directoryNames = null; 334 resetSelectedDirectoryData(); 335 } 336 337}