001/*
002 *  Copyright 2010 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.odfweb.clientside;
017
018import java.util.Arrays;
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Set;
022
023import javax.jcr.Node;
024import javax.jcr.Property;
025import javax.jcr.RepositoryException;
026import javax.jcr.Value;
027
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.lang3.Strings;
032import org.apache.jackrabbit.value.StringValue;
033
034import org.ametys.core.observation.Event;
035import org.ametys.core.observation.ObservationManager;
036import org.ametys.core.ui.Callable;
037import org.ametys.core.ui.StaticClientSideElement;
038import org.ametys.core.util.LambdaUtils;
039import org.ametys.odf.catalog.Catalog;
040import org.ametys.odf.catalog.CatalogsManager;
041import org.ametys.plugins.odfweb.OdfWebObservationConstants;
042import org.ametys.plugins.odfweb.repository.AbstractOdfPage;
043import org.ametys.plugins.odfweb.repository.FirstLevelPageFactory;
044import org.ametys.plugins.odfweb.repository.OdfPageHandler;
045import org.ametys.plugins.repository.AmetysObjectResolver;
046import org.ametys.plugins.repository.jcr.JCRAmetysObject;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.runtime.i18n.I18nizableTextParameter;
049import org.ametys.web.ObservationConstants;
050import org.ametys.web.repository.page.ModifiablePage;
051import org.ametys.web.repository.page.Page;
052
053import com.google.common.collect.ObjectArrays;
054
055/**
056 * This element creates an action button to set the ODF root page
057 */
058public class ODFRootClientSideElement extends StaticClientSideElement
059{
060    /** Ametys resolver */
061    protected AmetysObjectResolver _resolver;
062    /** Catalog manager */
063    protected CatalogsManager _catalogsManager;
064    /** Odf page handler */
065    protected OdfPageHandler _odfPageHandler;
066    /** Observation manager */
067    protected ObservationManager _observationManager;
068    
069    @Override
070    public void service(ServiceManager smanager) throws ServiceException
071    {
072        super.service(smanager);
073        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
074        _catalogsManager = (CatalogsManager) smanager.lookup(CatalogsManager.ROLE);
075        _odfPageHandler = (OdfPageHandler) smanager.lookup(OdfPageHandler.ROLE);
076        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
077    }
078    
079    /**
080     * Get odf root page status for the current page
081     * @param pageId the page identifier
082     * @return A map representing its status
083     */
084    @Callable(rights = "ODF_Rights_SetRootPage")
085    public Map<String, Object> getStatus(String pageId)
086    {
087        Map<String, Object> results = new HashMap<>();
088        Page page = _resolver.resolveById(pageId);
089        
090        // page id
091        results.put("page-id", page.getId());
092        
093        if (page instanceof JCRAmetysObject)
094        {
095            if (_odfPageHandler.isODFRootPage(page))
096            {
097                results.put("is-odf-root", true);
098                
099                // Odf root page description
100                I18nizableText rawDescription = (I18nizableText) this._script.getParameters().get("odf-root-page-description");
101                Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
102                i18nParams.put("title", new I18nizableText(page.getTitle()));
103                
104                I18nizableText description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams);
105                results.put("odf-root-page-description", description);
106                
107                // remove odf root
108                rawDescription = (I18nizableText) this._script.getParameters().get("remove-odf-page-description");
109                description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams);
110                results.put("remove-odf-page-description", description);
111                
112                // Catalog Odf root page descrption
113                String catalogCode = page.getValue(OdfPageHandler.CATALOG_DATA_NAME);
114                if (StringUtils.isNotEmpty(catalogCode))
115                {
116                    Catalog catalog = _catalogsManager.getCatalog(catalogCode);
117                    I18nizableText catalogText = new I18nizableText(catalog != null ? catalog.getTitle() : catalogCode);
118                    
119                    i18nParams = new HashMap<>();
120                    i18nParams.put("catalog", catalogText);
121                    
122                    rawDescription = (I18nizableText) this._script.getParameters().get("catalog-description");
123                    description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams);
124                    results.put("catalog-description", description);
125                }
126            }
127            else
128            {
129                // Add odf root page
130                results.put("add-odf-page", true);
131                
132                I18nizableText rawDescription = (I18nizableText) this._script.getParameters().get("add-odf-page-description");
133                Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
134                i18nParams.put("title", new I18nizableText(page.getTitle()));
135                
136                I18nizableText description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams);
137                results.put("add-odf-page-description", description);
138            }
139        }
140        else
141        {
142            // Invalid page
143            results.put("invalid-page", true);
144            
145            I18nizableText rawDescription = (I18nizableText) this._script.getParameters().get("no-jcr-page-description");
146            Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
147            i18nParams.put("title", new I18nizableText(page.getTitle()));
148            
149            I18nizableText description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams);
150            results.put("no-jcr-page-description", description);
151        }
152        
153        return results;
154    }
155    
156    /**
157     * Retrieves odf root page properties
158     * @param pageId page identifier
159     * @return the map of properties
160     */
161    @Callable(rights = "ODF_Rights_SetRootPage")
162    public Map<String, Object> getOdfRootPageInfo(String pageId)
163    {
164        Map<String, Object> properties = new HashMap<>();
165        
166        Page page = _resolver.resolveById(pageId);
167        Set<Page> currentODFPages = _odfPageHandler.getOdfRootPages(page.getSiteName(), page.getSitemapName());
168                
169        if (_catalogsManager.getCatalogs().isEmpty())
170        {
171            properties.put("noCatalog", true);
172        }
173        
174        if (!currentODFPages.contains(page))
175        {
176            properties.put("isOdfRootPage", false);
177            
178            properties.put("areChildrenIndexable", true);
179        }
180        else
181        {
182            properties.put("isOdfRootPage", true);
183            
184            properties.put("catalog", page.getValueOrDefault(OdfPageHandler.CATALOG_DATA_NAME, StringUtils.EMPTY));
185            properties.put("firstLevel", page.getValueOrDefault(OdfPageHandler.LEVEL1_ATTRIBUTE_NAME, StringUtils.EMPTY));
186            properties.put("secondLevel", page.getValueOrDefault(OdfPageHandler.LEVEL2_ATTRIBUTE_NAME, StringUtils.EMPTY));
187            properties.put("areChildrenIndexable", page.getValueOrDefault(AbstractOdfPage.INDEXABLE_CHILDREN, true));
188        }
189        
190        return properties;
191    }
192    
193    /**
194     * Add or update the odf root page property on a page
195     * @param pageId the page identifier
196     * @param catalog the catalog to set
197     * @param firstLevel the first level to set
198     * @param secondLevel the second level to set
199     * @param areChildrenIndexable set to <code>true</code> to index children pages
200     * @throws RepositoryException if a repository error occurs
201     * @return true if the page has actually been modified
202     */
203    @Callable(rights = "ODF_Rights_SetRootPage")
204    public boolean addOrUpdateOdfRootProperty(String pageId, String catalog, String firstLevel, String secondLevel, boolean areChildrenIndexable) throws RepositoryException
205    {
206        Page page = _resolver.resolveById(pageId);
207        Set<Page> currentODFPages = _odfPageHandler.getOdfRootPages(page.getSiteName(), page.getSitemapName());
208        
209        if (!currentODFPages.contains(page))
210        {
211            return _addOdfRootProperty(page, catalog, firstLevel, secondLevel, areChildrenIndexable);
212        }
213        else
214        {
215            return _updateOdfRootProperty(page, catalog, firstLevel, secondLevel, areChildrenIndexable);
216        }
217    }
218    
219    private boolean _addOdfRootProperty(Page page, String catalog, String firstLevel, String secondLevel, boolean areChildrenIndexable) throws RepositoryException
220    {
221        boolean hasChanges = false;
222        
223        if (page instanceof JCRAmetysObject)
224        {
225            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
226            
227            // Add odf root property
228            Property virtualProperty = _getVirtualProperty(jcrPage);
229            Value[] oldValues = virtualProperty != null ? virtualProperty.getValues() : new Value[]{};
230            
231            boolean hasVirtualProperty = Arrays.stream(oldValues)
232                    .anyMatch(LambdaUtils.wrapPredicate(ODFRootClientSideElement::isVirtualPageFactoryValue));
233            
234            if (!hasVirtualProperty)
235            {
236                Value[] newValues = ObjectArrays.concat(oldValues, new StringValue(FirstLevelPageFactory.class.getName()));
237                _setVirtualProperty(jcrPage, newValues);
238            }
239            
240            hasChanges = _updateOdfRootProperty(page, catalog, firstLevel, secondLevel, areChildrenIndexable);
241        }
242        
243        if (hasChanges)
244        {
245            _notifyOdfRootPageChange(page);
246        }
247        
248        return hasChanges;
249    }
250    
251    private boolean _updateOdfRootProperty(Page page, String catalog, String firstLevel, String secondLevel, boolean areChildrenIndexable)
252    {
253        boolean hasChanges = _setOdfRootAttributes(page, catalog, firstLevel, secondLevel, areChildrenIndexable);
254        
255        if (hasChanges)
256        {
257            // Odf root updated observer
258            Map<String, Object> eventParams = new HashMap<>();
259            eventParams.put(ObservationConstants.ARGS_PAGE, page);
260            _observationManager.notify(new Event(OdfWebObservationConstants.ODF_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams));
261            
262            _notifyOdfRootPageChange(page);
263        }
264        
265        return hasChanges;
266    }
267    
268    private boolean _setOdfRootAttributes(Page page, String catalog, String firstLevel, String secondLevel, boolean areChildrenIndexable)
269    {
270        boolean hasChanges = false;
271        
272        if (page instanceof ModifiablePage modifiablePage)
273        {
274            // Set the ODF catalog property
275            if (StringUtils.isNotEmpty(catalog))
276            {
277                modifiablePage.setValue(OdfPageHandler.CATALOG_DATA_NAME, catalog);
278            }
279            
280            // Set the level properties.
281            if (StringUtils.isNotEmpty(firstLevel))
282            {
283                modifiablePage.setValue(OdfPageHandler.LEVEL1_ATTRIBUTE_NAME, firstLevel);
284            }
285            else
286            {
287                modifiablePage.removeValue(OdfPageHandler.LEVEL1_ATTRIBUTE_NAME); //TODO level 1 mandatory ??
288            }
289            
290            if (StringUtils.isNotEmpty(secondLevel))
291            {
292                modifiablePage.setValue(OdfPageHandler.LEVEL2_ATTRIBUTE_NAME, secondLevel);
293            }
294            else
295            {
296                modifiablePage.removeValue(OdfPageHandler.LEVEL2_ATTRIBUTE_NAME);
297            }
298            
299            modifiablePage.setValue(AbstractOdfPage.INDEXABLE_CHILDREN, areChildrenIndexable);
300            
301            if (modifiablePage.needsSave())
302            {
303                modifiablePage.saveChanges();
304                hasChanges = true;
305            }
306        }
307        
308        return hasChanges;
309    }
310    
311    /**
312     * Remove the odf root page property on a page
313     * @param pageId the page identifier
314     * @return true if the property has actually been removed
315     * @throws RepositoryException if a repository error occurs
316     */
317    @Callable(rights = "ODF_Rights_SetRootPage")
318    public boolean removeOdfRootPage(String pageId) throws RepositoryException
319    {
320        boolean hasChanges = false;
321        Page page = _resolver.resolveById(pageId);
322        
323        if (page instanceof JCRAmetysObject)
324        {
325            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
326            Property virtualProperty = _getVirtualProperty(jcrPage);
327            
328            if (virtualProperty != null)
329            {
330                Value[] oldValues = virtualProperty.getValues();
331                
332                // remove the virtual page property
333                Value[] newValues = Arrays.stream(oldValues)
334                    .filter(LambdaUtils.wrapPredicate(ODFRootClientSideElement::isVirtualPageFactoryValue).negate())
335                    .toArray(Value[]::new);
336                
337                _setVirtualProperty(jcrPage, newValues);
338                
339                // Remove the level properties.
340                ModifiablePage modifiablePage = (ModifiablePage) page;
341                modifiablePage.removeValue(OdfPageHandler.LEVEL1_ATTRIBUTE_NAME);
342                modifiablePage.removeValue(OdfPageHandler.LEVEL2_ATTRIBUTE_NAME);
343                modifiablePage.removeValue(OdfPageHandler.CATALOG_DATA_NAME);
344                modifiablePage.removeValue(AbstractOdfPage.INDEXABLE_CHILDREN);
345                
346                if (jcrPage.needsSave())
347                {
348                    jcrPage.saveChanges();
349                    hasChanges = true;
350                }
351            }
352        }
353        
354        if (hasChanges)
355        {
356            // Odf root deleted observer
357            Map<String, Object> eventParams = new HashMap<>();
358            eventParams.put(ObservationConstants.ARGS_PAGE, page);
359            _observationManager.notify(new Event(OdfWebObservationConstants.ODF_ROOT_DELETED, _currentUserProvider.getUser(), eventParams));
360            
361            _notifyOdfRootPageChange(page);
362        }
363        
364        return hasChanges;
365    }
366    
367    private void _notifyOdfRootPageChange(Page page)
368    {
369        // clear root cache
370        _odfPageHandler.clearRootCache();
371        
372        // Page changed observer
373        Map<String, Object> eventParams = new HashMap<>();
374        eventParams.put(ObservationConstants.ARGS_PAGE, page);
375        _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
376    }
377    
378    private Property _getVirtualProperty(JCRAmetysObject jcrPage) throws RepositoryException
379    {
380        Node node = jcrPage.getNode();
381        
382        if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
383        {
384            return node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY);
385        }
386        
387        return null;
388    }
389    
390    private void _setVirtualProperty(JCRAmetysObject jcrPage, Value[] values) throws RepositoryException
391    {
392        jcrPage.getNode().setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values);
393    }
394    
395    /**
396     * Tests if a JCR Value represents the virtual page property
397     * @param value the value to test
398     * @return true if it is the case
399     * @throws RepositoryException if a repository error occurs
400     */
401    private static boolean isVirtualPageFactoryValue(Value value) throws RepositoryException
402    {
403        return Strings.CS.equals(value.getString(), FirstLevelPageFactory.class.getName());
404    }
405}