001/*
002 *  Copyright 2011 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.workspaces.repository.jcr;
017
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Map;
022import java.util.Set;
023
024import javax.jcr.RepositoryException;
025import javax.jcr.Session;
026import javax.jcr.Workspace;
027import javax.jcr.nodetype.NodeDefinition;
028import javax.jcr.nodetype.NodeType;
029import javax.jcr.nodetype.NodeTypeIterator;
030import javax.jcr.nodetype.NodeTypeManager;
031
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.context.Context;
034import org.apache.avalon.framework.context.ContextException;
035import org.apache.avalon.framework.context.Contextualizable;
036import org.apache.avalon.framework.logger.AbstractLogEnabled;
037import org.apache.avalon.framework.service.ServiceException;
038import org.apache.avalon.framework.service.ServiceManager;
039import org.apache.avalon.framework.service.Serviceable;
040import org.apache.cocoon.components.ContextHelper;
041import org.apache.cocoon.environment.Request;
042
043import org.ametys.plugins.repositoryapp.RepositoryProvider;
044
045/**
046 * Node type hierarchy component.
047 */
048public class NodeTypeHierarchyComponent extends AbstractLogEnabled implements Component, Contextualizable, Serviceable
049{
050    
051    /** The avalon role. */
052    public static final String ROLE = NodeTypeHierarchyComponent.class.getName();
053    
054    /** The subtypes registry, by workspace. */
055    protected Map<String, Map<String, Set<String>>> _subTypes;
056    
057    /** The repository provider. */
058    protected RepositoryProvider _repositoryProvider;
059    
060    /** The avalon context. */
061    protected Context _context;
062    
063    /** The service manager. */
064    protected ServiceManager _manager;
065    
066    @Override
067    public void contextualize(Context context) throws ContextException
068    {
069        this._context = context;
070    }
071    
072    @Override
073    public void service(ServiceManager manager) throws ServiceException
074    {
075        _manager = manager;
076        _repositoryProvider = (RepositoryProvider) manager.lookup(RepositoryProvider.ROLE);
077    }
078    
079    /**
080     * Get the sub types of a given node type in a specific workspace.
081     * @param nodeType the node type.
082     * @param workspaceName the workspace name.
083     * @return the sub types of the node type in the given workspace.
084     * @throws RepositoryException if an error occurred
085     */
086    public Set<String> getDirectSubTypes(NodeType nodeType, String workspaceName) throws RepositoryException
087    {
088        if (_subTypes == null || !_subTypes.containsKey(workspaceName))
089        {
090            _buildTypeHierarchy(workspaceName);
091        }
092        
093        if (_subTypes.get(workspaceName).containsKey(nodeType.getName()))
094        {
095            return Collections.unmodifiableSet(_subTypes.get(workspaceName).get(nodeType.getName()));
096        }
097        else
098        {
099            return Collections.emptySet();
100        }
101    }
102    
103    /**
104     * Get the sub types of a given node type in a specific workspace.
105     * @param nodeType the node type.
106     * @param workspaceName the workspace name.
107     * @return the sub types of the node type in the given workspace.
108     * @throws RepositoryException if an error occurred
109     */
110    public Set<String> getRecursiveSubTypes(String nodeType, String workspaceName) throws RepositoryException
111    {
112        if (_subTypes == null || !_subTypes.containsKey(workspaceName))
113        {
114            _buildTypeHierarchy(workspaceName);
115        }
116        
117        Set<String> allTypes = new HashSet<>();
118        
119        if (_subTypes.get(workspaceName).containsKey(nodeType))
120        {
121            for (String subType : _subTypes.get(workspaceName).get(nodeType))
122            {
123                allTypes.add(subType);
124                allTypes.addAll(getRecursiveSubTypes(subType, workspaceName));
125            }
126        }
127        
128        return allTypes;
129    }
130    
131    /**
132     * Get the available children types for a given node type in a workspace.
133     * @param nodeDef the node definition.
134     * @param workspaceName the workspace name.
135     * @return the available node types.
136     * @throws RepositoryException if an error occurred
137     */
138    public Set<String> getAvailableChildrenTypes(NodeDefinition nodeDef, String workspaceName) throws RepositoryException
139    {
140        if (_subTypes == null || !_subTypes.containsKey(workspaceName))
141        {
142            _buildTypeHierarchy(workspaceName);
143        }
144        
145        Set<String> types = new HashSet<>();
146        
147        NodeType[] requiredTypes = nodeDef.getRequiredPrimaryTypes();
148        if (requiredTypes.length > 0)
149        {
150            types.add(requiredTypes[0].getName());
151            types.addAll(getRecursiveSubTypes(requiredTypes[0].getName(), workspaceName));
152        }
153        
154        for (int i = 1; i < requiredTypes.length; i++)
155        {
156            types.add(requiredTypes[0].getName());
157            types.retainAll(getRecursiveSubTypes(requiredTypes[0].getName(), workspaceName));
158        }
159        
160        return types;
161    }
162    
163    /**
164     * Compute the primary type hierarchy.
165     * @param workspaceName The workspace name to use
166     * @throws RepositoryException if an error occurs.
167     */
168    protected void _buildTypeHierarchy(String workspaceName) throws RepositoryException
169    {
170        if (_subTypes == null)
171        {
172            _subTypes = new HashMap<>();
173        }
174        
175        Map<String, Set<String>> workspaceTypes = new HashMap<>();
176        
177        _subTypes.put(workspaceName, workspaceTypes);
178        
179        Request request = ContextHelper.getRequest(_context);
180        
181        try
182        {
183            Workspace workspace = _getWorkspace(request, workspaceName);
184            NodeTypeManager ntManager = workspace.getNodeTypeManager();
185            
186            if (getLogger().isDebugEnabled())
187            {
188                getLogger().debug("Computing node type hierarchy for workspace " + workspaceName);
189            }
190            
191            NodeTypeIterator nodeTypes = ntManager.getPrimaryNodeTypes();
192            while (nodeTypes.hasNext())
193            {
194                NodeType nodeType = nodeTypes.nextNodeType();
195                for (NodeType supertype : nodeType.getSupertypes())
196                {
197                    if (!supertype.isMixin())
198                    {
199                        Set<String> subtypes = null;
200                        if (workspaceTypes.containsKey(supertype.getName()))
201                        {
202                            subtypes = workspaceTypes.get(supertype.getName());
203                        }
204                        else
205                        {
206                            subtypes = new HashSet<>();
207                            workspaceTypes.put(supertype.getName(), subtypes);
208                        }
209                        
210                        if (getLogger().isDebugEnabled())
211                        {
212                            getLogger().debug(nodeType.getName() + " is a sub type of " + supertype.getName());
213                        }
214                        
215                        subtypes.add(nodeType.getName());
216                    }
217                }
218            }
219        }
220        catch (ServiceException e)
221        {
222            throw new RuntimeException(e);
223        }
224    }
225    
226    /**
227     * Get the repository and session and set the _repository and _session protected fields
228     * @param request The request
229     * @param workspaceName The jcr workspace
230     * @return the workspace.
231     * @throws RepositoryException if an error occured.
232     * @throws ServiceException if the repository can't be looked up.
233     */
234    protected Workspace _getWorkspace(Request request, String workspaceName) throws RepositoryException, ServiceException
235    {
236        Session session = _repositoryProvider.getSession(workspaceName);
237        
238        return session.getWorkspace();
239    }
240    
241}