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}