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