001/*
002 *  Copyright 2022 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.web.repository.page.jcr;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.List;
022import java.util.NoSuchElementException;
023import java.util.Optional;
024
025import javax.jcr.Node;
026
027import org.apache.commons.collections4.MapUtils;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030import org.xml.sax.ContentHandler;
031import org.xml.sax.SAXException;
032
033import org.ametys.plugins.repository.AmetysObject;
034import org.ametys.plugins.repository.AmetysObjectIterable;
035import org.ametys.plugins.repository.AmetysObjectIterator;
036import org.ametys.plugins.repository.AmetysRepositoryException;
037import org.ametys.plugins.repository.CollectionIterable;
038import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
039import org.ametys.plugins.repository.RepositoryConstants;
040import org.ametys.plugins.repository.TraversableAmetysObject;
041import org.ametys.plugins.repository.UnknownAmetysObjectException;
042import org.ametys.plugins.repository.data.ametysobject.ModifiableModelLessDataAwareAmetysObject;
043import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
044import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
045import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder;
046import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
047import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
048import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
049import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
050import org.ametys.runtime.model.View;
051import org.ametys.runtime.model.exception.NotUniqueTypeException;
052import org.ametys.runtime.model.exception.UnknownTypeException;
053import org.ametys.runtime.model.type.DataContext;
054import org.ametys.web.parameters.view.ViewParametersManager;
055import org.ametys.web.parameters.view.ViewParametersModel;
056import org.ametys.web.repository.page.MetadataAwareSitemapElement;
057import org.ametys.web.repository.page.ModifiableSitemapElement;
058import org.ametys.web.repository.page.ModifiableZone;
059import org.ametys.web.repository.page.Page;
060import org.ametys.web.repository.page.SitemapElement;
061import org.ametys.web.repository.page.UnknownZoneException;
062
063/**
064 * {@link SitemapElement} implementation stored into a JCR node.
065 * 
066 * @param <F> the actual type of factory.
067 */
068public abstract class AbstractSitemapElement<F extends AbstractSitemapElementFactory> extends DefaultTraversableAmetysObject<F> implements MetadataAwareSitemapElement, ModifiableSitemapElement, ModifiableModelLessDataAwareAmetysObject
069{
070    /** Constant for the zones node name. */
071    public static final String ZONES_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":zones";
072
073    /** Constant for the zones node type. */
074    public static final String ZONES_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":zones";
075
076    /** Constant for the zone node type. */
077    public static final String ZONE_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":zone";
078    
079    private static final Logger __LOGGER = LoggerFactory.getLogger(AbstractSitemapElement.class);
080
081    /**
082     * Creates an {@link AbstractSitemapElement}.
083     * 
084     * @param node the node backing this {@link AmetysObject}.
085     * @param parentPath the parent path in the Ametys hierarchy.
086     * @param factory the {@link DefaultPageFactory} which creates the
087     *            AmetysObject.
088     */
089    public AbstractSitemapElement(Node node, String parentPath, F factory)
090    {
091        super(node, parentPath, factory);
092    }
093    
094
095    @Override
096    public AmetysObjectIterable< ? extends Page> getChildrenPages() throws AmetysRepositoryException
097    {
098        return getChildrenPages(true);
099    }
100    
101    @Override
102    public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePage) throws AmetysRepositoryException
103    {
104        List<Page> childrenPages = new ArrayList<>();
105        for (AmetysObject childObject : getChildren())
106        {
107            if (childObject instanceof Page)
108            {
109                Page page = (Page) childObject;
110                if (includeInvisiblePage || page.isVisible())
111                {
112                    childrenPages.add(page);
113                }
114            }
115        }
116        return new CollectionIterable<>(childrenPages);
117    }
118    
119    @Override
120    public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException
121    {
122        if (index < 0)
123        {
124            throw new AmetysRepositoryException("Child page index cannot be negative");
125        }
126        
127        AmetysObjectIterable< ? extends Page> childPages = getChildrenPages();
128        AmetysObjectIterator< ? extends Page> it = childPages.iterator();
129        
130        try
131        {
132            it.skip(index);
133            return it.next();
134        }
135        catch (NoSuchElementException e)
136        {
137            throw new UnknownAmetysObjectException("There's no child page at index " + index + " for sitemap " + this.getId());
138        }
139    }
140    
141    @Override
142    public AmetysObjectIterable<ModifiableZone> getZones() throws AmetysRepositoryException
143    {
144        try
145        {
146            return ((TraversableAmetysObject) getChild(ZONES_NODE_NAME)).getChildren();
147        }
148        catch (UnknownAmetysObjectException e)
149        {
150            Collection<ModifiableZone> emptyCollection = Collections.emptyList();
151            return new CollectionIterable<>(emptyCollection);
152        }
153    }
154
155    @Override
156    public boolean hasZone(String name) throws AmetysRepositoryException
157    {
158        if (hasChild(ZONES_NODE_NAME))
159        {
160            return ((TraversableAmetysObject) getChild(ZONES_NODE_NAME)).hasChild(name);
161        }
162        else
163        {
164            return false;
165        }
166    }
167    
168    @Override
169    public ModifiableZone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
170    {
171        return ((TraversableAmetysObject) getChild(ZONES_NODE_NAME)).getChild(name);
172    }
173
174    @Override
175    public ModifiableZone createZone(String name) throws AmetysRepositoryException
176    {
177        ModifiableTraversableAmetysObject zones;
178        if (hasChild(ZONES_NODE_NAME))
179        {
180            zones = getChild(ZONES_NODE_NAME);
181        }
182        else
183        {
184            zones = createChild(ZONES_NODE_NAME, ZONES_NODE_TYPE);
185        }
186        
187        return zones.createChild(name, ZONE_NODE_TYPE); 
188    }
189
190    public ModifiableModelLessDataHolder getDataHolder()
191    {
192        ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode());
193        return new DefaultModifiableModelLessDataHolder(_getFactory().getPageDataTypeExtensionPoint(), repositoryData);
194    }
195    
196    @Override
197    public ModifiableCompositeMetadata getMetadataHolder()
198    {
199        __LOGGER.warn("The getMetadataHolder method of AbstractSitemapElement is deprecated. Use the getDataHolder instead. StackTrace: ", new Exception());
200        return super.getMetadataHolder();
201    }
202    
203    public ModifiableModelAwareDataHolder getTemplateParametersHolder() throws AmetysRepositoryException
204    {
205        JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode());
206        
207        // Get the template Id
208        String templateId = getTemplate();
209        
210        // Get the skin Id
211        String skinId = getSite().getSkinId();
212        
213        ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager();
214        Optional<ViewParametersModel> templateViewParameters = viewParametersManager.getTemplateViewParametersModel(skinId, templateId);
215        if (templateViewParameters.isEmpty())
216        {
217            templateViewParameters = Optional.of(new ViewParametersModel("template-no-param", new View(), MapUtils.EMPTY_SORTED_MAP));
218        }
219        
220        return viewParametersManager.getParametersHolder(zoneItemRepositoryData, templateViewParameters.get());
221    }
222    
223    /*
224     * The default implementation is overridden here to not use the getDataHolder()#dataToSAX implementation
225     * Each #dataToSAX method calls the next one and the implementation that has to change is the last one.
226     * If the getDataHolder() is used for one, the next ones will be called with the default implementation
227     */
228    public void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, UnknownTypeException, NotUniqueTypeException
229    {
230        for (String name : getDataNames())
231        {
232            dataToSAX(contentHandler, name, context);
233        }
234    }
235    
236    public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException
237    {
238        if (!ViewParametersManager.VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath))
239        {
240            ModifiableModelLessDataAwareAmetysObject.super.dataToSAX(contentHandler, dataPath, context);
241        }
242    }
243}