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}