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