001/*
002 *  Copyright 2013 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.cms.contenttype;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.FilenameFilter;
021import java.io.InputStream;
022import java.util.Arrays;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.configuration.DefaultConfiguration;
031import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.cocoon.Constants;
035import org.apache.commons.lang.ArrayUtils;
036import org.apache.commons.lang.StringUtils;
037
038import org.ametys.runtime.plugin.PluginsManager;
039import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
040
041/**
042 * This class is in charge of handling dynamic content type descriptor extension point.<br>
043 * This point handles the pool of available dynamic content type descriptors.
044 */
045public class DynamicContentTypeDescriptorExtentionPoint extends AbstractThreadSafeComponentExtensionPoint<DynamicContentTypeDescriptor>
046{
047    /** Avalon Role */
048    public static final String ROLE = "org.ametys.cms.contenttype.DynamicContentTypeExtentionPoint"; // FIXME RUNTIME-1023 DynamicContentTypeDescriptorExtentionPoint.class.getName();
049    
050    private ServiceManager _smanager;
051    private ContentTypeExtensionPoint _contentTypeEP;
052    private ContentTypesHelper _contentTypesHelper;
053    
054    private Map<String, String> _cache = new HashMap<>();
055    
056    @Override
057    public void service(ServiceManager manager) throws ServiceException
058    {
059        super.service(manager);
060        _smanager = manager;
061        _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
062    }
063    
064    @Override
065    public void initializeExtensions() throws Exception
066    {
067        org.apache.cocoon.environment.Context cocoonContext = (org.apache.cocoon.environment.Context) _context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
068        final Set<String> pluginNames = PluginsManager.getInstance().getPluginNames();
069        
070        File rootFile = new File(cocoonContext.getRealPath("/WEB-INF/param/content-types/_dynamic"));
071        File[] plugins = rootFile.listFiles(new FilenameFilter()
072        {
073            @Override
074            public boolean accept(File dir, String name)
075            {
076                return pluginNames.contains(name);
077            }
078        });
079        
080        if (plugins != null)
081        {
082            for (File pluginDir : plugins)
083            {
084                String pluginName = pluginDir.getName();
085                
086                File[] files = pluginDir.listFiles(new FilenameFilter()
087                {
088                    @Override
089                    public boolean accept(File dir, String name)
090                    {
091                        return name.endsWith(".xml");
092                    }
093                });
094                
095                for (File file : files)
096                {
097                    Configuration configuration = null;
098                    try (InputStream is = new FileInputStream(file))
099                    {
100                        configuration = new DefaultConfigurationBuilder(true).build(is);
101                        
102                        String id = "dynamic-content-type." + file.getName().substring(0, file.getName().lastIndexOf('.'));
103                        
104                        DefaultConfiguration conf = new DefaultConfiguration("extension");
105                        conf.setAttribute("id", id);
106                        conf.addChild(configuration);
107                        
108                        addComponent(pluginName, "unknown", id, DynamicContentTypeDescriptor.class, conf);
109                    }
110                    catch (Exception ex)
111                    {
112                        throw new ConfigurationException("Unable to parse dynamic content type configuration at WEB-INF/param/content-types/dynamic/" + file.getName() + ".xml", configuration, ex);
113                    }
114                }
115            }
116        }
117        
118        super.initializeExtensions();
119    }
120    
121    /**
122     * Get the matching dynamic descriptor for given content types and mixins
123     * @param contentTypes The content types
124     * @param mixinTypes The mixins
125     * @return The matching dynamic descriptor or <code>null</code> if not found
126     */
127    public DynamicContentTypeDescriptor getMatchingDescriptor (String[] contentTypes, String[] mixinTypes)
128    {
129        String cacheId = _getCacheIdentifier(contentTypes, mixinTypes);
130        
131        if (_cache.containsKey(cacheId))
132        {
133            return getExtension(_cache.get(cacheId));
134        }
135        
136        DynamicContentTypeDescriptor matchingDynamicCType = null;
137        
138        int bestCTypeScore = 0;
139        int bestMixinScore = 0;
140        
141        for (String id : getExtensionsIds())
142        {
143            int cTypeScore = 0;
144            int mixinScore = 0;
145            
146            DynamicContentTypeDescriptor dynamicContentType = getExtension(id);
147            
148            boolean dynamicTypesMatch = true;
149            String[] dynamicCTypes = dynamicContentType.getSupertypeIds();
150            Set<String> cTypesAndAncestors = _getContentTypesAndAncestor(contentTypes, mixinTypes);
151            for (String cTypeId : dynamicCTypes)
152            {
153                if (!cTypesAndAncestors.contains(cTypeId))
154                {
155                    // Dynamic types do not include all content types, ignore
156                    dynamicTypesMatch = false;
157                    break;
158                }
159            }
160            
161            if (dynamicTypesMatch)
162            {
163                for (String cTypeId : dynamicContentType.getSupertypeIds())
164                {
165                    ContentType cType = _contentTypeEP.getExtension(cTypeId);
166                    
167                    if (!cType.isMixin() && ArrayUtils.contains(contentTypes, cTypeId))
168                    {
169                        cTypeScore += 1;
170                    }
171                    else if (cType.isMixin() && ArrayUtils.contains(mixinTypes, cTypeId))
172                    {
173                        mixinScore += 1;
174                    }
175                }
176                
177                if (cTypeScore > bestCTypeScore || (cTypeScore == bestCTypeScore && mixinScore > bestMixinScore))
178                {
179                    bestCTypeScore = cTypeScore;
180                    bestMixinScore = mixinScore;
181
182                    matchingDynamicCType = dynamicContentType;
183                    _cache.put(cacheId, id);
184                }
185            }
186        }
187        
188        return matchingDynamicCType;
189    }
190    
191    private Set<String> _getContentTypesAndAncestor (String[] contentTypes, String[] mixins)
192    {
193        Set<String> cTypesAndAncestors = new HashSet<>();
194        
195        if (_contentTypesHelper == null)
196        {
197            try
198            {
199                _contentTypesHelper = (ContentTypesHelper) _smanager.lookup(ContentTypesHelper.ROLE);
200            }
201            catch (ServiceException e)
202            {
203                throw new IllegalStateException(e);
204            }
205        }
206        
207        String[] allContentTypes = (String[]) ArrayUtils.addAll(contentTypes, mixins);
208        
209        for (String id : allContentTypes)
210        {
211            cTypesAndAncestors.add(id);
212            cTypesAndAncestors.addAll(_contentTypesHelper.getAncestors(id));
213        }
214        
215        return cTypesAndAncestors;
216        
217    }
218    
219    private String _getCacheIdentifier (String[] contentTypes, String[] mixins)
220    {
221        Arrays.sort(contentTypes);
222        String name = StringUtils.join(contentTypes, ";");
223        
224        if (mixins.length > 0)
225        {
226            Arrays.sort(mixins);
227            name += ";";
228            name += StringUtils.join(mixins, ";");
229        }
230        
231        return name;
232    }
233}