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