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