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