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.OdfPageHandler;
042import org.ametys.plugins.odfweb.repository.FirstLevelPageFactory;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.jcr.JCRAmetysObject;
045import org.ametys.plugins.repository.metadata.CompositeMetadata;
046import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
047import org.ametys.runtime.i18n.I18nizableText;
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, I18nizableText> 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.getMetadataHolder().getString(OdfPageHandler.CATALOG_METADATA_NAME, null);
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, I18nizableText> 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, I18nizableText> 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        else
178        {
179            properties.put("isOdfRootPage", true);
180            
181            CompositeMetadata metadataHolder = page.getMetadataHolder();
182            properties.put("catalog", metadataHolder.getString(OdfPageHandler.CATALOG_METADATA_NAME, ""));
183            properties.put("firstLevel", metadataHolder.getString(OdfPageHandler.LEVEL1_METADATA_NAME, ""));
184            properties.put("secondLevel", metadataHolder.getString(OdfPageHandler.LEVEL2_METADATA_NAME, ""));
185        }
186        
187        return properties;
188    }
189    
190    /**
191     * Add or update the odf root page property on a page
192     * @param pageId the page identifier
193     * @param catalog the catalog to set
194     * @param firstLevel the first level to set
195     * @param secondLevel the second level to set
196     * @throws RepositoryException if a repository error occurs
197     * @return true if the page has actually been modified
198     */
199    @Callable
200    public boolean addOrUpdateOdfRootProperty(String pageId, String catalog, String firstLevel, String secondLevel) throws RepositoryException
201    {
202        Page page = _resolver.resolveById(pageId);
203        Set<Page> currentODFPages = _odfPageHandler.getOdfRootPages(page.getSiteName(), page.getSitemapName());
204        
205        if (!currentODFPages.contains(page))
206        {
207            return _addOdfRootProperty(page, catalog, firstLevel, secondLevel);
208        }
209        else
210        {
211            return _updateOdfRootProperty(page, catalog, firstLevel, secondLevel);
212        }
213    }
214    
215    private boolean _addOdfRootProperty(Page page, String catalog, String firstLevel, String secondLevel) throws RepositoryException
216    {
217        boolean hasChanges = false;
218        
219        if (page instanceof JCRAmetysObject)
220        {
221            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
222            
223            // Add odf root property
224            Property virtualProperty = _getVirtualProperty(jcrPage);
225            Value[] oldValues = virtualProperty != null ? virtualProperty.getValues() : new Value[]{};
226            
227            boolean hasVirtualProperty = Arrays.stream(oldValues)
228                    .anyMatch(LambdaUtils.wrapPredicate(ODFRootClientSideElement::isVirtualPageFactoryValue));
229            
230            if (!hasVirtualProperty)
231            {
232                Value[] newValues = ObjectArrays.concat(oldValues, new StringValue(FirstLevelPageFactory.class.getName()));
233                _setVirtualProperty(jcrPage, newValues);
234            }
235            
236            ModifiableCompositeMetadata pageMeta = jcrPage.getMetadataHolder();
237            
238            // Set the ODF catalog property
239            if (StringUtils.isNotEmpty(catalog))
240            {
241                pageMeta.setMetadata(OdfPageHandler.CATALOG_METADATA_NAME, catalog);
242            }
243            
244            // Set the level properties.
245            if (StringUtils.isNotEmpty(firstLevel))
246            {
247                pageMeta.setMetadata(OdfPageHandler.LEVEL1_METADATA_NAME, firstLevel);
248            }
249            if (StringUtils.isNotEmpty(secondLevel))
250            {
251                pageMeta.setMetadata(OdfPageHandler.LEVEL2_METADATA_NAME, secondLevel);
252            }
253            
254            if (jcrPage.needsSave())
255            {
256                jcrPage.saveChanges();
257                hasChanges = true;
258            }
259        }
260        
261        if (hasChanges)
262        {
263            _notifyOdfRootPageChange(page);
264        }
265        
266        return hasChanges;
267    }
268    
269    private boolean _updateOdfRootProperty(Page page, String catalog, String firstLevel, String secondLevel)
270    {
271        boolean hasChanges = false;
272        
273        if (page instanceof ModifiablePage)
274        {
275            ModifiablePage modifiablePage = (ModifiablePage) page;
276            ModifiableCompositeMetadata pageMeta = modifiablePage.getMetadataHolder();
277            
278            // Set the ODF catalog property
279            if (StringUtils.isNotEmpty(catalog))
280            {
281                pageMeta.setMetadata(OdfPageHandler.CATALOG_METADATA_NAME, catalog);
282            }
283            
284            // Set the level properties.
285            if (StringUtils.isNotEmpty(firstLevel))
286            {
287                pageMeta.setMetadata(OdfPageHandler.LEVEL1_METADATA_NAME, firstLevel);
288            }
289            if (StringUtils.isNotEmpty(secondLevel))
290            {
291                pageMeta.setMetadata(OdfPageHandler.LEVEL2_METADATA_NAME, secondLevel);
292            }
293            
294            if (modifiablePage.needsSave())
295            {
296                modifiablePage.saveChanges();
297                hasChanges = true;
298            }
299        }
300        
301        if (hasChanges)
302        {
303            // Odf root updated observer
304            Map<String, Object> eventParams = new HashMap<>();
305            eventParams.put(ObservationConstants.ARGS_PAGE, page);
306            _observationManager.notify(new Event(OdfWebObservationConstants.ODF_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams));
307            
308            _notifyOdfRootPageChange(page);
309        }
310        
311        return hasChanges;
312    }
313    
314    /**
315     * Remove the odf root page property on a page
316     * @param pageId the page identifier
317     * @return true if the property has actually been removed
318     * @throws RepositoryException if a repository error occurs
319     */
320    @Callable
321    public boolean removeOdfRootPage(String pageId) throws RepositoryException
322    {
323        boolean hasChanges = false;
324        Page page = _resolver.resolveById(pageId);
325        
326        if (page instanceof JCRAmetysObject)
327        {
328            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
329            Property virtualProperty = _getVirtualProperty(jcrPage);
330            
331            if (virtualProperty != null)
332            {
333                Value[] oldValues = virtualProperty.getValues();
334                
335                // remove the virtual page property
336                Value[] newValues = Arrays.stream(oldValues)
337                    .filter(LambdaUtils.wrapPredicate(ODFRootClientSideElement::isVirtualPageFactoryValue).negate())
338                    .toArray(Value[]::new);
339                
340                _setVirtualProperty(jcrPage, newValues);
341                
342                // Remove the level properties.
343                ModifiableCompositeMetadata pageMeta = jcrPage.getMetadataHolder();
344                
345                if (pageMeta.hasMetadata(OdfPageHandler.LEVEL1_METADATA_NAME))
346                {
347                    pageMeta.removeMetadata(OdfPageHandler.LEVEL1_METADATA_NAME);
348                }
349                if (pageMeta.hasMetadata(OdfPageHandler.LEVEL2_METADATA_NAME))
350                {
351                    pageMeta.removeMetadata(OdfPageHandler.LEVEL2_METADATA_NAME);
352                }
353                if (pageMeta.hasMetadata(OdfPageHandler.CATALOG_METADATA_NAME))
354                {
355                    pageMeta.removeMetadata(OdfPageHandler.CATALOG_METADATA_NAME);
356                }
357                
358                if (jcrPage.needsSave())
359                {
360                    jcrPage.saveChanges();
361                    hasChanges = true;
362                }
363            }
364        }
365        
366        if (hasChanges)
367        {
368            // Odf root deleted observer
369            Map<String, Object> eventParams = new HashMap<>();
370            eventParams.put(ObservationConstants.ARGS_PAGE, page);
371            _observationManager.notify(new Event(OdfWebObservationConstants.ODF_ROOT_DELETED, _currentUserProvider.getUser(), eventParams));
372            
373            _notifyOdfRootPageChange(page);
374        }
375        
376        return hasChanges;
377    }
378    
379    private void _notifyOdfRootPageChange(Page page)
380    {
381        // clear root cache
382        _odfPageHandler.clearRootCache();
383        
384        // Page changed observer
385        Map<String, Object> eventParams = new HashMap<>();
386        eventParams.put(ObservationConstants.ARGS_PAGE, page);
387        _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
388    }
389    
390    private Property _getVirtualProperty(JCRAmetysObject jcrPage) throws RepositoryException
391    {
392        Node node = jcrPage.getNode();
393        
394        if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
395        {
396            return node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY);
397        }
398        
399        return null;
400    }
401    
402    private void _setVirtualProperty(JCRAmetysObject jcrPage, Value[] values) throws RepositoryException
403    {
404        jcrPage.getNode().setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values);
405    }
406    
407    /**
408     * Tests if a JCR Value represents the virtual page property
409     * @param value the value to test
410     * @return true if it is the case
411     * @throws RepositoryException if a repository error occurs
412     */
413    private static boolean isVirtualPageFactoryValue(Value value) throws RepositoryException 
414    {
415        return StringUtils.equals(value.getString(), FirstLevelPageFactory.class.getName());
416    }
417}