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