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 */ 016package org.ametys.web.source; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.net.MalformedURLException; 021import java.util.LinkedHashMap; 022import java.util.Map; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import org.apache.avalon.framework.context.Context; 027import org.apache.avalon.framework.context.ContextException; 028import org.apache.avalon.framework.context.Contextualizable; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.cocoon.components.ContextHelper; 033import org.apache.cocoon.environment.Request; 034import org.apache.excalibur.source.Source; 035import org.apache.excalibur.source.SourceFactory; 036import org.apache.excalibur.source.SourceNotFoundException; 037import org.apache.excalibur.source.SourceResolver; 038import org.apache.excalibur.source.SourceValidity; 039 040import org.ametys.runtime.model.ElementDefinition; 041import org.ametys.runtime.model.ModelItem; 042import org.ametys.runtime.plugin.component.AbstractLogEnabled; 043import org.ametys.web.WebConstants; 044import org.ametys.web.repository.page.ZoneItem; 045import org.ametys.web.service.Service; 046import org.ametys.web.service.ServiceExtensionPoint; 047 048/** 049 * This factory looks for files in the current skin and fallback in the current plugin dir.<br> 050 * Use: service://path_to_file<br> 051 * Will first loon in the current template in the <i>stylesheets/services/{pluginName}</i> sub-directory => skins/{skin}/templates/{template}/stylesheets/services/{pluginName}/path_to_file<br> 052 * If not found, then it will look in the skin of the current site in the sub-directory services/{pluginName} => skins/{skin}/services/{currentPluginName}/path_to_file<br> 053 * And if the file does not exist will search in plugin:{currentPluginName}://path_to_file 054 */ 055public class ServiceSourceFactory extends AbstractLogEnabled implements SourceFactory, Serviceable, Contextualizable 056{ 057 private static final Pattern __SOURCE_PATTERN = Pattern.compile("^[\\w]+:([^:]+:)?//(.*)$"); 058 059 private SourceResolver _resolver; 060 private Context _context; 061 private ServiceExtensionPoint _sep; 062 private ServiceManager _manager; 063 064 /** 065 * The enum of existing types of servicesources 066 */ 067 public enum SourceType 068 { 069 /** The source is in the plugin */ 070 PLUGIN, 071 /** The source is in the skin */ 072 SKIN, 073 /** The source is in the template */ 074 TEMPLATE 075 } 076 077 @Override 078 public void contextualize(Context context) throws ContextException 079 { 080 _context = context; 081 } 082 083 @Override 084 public void service(ServiceManager manager) throws ServiceException 085 { 086 _manager = manager; 087 } 088 089 @Override 090 public Source getSource(String location, Map parameters) throws IOException, MalformedURLException 091 { 092 // lazy initialization to prevent chicken/egg scenario on startup 093 if (_sep == null) 094 { 095 try 096 { 097 _resolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE); 098 _sep = (ServiceExtensionPoint) _manager.lookup(ServiceExtensionPoint.ROLE); 099 } 100 catch (ServiceException e) 101 { 102 throw new IllegalStateException("Exception while getting components", e); 103 } 104 } 105 106 Matcher m = __SOURCE_PATTERN.matcher(location); 107 if (!m.matches()) 108 { 109 throw new MalformedURLException("URI must be like protocol://path/to/resource. Location was '" + location + "'"); 110 } 111 112 Request request = ContextHelper.getRequest(_context); 113 String pluginName = m.group(1); 114 if (pluginName == null) 115 { 116 pluginName = (String) request.getAttribute("pluginName"); 117 } 118 else 119 { 120 pluginName = pluginName.substring(0, pluginName.length() - 1); 121 } 122 123 String uri = m.group(2); 124 125 if (uri.startsWith("@")) 126 { 127 ZoneItem zoneItem = (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM); 128 129 String serviceId = zoneItem.getServiceId(); 130 Service service = _sep.getExtension(serviceId); 131 132 String parameterName = uri.substring(1); 133 ElementDefinition parameterDefinition = _findParameter(service, parameterName, location); 134 135 String file = null; 136 if (zoneItem.getServiceParameters().hasValue(parameterName)) 137 { 138 Object value = zoneItem.getServiceParameters().getValue(parameterName); 139 file = parameterDefinition.getType().toString(value); 140 } 141 142 if (file != null && file.length() > 0) 143 { 144 Source resolverSource = _resolver.resolveURI("service://" + file); 145 if (resolverSource.exists()) 146 { 147 return resolverSource; 148 } 149 } 150 151 getLogger().warn("ZoneItem '{}' references file '{}' that does not exist. Switching to default value.", zoneItem.getId(), file); 152 153 // Fallback to default value 154 file = (String) parameterDefinition.getDefaultValue(); 155 return _resolver.resolveURI("service://" + file); 156 } 157 else 158 { 159 Map<SourceType, String> loc = getLocations(pluginName, uri); 160 for (SourceType sourceType : loc.keySet()) 161 { 162 String sourceUri = loc.get(sourceType); 163 try 164 { 165 Source source = _resolver.resolveURI(sourceUri); 166 if (!source.exists()) 167 { 168 getLogger().debug("Failed to find a stylesheet at '{}'.", sourceUri); 169 } 170 else 171 { 172 getLogger().debug("Using source located at '{}'.", sourceUri); 173 return new ServiceSource(source, sourceType); 174 } 175 } 176 catch (IOException e) 177 { 178 getLogger().debug("Resolving protocol failed for resolving '{}'.", sourceUri); 179 } 180 } 181 182 // Should never occur because of the default stylesheet 183 throw new IOException("Can't find a stylesheet for: " + location); 184 } 185 } 186 187 private ElementDefinition _findParameter(Service service, String parameterName, String location) throws MalformedURLException 188 { 189 ModelItem parameter = service.getModelItem(parameterName); 190 191 if (parameter != null && parameter instanceof ElementDefinition) 192 { 193 return (ElementDefinition) parameter; 194 } 195 196 throw new MalformedURLException("The service '" + service.getId() + "' does not have a parameter named '" + parameterName + "' that is necessary to resolve '" + location + "'"); 197 } 198 199 /** 200 * Returns the ordered list of URIs to be tested to find the service stylesheet. 201 * @param pluginName the service plugin name. 202 * @param uri the service stylesheet. 203 * @return a list of possible URIs (id is the source type associated). 204 */ 205 protected Map<SourceType, String> getLocations(String pluginName, String uri) 206 { 207 Map<SourceType, String> locations = new LinkedHashMap<>(); 208 209 // First look in the current template. 210 locations.put(SourceType.TEMPLATE, "template://stylesheets/services/" + pluginName + "/" + uri); 211 212 // Then look in the skin. 213 locations.put(SourceType.SKIN, "skin://services/" + pluginName + "/" + uri); 214 215 // Then look in the plugin. 216 locations.put(SourceType.PLUGIN, "plugin:" + pluginName + "://" + uri); 217 218 return locations; 219 } 220 221 @Override 222 public void release(Source source) 223 { 224 // empty method 225 } 226 227 /** 228 * A wrapping source to know real location 229 */ 230 public class ServiceSource implements Source 231 { 232 private Source _source; 233 private SourceType _type; 234 235 /** 236 * Creates the service source 237 * @param source The source to wrap 238 * @param type The type of the source 239 */ 240 public ServiceSource (Source source, SourceType type) 241 { 242 _type = type; 243 _source = source; 244 } 245 246 /** 247 * Get the source type of this source 248 * @return the source type 249 */ 250 public SourceType getSourceType() 251 { 252 return _type; 253 } 254 255 /** 256 * Get the wrapped source 257 * @return the original source 258 */ 259 public Source getWrappedSource() 260 { 261 return _source; 262 } 263 264 @Override 265 public boolean exists() 266 { 267 return _source.exists(); 268 } 269 270 @Override 271 public long getContentLength() 272 { 273 return _source.getContentLength(); 274 } 275 276 @Override 277 public InputStream getInputStream() throws IOException, SourceNotFoundException 278 { 279 return _source.getInputStream(); 280 } 281 282 @Override 283 public long getLastModified() 284 { 285 return _source.getLastModified(); 286 } 287 288 @Override 289 public String getMimeType() 290 { 291 return _source.getMimeType(); 292 } 293 294 @Override 295 public String getScheme() 296 { 297 return _source.getScheme(); 298 } 299 300 @Override 301 public String getURI() 302 { 303 return _source.getURI(); 304 } 305 306 @Override 307 public SourceValidity getValidity() 308 { 309 return _source.getValidity(); 310 } 311 312 @Override 313 public void refresh() 314 { 315 _source.refresh(); 316 } 317 } 318}