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 */ 016 017package org.ametys.web.service; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.regex.Pattern; 030 031import org.apache.avalon.framework.component.Component; 032import org.apache.avalon.framework.configuration.Configurable; 033import org.apache.avalon.framework.configuration.Configuration; 034import org.apache.avalon.framework.configuration.ConfigurationException; 035import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 036import org.apache.avalon.framework.context.Context; 037import org.apache.avalon.framework.context.ContextException; 038import org.apache.avalon.framework.context.Contextualizable; 039import org.apache.avalon.framework.logger.AbstractLogEnabled; 040import org.apache.avalon.framework.service.ServiceException; 041import org.apache.avalon.framework.service.ServiceManager; 042import org.apache.avalon.framework.service.Serviceable; 043import org.apache.cocoon.components.ContextHelper; 044import org.apache.cocoon.environment.Request; 045import org.apache.commons.lang.StringUtils; 046import org.apache.excalibur.source.Source; 047import org.apache.excalibur.source.SourceResolver; 048import org.apache.excalibur.source.TraversableSource; 049 050import org.ametys.plugins.repository.AmetysObjectResolver; 051import org.ametys.runtime.i18n.I18nizableText; 052import org.ametys.runtime.model.Enumerator; 053import org.ametys.runtime.plugin.component.PluginAware; 054import org.ametys.web.WebConstants; 055import org.ametys.web.repository.page.ServicesAssignmentHandler; 056import org.ametys.web.repository.page.SitemapElement; 057import org.ametys.web.repository.page.ZoneItem; 058import org.ametys.web.source.ServiceSourceFactory; 059 060/** 061 * This enuerator return the list of available files for services. 062 * The files are searched in the skin and in the plugin 063 * Configuration is 064 * <path> to specify the path where file are stored 065 * <file> an optionnal regexp to list files in the path (default is ^.*\.xsl$ to list all xsl files) 066 */ 067public class ServiceXSLTEnumerator extends AbstractLogEnabled implements Enumerator<String>, org.ametys.runtime.parameter.Enumerator, Component, Configurable, PluginAware, Serviceable, Contextualizable 068{ 069 /** The relative path to search */ 070 protected String _path; 071 072 /** The file pattern */ 073 protected Pattern _fileFilter; 074 /** The plugin declaring the enumerator */ 075 protected String _pluginName; 076 /** The feature declaring the enumerator */ 077 protected String _featureName; 078 /** The excalibur source resolver */ 079 protected SourceResolver _resolver; 080 /** The default value of the xsl to use. Can be null. */ 081 protected String _defaultValue; 082 /** The plugin xsl to use. Can be empty. */ 083 protected Set<String> _values; 084 /** The avalon context */ 085 protected Context _context; 086 /** The service assignement handler instance */ 087 protected ServicesAssignmentHandler _servicesAssignmentHandler; 088 /** The ametys object resolver instance */ 089 protected AmetysObjectResolver _ametysObjectResolver; 090 091 @Override 092 public void configure(Configuration configuration) throws ConfigurationException 093 { 094 _defaultValue = configuration.getChild("default-value").getValue(null); 095 096 Configuration enumConf = configuration.getChild("enumeration").getChild("custom-enumerator"); 097 098 _values = _getPluginOtherXSL (enumConf); 099 100 _path = enumConf.getChild("path").getValue(""); 101 if (StringUtils.isBlank(_path)) 102 { 103 throw new ConfigurationException("The configuration of the path is mandatory", configuration); 104 } 105 106 String regexp = enumConf.getChild("file").getValue("^.*\\.xsl$"); 107 _fileFilter = Pattern.compile(regexp); 108 } 109 110 @Override 111 public void contextualize(Context context) throws ContextException 112 { 113 _context = context; 114 } 115 116 @Override 117 public void service(ServiceManager manager) throws ServiceException 118 { 119 _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 120 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 121 _servicesAssignmentHandler = (ServicesAssignmentHandler) manager.lookup(ServicesAssignmentHandler.ROLE); 122 } 123 124 @Override 125 public void setPluginInfo(String pluginName, String featureName, String id) 126 { 127 _pluginName = pluginName; 128 _featureName = featureName; 129 } 130 131 @Override 132 public Map<String, Object> getConfiguration() 133 { 134 Map<String, Object> config = new HashMap<>(); 135 136 config.put("defaultValue", _defaultValue); 137 config.put("values", _values); 138 config.put("path", _path); 139 config.put("fileFilter", _fileFilter.pattern()); 140 141 return config; 142 } 143 144 /** 145 * Filter a list of sources to return thoses matching the _filter 146 * @param files A non null list of files to filter 147 * @return The non null filtered list of names 148 */ 149 protected List<String> _filterNames(Collection<TraversableSource> files) 150 { 151 List<String> matched = new ArrayList<>(); 152 153 for (TraversableSource f : files) 154 { 155 if (!f.isCollection() && _fileFilter.matcher(f.getName()).matches()) 156 { 157 matched.add(f.getName()); 158 } 159 } 160 161 return matched; 162 } 163 164 public Map<String, I18nizableText> getTypedEntries() throws Exception 165 { 166 // 1 - Compute the list of files 167 Set<String> files = new HashSet<>(); 168 169 TraversableSource source = null; 170 try 171 { 172 source = (TraversableSource) _resolver.resolveURI("skin://services/" + _pluginName + "/" + _path); 173 if (source.exists()) 174 { 175 if (source.isCollection()) 176 { 177 @SuppressWarnings("unchecked") 178 List<String> sourceFiles = _filterNames(source.getChildren()); 179 files.addAll(sourceFiles); 180 } 181 else 182 { 183 getLogger().warn("Can not enumerate files for service defined in '" + _pluginName + "/" + _featureName + "' on the path '" + _path + "', because it exists on the skin but is not a directory in the skin"); 184 } 185 } 186 } 187 finally 188 { 189 _resolver.release(source); 190 } 191 192 if (_defaultValue != null) 193 { 194 Source pluginSource = null; 195 try 196 { 197 pluginSource = _resolver.resolveURI("plugin:" + _pluginName + "://" + _defaultValue); 198 if (pluginSource.exists()) 199 { 200 int i = _defaultValue.lastIndexOf('/'); 201 String name = _defaultValue; 202 if (i > 0) 203 { 204 name = _defaultValue.substring(i + 1); 205 } 206 files.add(name); 207 } 208 } 209 finally 210 { 211 _resolver.release(pluginSource); 212 } 213 } 214 215 if (_values != null) 216 { 217 for (String value : _values) 218 { 219 Source pluginSource = null; 220 try 221 { 222 pluginSource = _resolver.resolveURI("plugin:" + _pluginName + "://" + value); 223 if (pluginSource.exists()) 224 { 225 int i = value.lastIndexOf('/'); 226 String name = value; 227 if (i > 0) 228 { 229 name = value.substring(i + 1); 230 } 231 files.add(name); 232 } 233 } 234 finally 235 { 236 _resolver.release(pluginSource); 237 } 238 } 239 } 240 241 // 2 - Limit to authorized values 242 Request request = ContextHelper.getRequest(_context); 243 244 String pageId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE_ID); 245 if (StringUtils.isNotBlank(pageId)) // Specific places like forms preview, may need the enumerator with no restriction possible 246 { 247 SitemapElement page = _ametysObjectResolver.resolveById(pageId); 248 249 String zoneName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_ZONE_NAME); 250 if (zoneName == null) 251 { 252 String zoneItemId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM_ID); 253 ZoneItem zoneItem = _ametysObjectResolver.resolveById(zoneItemId); 254 zoneName = zoneItem.getZone().getName(); 255 } 256 257 String serviceId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SERVICE_ID); 258 files = _servicesAssignmentHandler.limitAvailableServiceViews(files, page, zoneName, serviceId); 259 } 260 261 // 3 - Convert the files into entries 262 Map<String, I18nizableText> entries = new HashMap<>(); 263 for (String filename : files) 264 { 265 String s = _path + "/" + filename; 266 267 I18nizableText i18ntext = getEntry(s); 268 if (i18ntext != null) 269 { 270 entries.put(s, i18ntext); 271 } 272 } 273 274 return entries; 275 } 276 277 @Override 278 // TODO NEWATTRIBUTEAPI: remove this method when org.ametys.runtime.parameter.Enumerator will be removed 279 public Map<Object, I18nizableText> getEntries() throws Exception 280 { 281 Map<Object, I18nizableText> result = new HashMap<>(); 282 for (Map.Entry<String, I18nizableText> entry : getTypedEntries().entrySet()) 283 { 284 result.put(entry.getKey(), entry.getValue()); 285 } 286 return Collections.unmodifiableMap(result); 287 } 288 289 @Override 290 public I18nizableText getEntry(String value) throws Exception 291 { 292 String valueWithNoExtension = value.substring(0, value.length() - 4); 293 String url = "service:" + _pluginName + "://" + valueWithNoExtension + ".xml"; 294 295 Source source = null; 296 try 297 { 298 source = _resolver.resolveURI(url); 299 if (source.exists()) 300 { 301 String defaultCatalogue = "plugin." + _pluginName; 302 303 if (source instanceof ServiceSourceFactory.ServiceSource) 304 { 305 if (((ServiceSourceFactory.ServiceSource) source).getSourceType() != ServiceSourceFactory.SourceType.PLUGIN) 306 { 307 String skinName = (String) ContextHelper.getRequest(_context).getAttribute("skin"); 308 defaultCatalogue = "skin." + skinName; 309 } 310 } 311 312 try (InputStream inputStream = source.getInputStream()) 313 { 314 Configuration conf = new DefaultConfigurationBuilder().build(inputStream); 315 316 if (!conf.getAttributeAsBoolean("exclude", false)) 317 { 318 getLogger().warn("The 'exclude' attribute in service view definition file (" + url + ") is obsolete. Consider using the services-views-xxx.xml file : search for \"services-views-default.xml\" in the Ametys documentation to know more about it."); 319 320 Configuration node = conf.getChild("label", false); 321 if (node != null) 322 { 323 return I18nizableText.parseI18nizableText(node, defaultCatalogue); 324 } 325 else 326 { 327 // No label defined 328 int i = valueWithNoExtension.lastIndexOf('/'); 329 String shortFilename = valueWithNoExtension.substring(i + 1); 330 return new I18nizableText(shortFilename); 331 } 332 } 333 else 334 { 335 return null; 336 } 337 } 338 } 339 } 340 catch (IOException e) 341 { 342 // Nothing 343 } 344 finally 345 { 346 _resolver.release(source); 347 } 348 349 int i = valueWithNoExtension.lastIndexOf('/'); 350 String shortFilename = valueWithNoExtension.substring(i + 1); 351 return new I18nizableText(shortFilename); 352 } 353 354 private Set<String> _getPluginOtherXSL (Configuration configuration) 355 { 356 Set<String> otherFiles = new HashSet<>(); 357 358 Configuration[] filesConf = configuration.getChild("values", true).getChildren("value"); 359 for (Configuration fileConf : filesConf) 360 { 361 otherFiles.add(fileConf.getValue(null)); 362 } 363 364 return otherFiles; 365 } 366 367}