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