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 */
016
017package org.ametys.plugins.repository.jcr;
018
019import javax.jcr.Node;
020import javax.jcr.NodeIterator;
021import javax.jcr.RepositoryException;
022import javax.jcr.nodetype.ConstraintViolationException;
023
024import org.apache.jackrabbit.util.Text;
025
026import org.ametys.plugins.repository.AbstractAmetysObject;
027import org.ametys.plugins.repository.AmetysObject;
028import org.ametys.plugins.repository.AmetysRepositoryException;
029import org.ametys.plugins.repository.RepositoryIntegrityViolationException;
030import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
031import org.ametys.plugins.repository.metadata.jcr.JCRCompositeMetadata;
032
033/**
034 * Default implementation of an {@link AmetysObject}, backed by a JCR node.<br>
035 * This implementation heavily relies on its {@link SimpleAmetysObjectFactory} counterpart.
036 * @param <F> the actual type of factory.
037 */
038public class SimpleAmetysObject<F extends SimpleAmetysObjectFactory> extends AbstractAmetysObject implements JCRAmetysObject 
039{
040    /** The corresponding {@link SimpleAmetysObjectFactory} */
041    private F _factory;
042    
043    private Node _node;
044    
045    private String _name;
046    private String _parentPath;
047
048    /**
049     * Creates an {@link SimpleAmetysObject}.
050     * @param node the node backing this {@link AmetysObject}
051     * @param parentPath the parentPath in the Ametys hierarchy
052     * @param factory the DefaultTraversableAmetysObjectFactory which created the AmetysObject
053     */
054    public SimpleAmetysObject(Node node, String parentPath, F factory)
055    {
056        _node = node;
057        _parentPath = parentPath;
058        _factory = factory;
059        
060        try
061        {
062            _name = Text.unescapeIllegalJcrChars(_node.getName());
063        }
064        catch (RepositoryException e)
065        {
066            throw new AmetysRepositoryException("Unable to get node name", e);
067        }
068    }
069    
070    /**
071     * Retrieves the factory.
072     * @return the factory.
073     */
074    protected F _getFactory()
075    {
076        return _factory;
077    }
078    
079    public String getName() throws AmetysRepositoryException
080    {
081        return _name;
082    }
083    
084    public ModifiableCompositeMetadata getMetadataHolder()
085    {
086        return new JCRCompositeMetadata(getNode(), _getFactory()._getResolver());
087    }
088
089    public String getParentPath() throws AmetysRepositoryException
090    {
091        if (_parentPath == null)
092        {
093            _parentPath = getParent().getPath();
094        }
095        
096        return _parentPath;
097    }
098    
099    /**
100     * Invalidates cached parent path.
101     */
102    protected void _invalidateParentPath()
103    {
104        _parentPath = null;
105    }
106    
107    /**
108     * Recompute the name from the Node.<br>
109     * To be used when the node name changesd internally.
110     */
111    protected void _invalidateName()
112    {
113        try
114        {
115            _name = Text.unescapeIllegalJcrChars(getNode().getName());
116        }
117        catch (RepositoryException e)
118        {
119            throw new AmetysRepositoryException(e);
120        }
121    }
122    
123    public String getPath() throws AmetysRepositoryException
124    {
125        return getParentPath() + "/" + getName();
126    }
127    
128    public Node getNode()
129    {
130        return _node;
131    }
132    
133    public String getId()
134    {
135        try
136        {
137            return _factory.getScheme() + "://" + _node.getIdentifier();
138        }
139        catch (RepositoryException e)
140        {
141            throw new AmetysRepositoryException("Unable to get node UUID", e);
142        }
143    }
144    
145    @Override
146    public void rename(String newName) throws AmetysRepositoryException
147    {
148        try
149        {
150            Node parentNode = getNode().getParent();
151            boolean order = parentNode.getPrimaryNodeType().hasOrderableChildNodes();
152            Node nextSibling = null;
153            
154            if (order)
155            {
156                // iterate over the siblings to find the following
157                NodeIterator siblings = parentNode.getNodes();
158                boolean iterate = true;
159                
160                while (siblings.hasNext() && iterate)
161                {
162                    Node sibling = siblings.nextNode();
163                    iterate = !sibling.getName().equals(getNode().getName());
164                }
165                
166                // iterator is currently on the node
167                while (siblings.hasNext() && nextSibling == null)
168                {
169                    Node sibling = siblings.nextNode();
170                    String path = sibling.getPath();
171                    if (getNode().getSession().itemExists(path))
172                    {
173                        nextSibling = sibling;
174                    }
175                }
176            }
177            
178            getNode().getSession().move(getNode().getPath(), getNode().getParent().getPath() + "/" + newName);
179            
180            if (order)
181            {
182                // nextSibling is either null meaning that the Node must be ordered last or is equals to the following sibling
183                if (nextSibling != null)
184                {
185                    parentNode.orderBefore(newName, nextSibling.getName());
186                }
187                else
188                {
189                    parentNode.orderBefore(newName, null);
190                }
191            }
192            
193            _name = Text.unescapeIllegalJcrChars(getNode().getName());
194        }
195        catch (RepositoryException e)
196        {
197            throw new AmetysRepositoryException(e);
198        }
199    }
200    
201    public void remove() throws AmetysRepositoryException, RepositoryIntegrityViolationException
202    {
203        try
204        {
205            getNode().remove();
206        }
207        catch (ConstraintViolationException e)
208        {
209            throw new RepositoryIntegrityViolationException(e);
210        }
211        catch (RepositoryException e)
212        {
213            throw new AmetysRepositoryException(e);
214        }
215    }
216
217    @SuppressWarnings("unchecked")
218    public <A extends AmetysObject> A getParent() throws AmetysRepositoryException
219    {
220        return (A) _factory.getParent(this);
221    }
222    
223    @Override
224    public boolean needsSave() throws AmetysRepositoryException
225    {
226        try
227        {
228            return _node.getSession().hasPendingChanges();
229        }
230        catch (RepositoryException e)
231        {
232            throw new AmetysRepositoryException(e);
233        }
234    }
235
236    public void saveChanges() throws AmetysRepositoryException
237    {
238        try
239        {
240            getNode().getSession().save();
241        }
242        catch (javax.jcr.RepositoryException e)
243        {
244            throw new AmetysRepositoryException("Unable to save changes", e);
245        }
246    }
247    
248    @Override
249    public void revertChanges() throws AmetysRepositoryException
250    {
251        try
252        {
253            getNode().refresh(false);
254        }
255        catch (javax.jcr.RepositoryException e)
256        {
257            throw new AmetysRepositoryException("Unable to revert changes.", e);
258        }
259    }
260    
261}