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