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}