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}