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.plugins.repository.jcr;
017
018import java.util.Collection;
019import java.util.Collections;
020
021import javax.jcr.ItemNotFoundException;
022import javax.jcr.Node;
023import javax.jcr.Repository;
024import javax.jcr.RepositoryException;
025import javax.jcr.Session;
026
027import org.apache.avalon.framework.configuration.Configurable;
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.logger.AbstractLogEnabled;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.avalon.framework.service.Serviceable;
034
035import org.ametys.plugins.repository.AmetysObject;
036import org.ametys.plugins.repository.AmetysObjectFactoryExtensionPoint;
037import org.ametys.plugins.repository.AmetysObjectResolver;
038import org.ametys.plugins.repository.AmetysRepositoryException;
039import org.ametys.plugins.repository.UnknownAmetysObjectException;
040import org.ametys.plugins.repository.data.type.ModelItemTypeExtensionPoint;
041import org.ametys.plugins.repository.provider.AbstractRepository;
042
043/**
044 * Default implementation of a {@link JCRAmetysObjectFactory},
045 * handling {@link SimpleAmetysObject}.<br>
046 * This implementation takes its scheme and nodetype through a configuration:<br>
047 * <code>
048 * &lt;extension point="org.ametys.plugins.repository.AmetysObjectFactoryExtensionPoint"<br>
049 * &nbsp;&nbsp;&nbsp;&nbsp;id="XXXX"
050 * class="org.ametys.plugins.repository.DefaultAmetysObjectFactory"&gt;<br>
051 * &nbsp;&nbsp;&lt;scheme&gt;your_scheme&lt;/scheme&gt;<br>
052 * &nbsp;&nbsp;&lt;nodetype&gt;your:nodetype&lt;/nodetype&gt;<br>
053 * &nbsp;&nbsp;[&lt;nodetype&gt;your:nodetype2&lt;/nodetype&gt;]<br>
054 * &nbsp;&nbsp;[...]<br>
055 * &lt;/extension&gt;<br>
056 * This implementation manages only one nodetype.
057 * </code>
058 */
059public class SimpleAmetysObjectFactory extends AbstractLogEnabled implements JCRAmetysObjectFactory<SimpleAmetysObject>, Configurable, Serviceable
060{
061    /** The application {@link AmetysObjectResolver} */
062    protected AmetysObjectResolver _resolver;
063    
064    /** The {@link AmetysObjectFactoryExtensionPoint} */
065    protected AmetysObjectFactoryExtensionPoint _ametysFactoryExtensionPoint;
066    
067    /** The configured scheme */
068    protected String _scheme;
069
070    /** The configured nodetype */
071    protected String _nodetype;
072    
073    /** JCR Repository */
074    protected Repository _repository;
075    
076    /** The Avalon {@link ServiceManager} */
077    protected ServiceManager _manager;
078    
079    /** The extension point with available modelItem types for handled ametys object */
080    protected ModelItemTypeExtensionPoint _modelItemTypeExtensionPoint;
081    
082    public void service(ServiceManager manager) throws ServiceException
083    {
084        _manager = manager;
085        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
086        _repository = (Repository) manager.lookup(AbstractRepository.ROLE);
087        _ametysFactoryExtensionPoint = (AmetysObjectFactoryExtensionPoint) manager.lookup(AmetysObjectFactoryExtensionPoint.ROLE);
088    }
089
090    public void configure(Configuration configuration) throws ConfigurationException
091    {
092        _scheme = configuration.getChild("scheme").getValue();
093        
094        Configuration[] nodetypesConf = configuration.getChildren("nodetype");
095        
096        if (nodetypesConf.length != 1)
097        {
098            throw new ConfigurationException("A SimpleAmetysObjectFactory must have one and only one associated nodetype. "
099                                           + "The '" + configuration.getAttribute("id") + "' component has " + nodetypesConf.length);
100        }
101        
102        _nodetype = nodetypesConf[0].getValue();
103    }
104    
105    AmetysObjectResolver _getResolver()
106    {
107        return _resolver;
108    }
109
110    public String getScheme()
111    {
112        return _scheme;
113    }
114    
115    public Collection<String> getNodetypes()
116    {
117        return Collections.singletonList(_nodetype);
118    }
119
120    @SuppressWarnings("unchecked")
121    public SimpleAmetysObject getAmetysObject(Node node, String parentPath) throws AmetysRepositoryException, RepositoryException
122    {
123        return new SimpleAmetysObject(node, parentPath, this);
124    }
125    
126    public SimpleAmetysObject getAmetysObjectById(String id) throws AmetysRepositoryException
127    {
128        try
129        {
130            return getAmetysObjectById(id, null);
131        }
132        catch (RepositoryException e)
133        {
134            throw new AmetysRepositoryException("Unable to get AmetysObject for id: " + id, e);
135        }
136    }
137    
138    @Override
139    public SimpleAmetysObject getAmetysObjectById(String id, Session session) throws AmetysRepositoryException, RepositoryException
140    {
141        Node node = getNode(id, session);
142        
143        if (!node.getPath().startsWith('/' + AmetysObjectResolver.ROOT_REPO))
144        {
145            throw new AmetysRepositoryException("Cannot resolve a Node outside Ametys tree");
146        }
147        
148        return getAmetysObject(node, null);
149    }
150    
151    public boolean hasAmetysObjectForId(String id) throws AmetysRepositoryException
152    {
153        try
154        {
155            getNode(id, null);
156            return true;
157        }
158        catch (UnknownAmetysObjectException e)
159        {
160            return false;
161        }
162    }
163    
164    /**
165     * Returns the JCR Node associated with the given object id.<br>
166     * This implementation assumes that the id is like <code>&lt;scheme&gt;://&lt;uuid&gt;</code>
167     * @param id the unique id of the object
168     * @param session the JCR Session to use to retrieve the Node.
169     * @return the JCR Node associated with the given id
170     */
171    protected Node getNode(String id, Session session)
172    {
173        // l'id est de la forme <scheme>://uuid
174        String uuid = id.substring(getScheme().length() + 3);
175        
176        Session jcrSession = null;
177        try
178        {
179            jcrSession = session != null ? session : _repository.login();
180            Node node = jcrSession.getNodeByIdentifier(uuid);
181            return node;
182        }
183        catch (ItemNotFoundException e)
184        {
185            if (session == null && jcrSession != null)
186            {
187                // logout only if the session was created here
188                jcrSession.logout();
189            }
190
191            throw new UnknownAmetysObjectException("There's no node for id " + id, e);
192        }
193        catch (RepositoryException e)
194        {
195            if (session == null && jcrSession != null)
196            {
197                // logout only if the session was created here
198                jcrSession.logout();
199            }
200
201            throw new AmetysRepositoryException("Unable to get AmetysObject for id: " + id, e);
202        }
203    }
204
205    /**
206     * Returns the parent of the given {@link SimpleAmetysObject} .
207     * @param object a {@link SimpleAmetysObject}.
208     * @return the parent of the given {@link SimpleAmetysObject}. 
209     * @throws AmetysRepositoryException if an error occurs.
210     */
211    public AmetysObject getParent(SimpleAmetysObject object) throws AmetysRepositoryException
212    {
213        if (getLogger().isDebugEnabled())
214        {
215            getLogger().debug("Entering DefaultTraversableAmetysObjectFactory.getParent with object of name: " + object.getName());
216        }
217
218        Node node = getWorkspaceNode (object);
219        
220        try
221        {
222            Node parentNode = node.getParent();
223            String parentNodetype = NodeTypeHelper.getNodeTypeName(parentNode);
224
225            if (getLogger().isDebugEnabled())
226            {
227                getLogger().debug("Parent nodetype is " + parentNodetype);
228            }
229
230            if (parentNodetype.equals(_nodetype))
231            {
232                if (getLogger().isDebugEnabled())
233                {
234                    getLogger().debug("The parent node has the same nodetype than this ObjectFactory: " + _nodetype);
235                }
236
237                // si le nodetype est celui de la factory, on peut éviter de passer par le resolver
238                return getAmetysObject(parentNode, null);
239            }
240        
241            return _resolver.resolve(parentNode, false);
242        }
243        catch (RepositoryException e)
244        {
245            throw new AmetysRepositoryException("Unable to retrieve parent object of object " + object.getName(), e);
246        }
247    }
248    
249    /**
250     * Returns the JCR node backing this {@link SimpleAmetysObject} in the JCR workspace. May be overridden to deal with e.g. versionning 
251     * @param object a {@link SimpleAmetysObject}.
252     * @return the JCR node backing this {@link SimpleAmetysObject}.
253     */
254    protected Node getWorkspaceNode(SimpleAmetysObject object)
255    {
256        return object.getNode();
257    }
258    
259    /**
260     * Retrieves the extension point with available modelItem types for handled ametys object
261     * @return the model item type extension point
262     */
263    public ModelItemTypeExtensionPoint getModelItemTypeExtensionPoint()
264    {
265        if (_modelItemTypeExtensionPoint == null)
266        {
267            try
268            {
269                _modelItemTypeExtensionPoint = (ModelItemTypeExtensionPoint) _manager.lookup(getModelItemTypeExtensionPointId());
270            }
271            catch (ServiceException e)
272            {
273                throw new AmetysRepositoryException("Caught an exception while retrieving ModelItemTypeExtensionPoint", e);
274            }
275        }
276        
277        return _modelItemTypeExtensionPoint;
278    }
279    
280    /**
281     * Retrieves the identifier of extension point with available modelItem types for handled ametys object
282     * @return the identifier of the model item type extension point
283     */
284    protected String getModelItemTypeExtensionPointId()
285    {
286        return ModelItemTypeExtensionPoint.ROLE_BASIC;
287    }
288}