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.web.repository.page.jcr;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashSet;
022import java.util.List;
023import java.util.NoSuchElementException;
024import java.util.Optional;
025import java.util.Set;
026
027import javax.jcr.ItemExistsException;
028import javax.jcr.Node;
029import javax.jcr.RepositoryException;
030import javax.jcr.Value;
031
032import org.apache.commons.collections4.MapUtils;
033import org.apache.commons.lang.StringUtils;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036import org.xml.sax.ContentHandler;
037import org.xml.sax.SAXException;
038
039import org.ametys.plugins.explorer.resources.ResourceCollection;
040import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory;
041import org.ametys.plugins.repository.AmetysObject;
042import org.ametys.plugins.repository.AmetysObjectIterable;
043import org.ametys.plugins.repository.AmetysObjectIterator;
044import org.ametys.plugins.repository.AmetysRepositoryException;
045import org.ametys.plugins.repository.CollectionIterable;
046import org.ametys.plugins.repository.CopiableAmetysObject;
047import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
048import org.ametys.plugins.repository.RepositoryConstants;
049import org.ametys.plugins.repository.RepositoryIntegrityViolationException;
050import org.ametys.plugins.repository.TraversableAmetysObject;
051import org.ametys.plugins.repository.UnknownAmetysObjectException;
052import org.ametys.plugins.repository.data.holder.ModifiableDataHolder;
053import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
054import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
055import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper;
056import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder;
057import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
058import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
059import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
060import org.ametys.plugins.repository.jcr.SimpleAmetysObject;
061import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
062import org.ametys.plugins.repository.tag.TaggableAmetysObjectHelper;
063import org.ametys.runtime.model.View;
064import org.ametys.runtime.model.exception.BadItemTypeException;
065import org.ametys.runtime.model.exception.NotUniqueTypeException;
066import org.ametys.runtime.model.exception.UndefinedItemPathException;
067import org.ametys.runtime.model.exception.UnknownTypeException;
068import org.ametys.runtime.model.type.DataContext;
069import org.ametys.runtime.model.type.ModelItemTypeConstants;
070import org.ametys.web.parameters.view.ViewParametersManager;
071import org.ametys.web.parameters.view.ViewParametersModel;
072import org.ametys.web.repository.page.ModifiablePage;
073import org.ametys.web.repository.page.ModifiableZone;
074import org.ametys.web.repository.page.MoveablePage;
075import org.ametys.web.repository.page.Page;
076import org.ametys.web.repository.page.PagesContainer;
077import org.ametys.web.repository.page.UnknownZoneException;
078import org.ametys.web.repository.site.Site;
079import org.ametys.web.repository.sitemap.Sitemap;
080
081/**
082 * {@link Page} implementation stored into a JCR node using
083 * <code>ametys:defaultPage</code> node type.
084 * 
085 * @param <F> the actual type of factory.
086 */
087public class DefaultPage<F extends DefaultPageFactory> extends DefaultTraversableAmetysObject<F> implements ModifiablePage, CopiableAmetysObject, MoveablePage
088{
089    /** Constant for title metadata. */
090    public static final String METADATA_TITLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":title";
091
092    /** Constant for long title metadata. */
093    public static final String METADATA_LONG_TITLE = "long-title";
094
095    /** Constant for title metadata. */
096    public static final String METADATA_TYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":type";
097
098    /** Constant for title metadata. */
099    public static final String METADATA_URL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url";
100
101    /** Constant for title metadata. */
102    public static final String METADATA_URLTYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url-type";
103
104    /** Constant for template metadata. */
105    public static final String METADATA_TEMPLATE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":template";
106
107    /** Constant for referers metadata. */
108    public static final String METADATA_REFERERS = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":referers";
109    
110    /** Constant for the visible attribute. */
111    public static final String METADATA_VISIBLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":visible";
112
113    /** Constant for robots metadata. */
114    public static final String METADATA_ROBOTS_DISALLOW = "robots-disallow";
115    
116    /** Constant for the zones node name. */
117    public static final String ZONES_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":zones";
118
119    /** Constant for the zones node type. */
120    public static final String ZONES_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":zones";
121
122    /** Constant for the zone node type. */
123    public static final String ZONE_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":zone";
124    
125    /** Constant for the attachment node name. */
126    public static final String ATTACHMENTS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":attachments";
127    
128    /** Constants for site Metadata* */
129    public static final String METADATA_SITE = "site";
130    
131    /** Constants for sitemap Metadata* */
132    public static final String METADATA_SITEMAP = "sitemap";
133
134    /** Constant for publication start date metadata. */
135    public static final String METADATA_PUBLICATION_START_DATE = "publicationStartDate";
136    
137    /** Constant for publication end date metadata. */
138    public static final String METADATA_PUBLICATION_END_DATE = "publicationEndDate";
139    
140    private static final Logger __LOGGER = LoggerFactory.getLogger(DefaultPage.class);
141    
142    /**
143     * Creates a {@link DefaultPage}.
144     * 
145     * @param node the node backing this {@link AmetysObject}.
146     * @param parentPath the parent path in the Ametys hierarchy.
147     * @param factory the {@link DefaultPageFactory} which creates the
148     *            AmetysObject.
149     */
150    public DefaultPage(Node node, String parentPath, F factory)
151    {
152        super(node, parentPath, factory);
153    }
154
155    @Override
156    public Set<String> getTags() throws AmetysRepositoryException
157    {
158        return TaggableAmetysObjectHelper.getTags(this);
159    }
160    
161    @Override
162    public void tag(String tag) throws AmetysRepositoryException
163    {
164        TaggableAmetysObjectHelper.tag(this, tag);
165    }
166
167    @Override
168    public void untag(String tag) throws AmetysRepositoryException
169    {
170        TaggableAmetysObjectHelper.untag(this, tag);
171    }
172
173    @Override
174    public String getTitle() throws AmetysRepositoryException
175    {
176        try
177        {
178            return getNode().getProperty(METADATA_TITLE).getString();
179        }
180        catch (RepositoryException e)
181        {
182            throw new AmetysRepositoryException("Unable to get title property", e);
183        }
184    }
185
186    @Override
187    public void setTitle(String title) throws AmetysRepositoryException
188    {
189        try
190        {
191            getNode().setProperty(METADATA_TITLE, title);
192        }
193        catch (RepositoryException e)
194        {
195            throw new AmetysRepositoryException("Unable to set title property", e);
196        }
197    }
198
199    @Override
200    public String getLongTitle() throws AmetysRepositoryException
201    {
202        String longTitle = getValue(METADATA_LONG_TITLE);
203        if (StringUtils.isNotBlank(longTitle))
204        {
205            return longTitle;
206        }
207        else
208        {
209            return this.getTitle();
210        }
211    }
212
213    @Override
214    public void setLongTitle(String title) throws AmetysRepositoryException
215    {
216        String newTitle = StringUtils.trimToNull(title);
217        setValue(METADATA_LONG_TITLE, newTitle, ModelItemTypeConstants.STRING_TYPE_ID);
218    }
219
220    @Override
221    public Sitemap getSitemap() throws AmetysRepositoryException
222    {
223        try
224        {
225            Node node = getNode();
226
227            do
228            {
229                node = node.getParent();
230            }
231            while (!node.getPrimaryNodeType().getName().equals(Sitemap.NODE_TYPE));
232
233            return _getFactory().resolveAmetysObject(node);
234        }
235        catch (RepositoryException e)
236        {
237            throw new AmetysRepositoryException("Unable to get sitemap", e);
238        }
239    }
240    
241    @Override
242    public int getDepth() throws AmetysRepositoryException
243    {
244        try
245        {
246            int depth = 1;
247            Node node = getNode().getParent();
248            while (!node.getPrimaryNodeType().getName().equals(Sitemap.NODE_TYPE))
249            {
250                depth++;
251                node = node.getParent();
252            }
253
254            return depth;
255        }
256        catch (RepositoryException e)
257        {
258            throw new AmetysRepositoryException("Unable to get depth", e);
259        }
260    }
261
262    @Override
263    public Site getSite() throws AmetysRepositoryException
264    {
265        return getSitemap().getSite();
266    }
267    
268    @Override
269    public String getSiteName()
270    {
271        return getValue(METADATA_SITE);
272    }
273    
274    @Override
275    public void setSiteName(String siteName)
276    {
277        setValue(METADATA_SITE, siteName);
278    }
279    
280    @Override
281    public String getSitemapName() throws AmetysRepositoryException
282    {
283        return getValue(METADATA_SITEMAP);
284    }
285    
286    @Override
287    public void setSitemapName(String sitemapName) throws AmetysRepositoryException
288    {
289        setValue(METADATA_SITEMAP, sitemapName);
290    }
291
292    @Override
293    public String getPathInSitemap() throws AmetysRepositoryException
294    {
295        String nodePath = getPath();
296        String sitemapPath = getSitemap().getPath();
297        return nodePath.substring(sitemapPath.length() + 1);
298    }
299
300    @Override
301    public PageType getType() throws AmetysRepositoryException
302    {
303        try
304        {
305            return PageType.valueOf(getNode().getProperty(METADATA_TYPE).getString());
306        }
307        catch (RepositoryException e)
308        {
309            throw new AmetysRepositoryException("Unable to get type property", e);
310        }
311    }
312
313    @Override
314    public void setType(PageType type) throws AmetysRepositoryException
315    {
316        try
317        {
318            getNode().setProperty(METADATA_TYPE, type.name());
319        }
320        catch (RepositoryException e)
321        {
322            throw new AmetysRepositoryException("Unable to set type property", e);
323        }
324    }
325
326    @Override
327    public String getURL() throws AmetysRepositoryException
328    {
329        try
330        {
331            return getNode().getProperty(METADATA_URL).getString();
332        }
333        catch (RepositoryException e)
334        {
335            throw new AmetysRepositoryException("Unable to get url property", e);
336        }
337    }
338    
339    @Override
340    public LinkType getURLType() throws AmetysRepositoryException
341    {
342        try
343        {
344            return LinkType.valueOf(getNode().getProperty(METADATA_URLTYPE).getString());
345        }
346        catch (RepositoryException e)
347        {
348            throw new AmetysRepositoryException("Unable to get url type property", e);
349        }
350    }
351
352    @Override
353    public void setURL(LinkType type, String url) throws AmetysRepositoryException
354    {
355        try
356        {
357            getNode().setProperty(METADATA_URLTYPE, type.toString());
358            getNode().setProperty(METADATA_URL, url);
359        }
360        catch (RepositoryException e)
361        {
362            throw new AmetysRepositoryException("Unable to set url property", e);
363        }
364    }
365
366    @Override
367    public String getTemplate() throws AmetysRepositoryException
368    {
369        try
370        {
371            Node node = getNode();
372            
373            if (node.hasProperty(METADATA_TEMPLATE))
374            {
375                return node.getProperty(METADATA_TEMPLATE).getString();
376            }
377            else
378            {
379                return null;
380            }
381        }
382        catch (RepositoryException e)
383        {
384            throw new AmetysRepositoryException("Unable to get template property", e);
385        }
386    }
387
388    @Override
389    public void setTemplate(String template) throws AmetysRepositoryException
390    {
391        try
392        {
393            getNode().setProperty(METADATA_TEMPLATE, template);
394        }
395        catch (RepositoryException e)
396        {
397            throw new AmetysRepositoryException("Unable to set template property", e);
398        }
399    }
400
401    @Override
402    public AmetysObjectIterable<ModifiableZone> getZones() throws AmetysRepositoryException
403    {
404        try
405        {
406            return ((TraversableAmetysObject) getChild(ZONES_NODE_NAME)).getChildren();
407        }
408        catch (UnknownAmetysObjectException e)
409        {
410            Collection<ModifiableZone> emptyCollection = Collections.emptyList();
411            return new CollectionIterable<>(emptyCollection);
412        }
413    }
414
415    @Override
416    public boolean hasZone(String name) throws AmetysRepositoryException
417    {
418        if (hasChild(ZONES_NODE_NAME))
419        {
420            return ((TraversableAmetysObject) getChild(ZONES_NODE_NAME)).hasChild(name);
421        }
422        else
423        {
424            return false;
425        }
426    }
427    
428    @Override
429    public ModifiableZone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
430    {
431        return ((TraversableAmetysObject) getChild(ZONES_NODE_NAME)).getChild(name);
432    }
433
434    @Override
435    public ModifiableZone createZone(String name) throws AmetysRepositoryException
436    {
437        ModifiableTraversableAmetysObject zones;
438        if (hasChild(ZONES_NODE_NAME))
439        {
440            zones = getChild(ZONES_NODE_NAME);
441        }
442        else
443        {
444            zones = createChild(ZONES_NODE_NAME, ZONES_NODE_TYPE);
445        }
446        
447        return zones.createChild(name, ZONE_NODE_TYPE); 
448    }
449
450    @Override
451    public ResourceCollection getRootAttachments() throws AmetysRepositoryException
452    {
453        ResourceCollection attachments;
454        if (hasChild(ATTACHMENTS_NODE_NAME))
455        {
456            attachments = getChild(ATTACHMENTS_NODE_NAME);
457        }
458        else
459        {
460            attachments = createChild(ATTACHMENTS_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE);
461            
462            try
463            {
464                getNode().getSession().save();
465            }
466            catch (RepositoryException e)
467            {
468                throw new AmetysRepositoryException("Unable to save session", e);
469            }
470        }
471        
472        return attachments;
473    }
474    
475    @Override
476    public Set<String> getReferers() throws AmetysRepositoryException
477    {
478        try
479        {
480            Node node = getNode();
481
482            if (!node.hasProperty(METADATA_REFERERS))
483            {
484                return Collections.emptySet();
485            }
486
487            Value[] values = getNode().getProperty(METADATA_REFERERS).getValues();
488            Set<String> ids = new HashSet<>(values.length);
489
490            for (Value value : values)
491            {
492                ids.add(value.getString());
493            }
494
495            return ids;
496        }
497        catch (RepositoryException e)
498        {
499            throw new AmetysRepositoryException("Unable to get referers property", e);
500        }
501    }
502
503    @Override
504    public void addReferer(String ametysObjectId) throws AmetysRepositoryException
505    {
506        try
507        {
508            Set<String> ids = null;
509            Node node = getNode();
510
511            if (!node.hasProperty(METADATA_REFERERS))
512            {
513                ids = Collections.singleton(ametysObjectId);
514            }
515            else
516            {
517                Value[] values = getNode().getProperty(METADATA_REFERERS).getValues();
518                ids = new HashSet<>(values.length + 1);
519
520                for (Value value : values)
521                {
522                    ids.add(value.getString());
523                }
524
525                ids.add(ametysObjectId);
526            }
527
528            node.setProperty(METADATA_REFERERS, ids.toArray(new String[ids.size()]));
529        }
530        catch (RepositoryException e)
531        {
532            throw new AmetysRepositoryException("Unable to add an id into referers property", e);
533        }
534    }
535
536    @Override
537    public void removeReferer(String ametysObjectId) throws AmetysRepositoryException
538    {
539        try
540        {
541            Node node = getNode();
542
543            if (node.hasProperty(METADATA_REFERERS))
544            {
545                Value[] values = getNode().getProperty(METADATA_REFERERS).getValues();
546                Set<String> ids = new HashSet<>(values.length + 1);
547
548                for (Value value : values)
549                {
550                    ids.add(value.getString());
551                }
552
553                ids.remove(ametysObjectId);
554                node.setProperty(METADATA_REFERERS, ids.toArray(new String[ids.size()]));
555            }
556        }
557        catch (RepositoryException e)
558        {
559            throw new AmetysRepositoryException("Unable to remove an id from referers property", e);
560        }
561    }
562
563    @Override
564    public AmetysObjectIterable< ? extends Page> getChildrenPages() throws AmetysRepositoryException
565    {
566        return getChildrenPages(true);
567    }
568    
569    @Override
570    public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePage) throws AmetysRepositoryException
571    {
572        List<Page> childrenPages = new ArrayList<>();
573        for (AmetysObject childObject : getChildren())
574        {
575            if (childObject instanceof Page)
576            {
577                Page page = (Page) childObject;
578                if (includeInvisiblePage || page.isVisible())
579                {
580                    childrenPages.add(page);
581                }
582            }
583        }
584        return new CollectionIterable<>(childrenPages);
585    }
586    
587    @Override
588    public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException
589    {
590        if (index < 0)
591        {
592            throw new AmetysRepositoryException("Child page index cannot be negative");
593        }
594        
595        AmetysObjectIterable< ? extends Page> childPages = getChildrenPages();
596        AmetysObjectIterator< ? extends Page> it = childPages.iterator();
597        
598        try
599        {
600            it.skip(index);
601            return it.next();
602        }
603        catch (NoSuchElementException e)
604        {
605            throw new UnknownAmetysObjectException("There's no child page at index " + index + " for page " + this.getId());
606        }
607    }
608
609    @Override
610    public DefaultPage copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException
611    {
612        try
613        {
614            int index = 1;
615            String originalName = name == null ? getName() : name;
616            String pageName = originalName;
617            while (parent.hasChild(pageName))
618            {
619                // Find unused name on new parent node
620                pageName = originalName + "-" + index++;
621            }
622            
623            DefaultPage cPage = parent.createChild(pageName, "ametys:defaultPage");
624            cPage.setType(getType());
625            cPage.setTitle(getTitle());
626            
627            if (PageType.CONTAINER.equals(getType()))
628            {
629                cPage.setTemplate(getTemplate());
630            }
631            else if (PageType.LINK.equals(getType()))
632            {
633                cPage.setURL(getURLType(), getURL());
634            }
635            
636            // Copy metadata
637            copyTo(cPage);
638            
639            // Copy view parameters
640            getTemplateParametersHolder().copyTo(cPage.getTemplateParametersHolder());
641            
642            // Copy zones
643            for (ModifiableZone zone : getZones())
644            {
645                if (zone instanceof CopiableAmetysObject)
646                {
647                    ((CopiableAmetysObject) zone).copyTo(cPage, null);
648                }
649            }
650            
651            // Copy tags
652            Set<String> tags = getTags();
653            for (String tag : tags)
654            {
655                cPage.tag(tag);
656            }
657            
658            // Update sitemap name
659            if (parent instanceof PagesContainer)
660            {
661                cPage.setSitemapName(((PagesContainer) parent).getSitemapName());
662            }
663            
664            parent.saveChanges();
665            
666            // Copy attachments
667            ResourceCollection rootAttachments = getRootAttachments();
668            if (rootAttachments instanceof SimpleAmetysObject)
669            {
670                Node resourcesNode = ((SimpleAmetysObject) rootAttachments).getNode();
671                getNode().getSession().getWorkspace().copy(resourcesNode.getPath(), cPage.getNode().getPath() + "/" + resourcesNode.getName());
672            }
673            
674            // Copy sub-pages
675            AmetysObjectIterable< ? extends Page> childrenPages = getChildrenPages();
676            for (Page page : childrenPages)
677            {
678                // Avoid infinite loop : do not copy the page itself
679                if (page instanceof CopiableAmetysObject && restrictTo.contains(page.getId()))
680                {
681                    ((CopiableAmetysObject) page).copyTo(cPage, null, restrictTo);
682                }
683            }
684            
685            return cPage;
686        }
687        catch (RepositoryException e)
688        {
689            throw new AmetysRepositoryException(e);
690        }
691    }
692    
693    @Override
694    public DefaultPage copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException
695    {
696        return copyTo (parent, name, new ArrayList<String>());
697    }
698    
699    public void copyTo(ModifiableDataHolder dataHolder) throws UndefinedItemPathException, BadItemTypeException, UnknownTypeException, NotUniqueTypeException
700    {
701        for (String name : getDataNames())
702        {
703            if (!ViewParametersManager.VIEW_PARAMETERS_COMPOSITE_NAME.equals(name))
704            {
705                DataHolderHelper.copyTo(getDataHolder(), dataHolder, name);
706            }
707        }
708    }
709    
710    @Override
711    public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException
712    {
713        Node node = getNode();
714
715        try
716        {
717            if (getParent().equals(newParent))
718            {
719                // Just order current node to the end
720                node.getParent().orderBefore(node.getName(), null);
721            }
722            else
723            {
724                if (!canMoveTo(newParent))
725                {
726                    throw new AmetysRepositoryException("DefaultPage " + toString() + " can only be moved to a CompositePages and ");
727                }
728                PagesContainer newParentPage = (PagesContainer) newParent;
729                
730                String sitemapNodePath = getSitemap().getNode().getPath();
731                String newPath = sitemapNodePath + (newParentPage instanceof Sitemap ? "" : '/' + newParentPage.getPathInSitemap());
732
733                String name = node.getName();
734                
735                if (renameIfExist)
736                {
737                    // Find unused name on new parent node
738                    int index = 1;
739                    while (node.getSession().getRootNode().hasNode(newPath.substring(1) + "/" + name))
740                    {
741                        name = name + "-" + index++;
742                    }
743                }
744
745                try
746                {
747                    // Move node
748                    node.getSession().move(node.getPath(), newPath + "/" + name);
749                }
750                catch (ItemExistsException e)
751                {
752                    throw new AmetysRepositoryException(String.format("A page already exists for in parent path '%s'", newPath), e);
753                }
754                
755                // recompute name in case it has changed
756                _invalidateName();
757
758                // Invalidate parent path as the parent path has changed
759                _invalidateParentPath();
760            }
761        }
762        catch (RepositoryException e)
763        {
764            throw new AmetysRepositoryException(String.format("Unable to move page '%s' to node '%s'", this, newParent.getId()), e);
765        }
766    }
767    
768    @Override
769    public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException
770    {
771        return  newParent instanceof PagesContainer && ((PagesContainer) newParent).getSitemap().equals(getSitemap());
772    }
773
774    @Override
775    public void orderBefore(AmetysObject siblingNode) throws AmetysRepositoryException
776    {
777        Node node = getNode();
778        try
779        {
780            node.getParent().orderBefore(node.getName(), siblingNode != null ? siblingNode.getName() : null);
781        }
782        catch (RepositoryException e)
783        {
784            throw new AmetysRepositoryException(String.format("Unable to order page '%s' before sibling '%s'", this, siblingNode != null ? siblingNode.getName() : ""), e);
785        }
786    }
787
788    @Override
789    public boolean isVisible() throws AmetysRepositoryException
790    {
791        try
792        {
793            if (getNode().hasProperty(METADATA_VISIBLE))
794            {
795                return getNode().getProperty(METADATA_VISIBLE).getBoolean();
796            }
797            else
798            {
799                return true;
800            }
801        }
802        catch (RepositoryException e)
803        {
804            throw new AmetysRepositoryException("Unable to get visible property", e);
805        }
806    }
807    
808    @Override
809    public void setVisible(boolean isVisible) throws AmetysRepositoryException
810    {
811        try
812        {
813            getNode().setProperty(METADATA_VISIBLE, isVisible);
814        }
815        catch (RepositoryException e)
816        {
817            throw new AmetysRepositoryException("Unable to set visible property", e);
818        }
819    }
820
821    public ModifiableModelLessDataHolder getDataHolder()
822    {
823        ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode());
824        return new DefaultModifiableModelLessDataHolder(_getFactory().getPageDataTypeExtensionPoint(), repositoryData);
825    }
826    
827    @Override
828    public ModifiableCompositeMetadata getMetadataHolder()
829    {
830        __LOGGER.warn("The getMetadataHolder method of DefaultPage is deprecated. Use the getDataHolder instead. StackTrace: ", new Exception());
831        return super.getMetadataHolder();
832    }
833    
834    public ModifiableModelAwareDataHolder getTemplateParametersHolder() throws AmetysRepositoryException
835    {
836        JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode());
837        
838        // Get the template Id
839        String templateId = getTemplate();
840        
841        // Get the skin Id
842        String skinId = getSite().getSkinId();
843        
844        ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager();
845        Optional<ViewParametersModel> templateViewParameters = viewParametersManager.getTemplateViewParametersModel(skinId, templateId);
846        if (templateViewParameters.isEmpty())
847        {
848            templateViewParameters = Optional.of(new ViewParametersModel("template-no-param", new View(), MapUtils.EMPTY_SORTED_MAP));
849        }
850        
851        return viewParametersManager.getParametersHolder(zoneItemRepositoryData, templateViewParameters.get());
852    }
853    
854    /*
855     * The default implementation is overridden here to not use the getDataHolder()#dataToSAX implementation
856     * Each #dataToSAX method calls the next one and the implementation that has to change is the last one.
857     * If the getDataHolder() is used for one, the next ones will be called with the default implementation
858     */
859    public void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, UnknownTypeException, NotUniqueTypeException
860    {
861        for (String name : getDataNames())
862        {
863            dataToSAX(contentHandler, name, context);
864        }
865    }
866    
867    public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException
868    {
869        if (!ViewParametersManager.VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath))
870        {
871            ModifiablePage.super.dataToSAX(contentHandler, dataPath, context);
872        }
873    }
874}