001/*
002 *  Copyright 2017 Anyware Services
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 */
016package org.ametys.plugins.userdirectory.clientsideelement;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import javax.jcr.Node;
028import javax.jcr.RepositoryException;
029import javax.jcr.Value;
030
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.commons.lang3.StringUtils;
034import org.apache.jackrabbit.value.StringValue;
035
036import org.ametys.cms.contenttype.ContentType;
037import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
038import org.ametys.core.observation.Event;
039import org.ametys.core.observation.ObservationManager;
040import org.ametys.core.ui.Callable;
041import org.ametys.core.util.LambdaUtils;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.jcr.JCRAmetysObject;
044import org.ametys.plugins.userdirectory.OrganisationChartPageHandler;
045import org.ametys.plugins.userdirectory.UserDirectoryHelper;
046import org.ametys.plugins.userdirectory.observation.ObservationConstants;
047import org.ametys.plugins.userdirectory.page.VirtualOrganisationChartPageFactory;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.runtime.i18n.I18nizableTextParameter;
050import org.ametys.web.clientsideelement.AbstractPageClientSideElement;
051import org.ametys.web.repository.page.ModifiablePage;
052import org.ametys.web.repository.page.Page;
053import org.ametys.web.rights.PageRightAssignmentContext;
054
055/**
056 * Client side element for a controller wich set/remove the organisation chart root page
057 */
058public class SetOrganisationChartRootClientSideElement extends AbstractPageClientSideElement
059{
060    /** Observer manager. */
061    protected ObservationManager _observationManager;
062
063    /** The organisation chart page handler */
064    protected OrganisationChartPageHandler _pageHandler;
065    
066    /** The extension point for content types */
067    protected ContentTypeExtensionPoint _contentTypeEP;
068    
069    /** The organization chart page handler */
070    protected OrganisationChartPageHandler _orgUnitPageHandler;
071
072    @Override
073    public void service(ServiceManager smanager) throws ServiceException
074    {
075        super.service(smanager);
076        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
077        _pageHandler = (OrganisationChartPageHandler) smanager.lookup(OrganisationChartPageHandler.ROLE);
078        _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
079        _orgUnitPageHandler = (OrganisationChartPageHandler) smanager.lookup(OrganisationChartPageHandler.ROLE);
080    }
081    /**
082     * Gets the status of the given page
083     * @param pageId The page id
084     * @return the status of the given page
085     */
086    @Callable (rights = Callable.READ_ACCESS, rightContext = PageRightAssignmentContext.ID, paramIndex = 0)
087    public Map<String, Object> getStatus(String pageId)
088    {
089        Map<String, Object> result = new HashMap<>();
090        
091        Map<String, Object> parameters = this._script.getParameters();
092        
093        Page page = _resolver.resolveById(pageId);
094
095        if (page instanceof JCRAmetysObject)
096        {
097            if (_pageHandler.isOrganisationChartRootPage((JCRAmetysObject) page))
098            {
099                List<String> i18nParameters = new ArrayList<>();
100                i18nParameters.add(page.getTitle());
101    
102                I18nizableText ed = (I18nizableText) parameters.get("organisation-chart-page-description");
103                I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
104                result.put("organisation-chart-page-title", msg);
105                
106                ed = (I18nizableText) parameters.get("remove-organisation-chart-page-description");
107                msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
108                result.put("remove-organisation-chart-page-title", msg);
109                
110                String contentTypeId = page.getValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY);
111                
112                if (StringUtils.isNotEmpty(contentTypeId))
113                {
114                    I18nizableText contentTypeText = _contentTypeEP.hasExtension(contentTypeId) ? _contentTypeEP.getExtension(contentTypeId).getLabel() : new I18nizableText(contentTypeId);
115                    
116                    Map<String, I18nizableTextParameter> contentTypeI18nParameters = new HashMap<>();
117                    contentTypeI18nParameters.put("0", contentTypeText);
118                    
119                    ed = (I18nizableText) parameters.get("contenttype-organisation-chart-page-description");
120                    msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), contentTypeI18nParameters);
121                    result.put("contenttype-organisation-chart-page-description", msg);
122                }
123                
124                result.put("organisation-chart-page-id", new I18nizableText(page.getId()));
125            }
126            else
127            {
128                List<String> i18nParameters = new ArrayList<>();
129                i18nParameters.add(page.getTitle());
130    
131                I18nizableText ed = (I18nizableText) parameters.get("add-organisation-chart-page-description");
132                I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
133                
134                result.put("add-organisation-chart-page-id", new I18nizableText(page.getId()));
135                result.put("add-organisation-chart-page-title", msg);
136            }
137        }
138        else
139        {
140            List<String> noJcrI18nParameters = new ArrayList<>();
141            noJcrI18nParameters.add(page.getTitle());
142
143            I18nizableText ed = (I18nizableText) parameters.get("no-jcr-page-description");
144            I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), noJcrI18nParameters);
145            
146            result.put("no-jcr-page-id", new I18nizableText(page.getId()));
147            result.put("no-jcr-page-title", msg);
148        }
149        
150        return result;
151    }
152    
153    /**
154     * Sets the given page as the root of a organization chart
155     * @param pageId The id of the page
156     * @param contentType The id of the content type
157     * @return A result map
158     * @throws RepositoryException if a repository error occurred
159     */
160    @Callable (rights = "User_Directory_Right_Organisation_Chart_SetRoot", rightContext = PageRightAssignmentContext.ID, paramIndex = 0)
161    public Map<String, Object> setOrganisationChartRoot(String pageId, String contentType) throws RepositoryException
162    {
163        Map<String, Object> result = new HashMap<>();
164        if (!_contentTypeEP.isSameOrDescendant(contentType, UserDirectoryHelper.ORGUNIT_CONTENT_TYPE))
165        {
166            result.put("error", "invalid-content-type");
167            return result;
168        }
169        
170        Page page = _resolver.resolveById(pageId);
171        String oldContentType = page.getValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY);
172        
173        // Do nothing if page attribute are the same
174        if (!oldContentType.equals(contentType))
175        {
176            Set<Page> currentOrgUnitPages = _orgUnitPageHandler.getOrganisationChartRootPages(page.getSiteName(), page.getSitemapName());
177            
178            Map<String, Object> eventParams = new HashMap<>();
179            eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
180            
181            if (currentOrgUnitPages.contains(page))
182            {
183                // Unindex pages for all workspaces before the properties changed
184                _observationManager.notify(new Event(ObservationConstants.EVENT_ORGANISATION_CHART_ROOT_UPDATING, _currentUserProvider.getUser(), eventParams));
185                
186                _updateOrgUnitRootProperty(page, contentType);
187            }
188            else
189            {
190                _addOrgUnitRootProperty(page, contentType);
191            }
192            
193            
194            // Live synchronization
195            _notifyPageUpdated(page);
196            
197            // Indexation
198            _observationManager.notify(new Event(ObservationConstants.EVENT_ORGANISATION_CHART_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams));
199        }
200        
201        return result;
202    }
203    private void _addOrgUnitRootProperty(Page page, String contentType) throws RepositoryException
204    {
205        if (page instanceof JCRAmetysObject)
206        {
207            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
208            Node node = jcrPage.getNode();
209            
210            List<Value> values = new ArrayList<>();
211            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
212            {
213                values.addAll(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()));
214            }
215            
216            StringValue virtualOrgUnitPageFactoryClassName = new StringValue(VirtualOrganisationChartPageFactory.class.getName());
217            if (!values.contains(virtualOrgUnitPageFactoryClassName))
218            {
219                values.add(virtualOrgUnitPageFactoryClassName);
220            }
221            
222            node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()]));
223
224            // Set the organisation chart root property
225            if (page instanceof ModifiablePage)
226            {
227                ModifiablePage modifiablePage = (ModifiablePage) page;
228                modifiablePage.setValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME, contentType);
229            }
230            
231            jcrPage.saveChanges();
232        }
233    }
234    
235    private void _updateOrgUnitRootProperty(Page page, String contentType)
236    {
237        if (page instanceof ModifiablePage)
238        {
239            ModifiablePage modifiablePage = (ModifiablePage) page;
240            
241            // Set the organisation chart root property
242            modifiablePage.setValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME, contentType);
243            
244            modifiablePage.saveChanges();
245        }
246    }
247    /**
248     * Remove the organization chart root status to the given page
249     * @param pageId The id of the page
250     * @return A result map
251     * @throws RepositoryException if a repository error occurred
252     */
253    @Callable (rights = "User_Directory_Right_Organisation_Chart_SetRoot", rightContext = PageRightAssignmentContext.ID, paramIndex = 0)
254    public Map<String, Object> removeOrganisationChartRoot(String pageId) throws RepositoryException
255    {
256        Map<String, Object> result = new HashMap<>();
257        
258        Page page = _resolver.resolveById(pageId);
259        
260        if (page instanceof JCRAmetysObject)
261        {
262            if (!_pageHandler.isOrganisationChartRootPage((JCRAmetysObject) page))
263            {
264                result.put("error", "no-root");
265                return result;
266            }
267            
268            Map<String, Object> eventParams = new HashMap<>();
269            eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
270            
271            // Unindex pages for all workspaces before the properties were removed
272            _observationManager.notify(new Event(ObservationConstants.EVENT_ORGANISATION_CHART_ROOT_DELETING, _currentUserProvider.getUser(), eventParams));
273            
274            _removeOrgUnitRootProperty(page);
275            
276            _notifyPageUpdated(page);
277            
278            // After live synchronization
279            _observationManager.notify(new Event(ObservationConstants.EVENT_ORGANISATION_CHART_ROOT_DELETED, _currentUserProvider.getUser(), eventParams));
280        }
281        else
282        {
283            result.put("error", "no-root");
284        }
285        return result;
286    }
287
288    
289    /**
290     * Gets the content types which can build an organisation chart
291     * @param pageId The id of the page being edited
292     * @return the content types which can build an organisation chart
293     */
294    @Callable (rights = "User_Directory_Right_Organisation_Chart_SetRoot", rightContext = PageRightAssignmentContext.ID, paramIndex = 0)
295    public List<Map<String, Object>> getSupportedContentTypes(String pageId)
296    {
297        List<Map<String, Object>> result = new ArrayList<>();
298        Page page = _resolver.resolveById(pageId);
299        
300        Set<String> orgUnitContentTypes = new HashSet<>();
301                
302        orgUnitContentTypes.add(UserDirectoryHelper.ORGUNIT_CONTENT_TYPE);
303        orgUnitContentTypes.addAll(_contentTypeEP.getSubTypes(UserDirectoryHelper.ORGUNIT_CONTENT_TYPE));
304        
305        for (String contentTypeId : orgUnitContentTypes)
306        {
307            ContentType contentType = _contentTypeEP.getExtension(contentTypeId);
308            Page orgUnitRootPage = _orgUnitPageHandler.getOrganisationChartRootPage(page.getSiteName(), page.getSitemapName(), contentTypeId);
309            if (!contentType.isAbstract() && (orgUnitRootPage == null || orgUnitRootPage.equals(page)))
310            {
311                // The content type is not already a root of an organisation chart or is the root of the currently edited page
312                Map<String, Object> entry = new HashMap<>();
313                entry.put("value", contentType.getId());
314                entry.put("text", contentType.getLabel());
315                result.add(entry);
316            }
317        }
318        
319        return result;
320    }
321    
322    private void _removeOrgUnitRootProperty(Page page) throws RepositoryException
323    {
324        if (page instanceof JCRAmetysObject)
325        {
326            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
327            Node node = jcrPage.getNode();
328            
329            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
330            {
331                List<Value> values = new ArrayList<>(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()));
332                int index = values.stream()
333                        .map(LambdaUtils.wrap(Value::getString))
334                        .collect(Collectors.toList())
335                        .indexOf(VirtualOrganisationChartPageFactory.class.getName());
336                
337                if (index != -1)
338                {
339                    values.remove(index);
340                    node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()]));
341                }
342
343                // Remove the organization chart root property
344                if (page instanceof ModifiablePage)
345                {
346                    ModifiablePage modifiablePage = (ModifiablePage) page;
347                    modifiablePage.removeValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME);
348                }
349                jcrPage.saveChanges();
350            }
351        }
352    }
353    
354    private void _notifyPageUpdated(Page page)
355    {
356        Map<String, Object> eventParams = new HashMap<>();
357        eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
358        _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
359    }
360
361    
362    /**
363     * Gets information about organisation chart root status on the given.
364     * @param pageId The id of the page
365     * @return information about organisation chart root status on the given.
366     */
367    @Callable (rights = "User_Directory_Right_Organisation_Chart_SetRoot", rightContext = PageRightAssignmentContext.ID, paramIndex = 0)
368    public Map<String, Object> getRootPageInfo(String pageId)
369    {
370        Map<String, Object> result = new HashMap<>();
371        
372        Page page = _resolver.resolveById(pageId);
373        
374        if (page.hasValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME))
375        {
376            result.put("isRoot", true);
377            result.put("contentType", page.getValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME));
378        }
379        else
380        {
381            result.put("isRoot", false);
382        }
383        
384        return result;
385    }
386}