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