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 java.util.Map;
020import java.util.Optional;
021
022import javax.jcr.Node;
023import javax.jcr.RepositoryException;
024import javax.jcr.nodetype.ConstraintViolationException;
025
026import org.apache.jackrabbit.util.Text;
027
028import org.ametys.plugins.repository.AbstractAmetysObject;
029import org.ametys.plugins.repository.AmetysObject;
030import org.ametys.plugins.repository.AmetysRepositoryException;
031import org.ametys.plugins.repository.RepositoryIntegrityViolationException;
032import org.ametys.plugins.repository.data.UnknownDataException;
033import org.ametys.plugins.repository.data.holder.ModifiableDataHolder;
034import org.ametys.plugins.repository.data.holder.group.ModifiableComposite;
035import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableDataHolder;
036import org.ametys.plugins.repository.data.holder.values.SynchronizationResult;
037import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
038import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
039import org.ametys.plugins.repository.metadata.CompositeMetadata;
040import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
041import org.ametys.plugins.repository.metadata.jcr.JCRCompositeMetadata;
042import org.ametys.runtime.model.exception.BadDataPathCardinalityException;
043import org.ametys.runtime.model.exception.BadItemTypeException;
044import org.ametys.runtime.model.exception.NotUniqueTypeException;
045import org.ametys.runtime.model.exception.UndefinedItemPathException;
046import org.ametys.runtime.model.exception.UnknownTypeException;
047
048/**
049 * Default implementation of an {@link AmetysObject}, backed by a JCR node.<br>
050 * This implementation heavily relies on its {@link SimpleAmetysObjectFactory} counterpart.
051 * @param <F> the actual type of factory.
052 */
053public class SimpleAmetysObject<F extends SimpleAmetysObjectFactory> extends AbstractAmetysObject implements JCRAmetysObject 
054{
055    /** The corresponding {@link SimpleAmetysObjectFactory} */
056    private F _factory;
057    
058    private Node _node;
059    
060    private String _name;
061    private String _parentPath;
062
063    /**
064     * Creates an {@link SimpleAmetysObject}.
065     * @param node the node backing this {@link AmetysObject}
066     * @param parentPath the parentPath in the Ametys hierarchy
067     * @param factory the {@link SimpleAmetysObjectFactory} which created the AmetysObject
068     */
069    public SimpleAmetysObject(Node node, String parentPath, F factory)
070    {
071        _node = node;
072        _parentPath = parentPath;
073        _factory = factory;
074        
075        try
076        {
077            _name = Text.unescapeIllegalJcrChars(_node.getName());
078        }
079        catch (RepositoryException e)
080        {
081            throw new AmetysRepositoryException("Unable to get node name", e);
082        }
083    }
084    
085    /**
086     * Retrieves the factory.
087     * @return the factory.
088     */
089    protected F _getFactory()
090    {
091        return _factory;
092    }
093    
094    public String getName() throws AmetysRepositoryException
095    {
096        return _name;
097    }
098    
099    /**
100     * Returns the root {@link CompositeMetadata} of this {@link AmetysObject}.
101     * @return the root {@link CompositeMetadata} of this {@link AmetysObject}
102     * @deprecated use getRepositoryData instead
103     */
104    @Deprecated
105    public ModifiableCompositeMetadata getMetadataHolder()
106    {
107        return new JCRCompositeMetadata(getNode(), _getFactory()._getResolver());
108    }
109    
110    public String getParentPath() throws AmetysRepositoryException
111    {
112        if (_parentPath == null)
113        {
114            _parentPath = getParent().getPath();
115        }
116        
117        return _parentPath;
118    }
119    
120    /**
121     * Invalidates cached parent path.
122     */
123    protected void _invalidateParentPath()
124    {
125        _parentPath = null;
126    }
127    
128    /**
129     * Recompute the name from the Node.<br>
130     * To be used when the node name changesd internally.
131     */
132    protected void _invalidateName()
133    {
134        try
135        {
136            _name = Text.unescapeIllegalJcrChars(getNode().getName());
137        }
138        catch (RepositoryException e)
139        {
140            throw new AmetysRepositoryException(e);
141        }
142    }
143    
144    public String getPath() throws AmetysRepositoryException
145    {
146        return getParentPath() + "/" + getName();
147    }
148    
149    public Node getNode()
150    {
151        return _node;
152    }
153    
154    public String getId()
155    {
156        try
157        {
158            return _factory.getScheme() + "://" + _node.getIdentifier();
159        }
160        catch (RepositoryException e)
161        {
162            throw new AmetysRepositoryException("Unable to get node UUID", e);
163        }
164    }
165    
166    @Override
167    public void rename(String newName) throws AmetysRepositoryException
168    {
169        try
170        {
171            NodeHelper.rename(getNode(), newName);
172            _name = Text.unescapeIllegalJcrChars(getNode().getName());
173        }
174        catch (RepositoryException e)
175        {
176            throw new AmetysRepositoryException(e);
177        }
178    }
179    
180    public void remove() throws AmetysRepositoryException, RepositoryIntegrityViolationException
181    {
182        try
183        {
184            getNode().remove();
185        }
186        catch (ConstraintViolationException e)
187        {
188            throw new RepositoryIntegrityViolationException(e);
189        }
190        catch (RepositoryException e)
191        {
192            throw new AmetysRepositoryException(e);
193        }
194    }
195
196    @SuppressWarnings("unchecked")
197    public <A extends AmetysObject> A getParent() throws AmetysRepositoryException
198    {
199        return (A) _factory.getParent(this);
200    }
201    
202    @Override
203    public boolean needsSave() throws AmetysRepositoryException
204    {
205        try
206        {
207            return _node.getSession().hasPendingChanges();
208        }
209        catch (RepositoryException e)
210        {
211            throw new AmetysRepositoryException(e);
212        }
213    }
214
215    public void saveChanges() throws AmetysRepositoryException
216    {
217        try
218        {
219            getNode().getSession().save();
220        }
221        catch (javax.jcr.RepositoryException e)
222        {
223            throw new AmetysRepositoryException("Unable to save changes", e);
224        }
225    }
226    
227    @Override
228    public void revertChanges() throws AmetysRepositoryException
229    {
230        try
231        {
232            getNode().refresh(false);
233        }
234        catch (javax.jcr.RepositoryException e)
235        {
236            throw new AmetysRepositoryException("Unable to revert changes.", e);
237        }
238    }
239    
240    @Override
241    protected ModifiableDataHolder getDataHolder()
242    {
243        ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode());
244        return new DefaultModifiableDataHolder(_getFactory().getModelItemTypeExtensionPoint(), repositoryData);
245    }
246    
247    @Override
248    public ModifiableComposite getComposite(String compositePath) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException
249    {
250        return getDataHolder().getComposite(compositePath);
251    }
252    
253    public ModifiableComposite getComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException
254    {
255        return getDataHolder().getComposite(compositePath, createNew);
256    }
257    
258    public <T extends SynchronizationResult> T synchronizeValues(Map<String, Object> values) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException, UndefinedItemPathException
259    {
260        return getDataHolder().synchronizeValues(values);
261    }
262
263    public void setValue(String dataPath, Object value) throws IllegalArgumentException, UnknownDataException, UnknownTypeException, NotUniqueTypeException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException
264    {
265        getDataHolder().setValue(dataPath, value);
266    }
267
268    public void setValue(String dataPath, Object value, String dataType) throws IllegalArgumentException, UnknownDataException, UnknownTypeException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException
269    {
270        getDataHolder().setValue(dataPath, value, dataType);
271    }
272
273    public void removeValue(String dataPath) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException
274    {
275        getDataHolder().removeValue(dataPath);
276    }
277    
278    @Override
279    public ModifiableRepositoryData getRepositoryData()
280    {
281        return getDataHolder().getRepositoryData();
282    }
283    
284    @Override
285    public Optional<? extends ModifiableDataHolder> getParentDataHolder()
286    {
287        return getDataHolder().getParentDataHolder();
288    }
289    
290    @Override
291    public ModifiableDataHolder getRootDataHolder()
292    {
293        return getDataHolder().getRootDataHolder();
294    }
295}