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        Set<Page> currentUserDirectoryPages = _userDirectoryPageHandler.getUserDirectoryRootPages(page.getSiteName(), page.getSitemapName());
223        
224        Map<String, Object> eventParams = new HashMap<>();
225        eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
226        
227        if (currentUserDirectoryPages.contains(page))
228        {
229            // Unindex pages for all workspaces before the properties changed 
230            _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_UPDATING, _currentUserProvider.getUser(), eventParams));
231            
232            _updateUserDirectoryRootProperty(page, contentType, metadata, depth);
233        }
234        else
235        {
236            _addUserDirectoryRootProperty(page, contentType, metadata, depth);
237        }
238        
239        _userDirectoryPageHandler.clearCache(page);
240        
241        // Live synchronization
242        _notifyPageUpdated(page);
243        
244        // Indexation
245        _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams));
246        return result;
247    }
248    
249    /**
250     * Remove the user directory root status to the given page
251     * @param pageId The id of the page
252     * @return A result map
253     * @throws RepositoryException if a repository error occured
254     */
255    @Callable
256    public Map<String, Object> removeUserDirectoryRoot(String pageId) throws RepositoryException
257    {
258        Map<String, Object> result = new HashMap<>();
259        
260        Page page = _resolver.resolveById(pageId);
261        
262        if (page instanceof JCRAmetysObject)
263        {
264            if (!_isUserDirectoryRootPage((JCRAmetysObject) page))
265            {
266                result.put("error", "no-root");
267                return result;
268            }
269            
270            Map<String, Object> eventParams = new HashMap<>();
271            eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
272            
273            // Unindex pages for all workspaces before the properties were removed 
274            _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_DELETING, _currentUserProvider.getUser(), eventParams));
275            
276            _userDirectoryPageHandler.clearCache(page);
277            _removeUserDirectoryRootProperty(page);
278            
279            _notifyPageUpdated(page);
280            
281            // After live synchronization
282            _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_DELETED, _currentUserProvider.getUser(), eventParams));
283        }
284        else
285        {
286            result.put("error", "no-root");
287        }
288        return result;
289    }
290    
291    /**
292     * Gets information about user directory root status on the given.
293     * @param pageId The id of the page
294     * @return information about user directory root status on the given.
295     */
296    @Callable
297    public Map<String, Object> getRootPageInfo(String pageId)
298    {
299        Map<String, Object> result = new HashMap<>();
300        
301        Page page = _resolver.resolveById(pageId);
302        Set<Page> currentUserDirectoryPages = _userDirectoryPageHandler.getUserDirectoryRootPages(page.getSiteName(), page.getSitemapName());
303        
304        if (currentUserDirectoryPages.contains(page))
305        {
306            result.put("isRoot", true);
307            result.put("contentType", page.getValue(UserDirectoryPageHandler.CONTENT_TYPE_METADATA_NAME));
308            result.put("metadata", page.getValue(UserDirectoryPageHandler.CLASSIFICATION_METADATA_METADATA_NAME));
309            result.put("depth", page.getValue(UserDirectoryPageHandler.DEPTH_METADATA_NAME));
310        }
311        else
312        {
313            result.put("isRoot", false);
314        }
315        
316        return result;
317    }
318    
319    private void _addUserDirectoryRootProperty(Page page, String contentType, String metadata, int depth) throws RepositoryException
320    {
321        if (page instanceof JCRAmetysObject)
322        {
323            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
324            Node node = jcrPage.getNode();
325            
326            List<Value> values = new ArrayList<>();
327            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
328            {
329                values.addAll(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()));
330            }
331            
332            StringValue virtualUserDirectoryPageFactoryClassName = new StringValue(VirtualUserDirectoryPageFactory.class.getName());
333            if (!values.contains(virtualUserDirectoryPageFactoryClassName))
334            {
335                values.add(virtualUserDirectoryPageFactoryClassName);
336            }
337            
338            node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()]));
339
340            // Set the user directory root property
341            if (page instanceof ModifiablePage)
342            {
343                ModifiablePage modifiablePage = (ModifiablePage) page;
344                modifiablePage.setValue(UserDirectoryPageHandler.CONTENT_TYPE_METADATA_NAME, contentType);
345                modifiablePage.setValue(UserDirectoryPageHandler.CLASSIFICATION_METADATA_METADATA_NAME, metadata);
346                modifiablePage.setValue(UserDirectoryPageHandler.DEPTH_METADATA_NAME, depth);
347            }
348            
349            jcrPage.saveChanges();
350        }
351    }
352    
353    private void _updateUserDirectoryRootProperty(Page page, String contentType, String metadata, int depth)
354    {
355        if (page instanceof ModifiablePage)
356        {
357            ModifiablePage modifiablePage = (ModifiablePage) page;
358            
359            // Set the user directory root property
360            modifiablePage.setValue(UserDirectoryPageHandler.CONTENT_TYPE_METADATA_NAME, contentType);
361            modifiablePage.setValue(UserDirectoryPageHandler.CLASSIFICATION_METADATA_METADATA_NAME, metadata);
362            modifiablePage.setValue(UserDirectoryPageHandler.DEPTH_METADATA_NAME, depth);
363            
364            modifiablePage.saveChanges();
365        }
366    }
367    
368    private void _removeUserDirectoryRootProperty(Page page) throws RepositoryException
369    {
370        if (page instanceof JCRAmetysObject)
371        {
372            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
373            Node node = jcrPage.getNode();
374            
375            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
376            {
377                List<Value> values = new ArrayList<>(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()));
378                int index = values.stream()
379                        .map(LambdaUtils.wrap(Value::getString))
380                        .collect(Collectors.toList())
381                        .indexOf(VirtualUserDirectoryPageFactory.class.getName());
382                if (index != -1)
383                {
384                    values.remove(index);
385                    node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()]));
386                }
387
388                // Remove the user directory root property
389                if (page instanceof ModifiablePage)
390                {
391                    ModifiablePage modifiablePage = (ModifiablePage) page;
392                    if (modifiablePage.hasValue(UserDirectoryPageHandler.CONTENT_TYPE_METADATA_NAME))
393                    {
394                        modifiablePage.removeValue(UserDirectoryPageHandler.CONTENT_TYPE_METADATA_NAME);
395                    }
396                    if (modifiablePage.hasValue(UserDirectoryPageHandler.CLASSIFICATION_METADATA_METADATA_NAME))
397                    {
398                        modifiablePage.removeValue(UserDirectoryPageHandler.CLASSIFICATION_METADATA_METADATA_NAME);
399                    }
400                    if (modifiablePage.hasValue(UserDirectoryPageHandler.DEPTH_METADATA_NAME))
401                    {
402                        modifiablePage.removeValue(UserDirectoryPageHandler.DEPTH_METADATA_NAME);
403                    }
404                }
405                
406                jcrPage.saveChanges();
407            }
408        }
409    }
410    
411    private void _notifyPageUpdated(Page page)
412    {
413        Map<String, Object> eventParams = new HashMap<>();
414        eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
415        _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
416    }
417}