001/* 002 * Copyright 2017 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.plugins.linkdirectory; 017 018import java.io.InputStream; 019import java.util.Arrays; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.context.Contextualizable; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.cocoon.components.ContextHelper; 035import org.apache.cocoon.environment.Request; 036 037import org.ametys.cms.transformation.ConsistencyChecker.CHECK; 038import org.ametys.cms.transformation.ImageResolverHelper; 039import org.ametys.cms.transformation.URIResolver; 040import org.ametys.core.util.URLEncoder; 041import org.ametys.plugins.repository.AmetysObjectResolver; 042import org.ametys.plugins.repository.metadata.BinaryMetadata; 043import org.ametys.plugins.repository.metadata.CompositeMetadata; 044import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType; 045import org.ametys.plugins.repository.metadata.MetadataAwareAmetysObject; 046import org.ametys.plugins.repository.metadata.UnknownMetadataException; 047import org.ametys.runtime.i18n.I18nizableText; 048import org.ametys.runtime.plugin.component.PluginAware; 049import org.ametys.web.URIPrefixHandler; 050import org.ametys.web.renderingcontext.RenderingContext; 051import org.ametys.web.renderingcontext.RenderingContextHandler; 052import org.ametys.web.repository.SiteAwareAmetysObject; 053import org.ametys.web.repository.site.Site; 054import org.ametys.web.repository.site.SiteManager; 055 056/** 057 * {@link URIResolver} for type "link-metadata".<br> 058 * These links or images point to a file from the metadata of a {@link Link}. 059 */ 060public class LinkMetadataURIResolver implements URIResolver, Serviceable, Contextualizable, PluginAware 061{ 062 private static final Pattern _OBJECT_URI_PATTERN = Pattern.compile("([^?]*)\\?objectId=(.*)"); 063 064 private AmetysObjectResolver _resolver; 065 private URIPrefixHandler _prefixHandler; 066 private RenderingContextHandler _renderingContexthandler; 067 private SiteManager _siteManager; 068 069 /** The context */ 070 private Context _context; 071 072 private String _pluginName; 073 074 public void setPluginInfo(String pluginName, String featureName, String id) 075 { 076 _pluginName = pluginName; 077 } 078 079 @Override 080 public void contextualize(Context context) throws ContextException 081 { 082 _context = context; 083 } 084 085 @Override 086 public void service(ServiceManager manager) throws ServiceException 087 { 088 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 089 _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE); 090 _renderingContexthandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE); 091 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 092 } 093 094 @Override 095 public String getType() 096 { 097 return "link-metadata"; 098 } 099 100 @Override 101 public String resolve(String uri, boolean download, boolean absolute, boolean internal) 102 { 103 try 104 { 105 Request request = ContextHelper.getRequest(_context); 106 107 MetaInfo metaInfo = _getMetaInfo(uri, request); 108 109 MetadataAwareAmetysObject object = metaInfo.getAmetysObject(); 110 String metadataPath = metaInfo.getMetadataPath(); 111 112 if (object == null) 113 { 114 throw new IllegalStateException("Cannot resolve a local link to an unknown link for uri " + request.getRequestURI()); 115 } 116 117 BinaryMetadata binary = getBinaryMetadata(object, metadataPath); 118 119 StringBuilder resultPath = new StringBuilder(); 120 121 resultPath.append("/_plugins/") 122 .append(_pluginName) 123 .append("/_links") 124 .append(URLEncoder.encodePath(object.getPath()).replaceAll(":", "%3A")) 125 .append("/_metadata/") 126 .append(metadataPath) 127 .append("/").append(URLEncoder.encodePath(binary.getFilename())); 128 129 String path = getUri(resultPath.toString(), object, download, absolute, internal); 130 131 Map<String, String> params = new HashMap<>(); 132 params.put("objectId", object.getId()); 133 if (download) 134 { 135 params.put("download", "true"); 136 } 137 138 return URLEncoder.encodeURI(path, params); 139 } 140 catch (Exception e) 141 { 142 throw new IllegalStateException(e); 143 } 144 } 145 146 @Override 147 public String resolveImage(String uri, int height, int width, boolean download, boolean absolute, boolean internal) 148 { 149 if (height == 0 && width == 0) 150 { 151 return resolve(uri, download, absolute, internal); 152 } 153 154 try 155 { 156 Request request = ContextHelper.getRequest(_context); 157 158 MetaInfo metaInfo = _getMetaInfo(uri, request); 159 160 MetadataAwareAmetysObject object = metaInfo.getAmetysObject(); 161 String metadataPath = metaInfo.getMetadataPath(); 162 163 if (object == null) 164 { 165 throw new IllegalStateException("Cannot resolve a local link to an unknown link for uri " + request.getRequestURI()); 166 } 167 168 BinaryMetadata binary = getBinaryMetadata(object, metadataPath); 169 170 StringBuilder resultPath = new StringBuilder(); 171 172 resultPath.append("/_plugins/") 173 .append(_pluginName) 174 .append("/_links-images") 175 .append(object.getPath().replaceAll(":", "%3A")) 176 .append("/_metadata/") 177 .append(metadataPath) 178 .append("_").append(height).append("x").append(width) 179 .append("/").append(URLEncoder.encodePath(binary.getFilename())); 180 181 String path = getUri(resultPath.toString(), object, download, absolute, internal); 182 183 Map<String, String> params = new HashMap<>(); 184 params.put("objectId", object.getId()); 185 if (download) 186 { 187 params.put("download", "true"); 188 } 189 190 return URLEncoder.encodeURI(path, params); 191 } 192 catch (Exception e) 193 { 194 throw new IllegalStateException(e); 195 } 196 } 197 198 @Override 199 public String resolveImageAsBase64(String uri, int height, int width) 200 { 201 return resolveImageAsBase64(uri, height, width, 0, 0); 202 } 203 204 @Override 205 public String resolveBoundedImage(String uri, int maxHeight, int maxWidth, boolean download, boolean absolute, boolean internal) 206 { 207 if (maxHeight == 0 && maxWidth == 0) 208 { 209 return resolve(uri, download, absolute, internal); 210 } 211 212 try 213 { 214 Request request = ContextHelper.getRequest(_context); 215 216 MetaInfo metaInfo = _getMetaInfo(uri, request); 217 218 MetadataAwareAmetysObject object = metaInfo.getAmetysObject(); 219 String metadataPath = metaInfo.getMetadataPath(); 220 221 if (object == null) 222 { 223 throw new IllegalStateException("Cannot resolve a local link to an unknown link for uri " + request.getRequestURI()); 224 } 225 226 BinaryMetadata binary = getBinaryMetadata(object, metadataPath); 227 228 StringBuilder resultPath = new StringBuilder(); 229 230 resultPath.append("/_plugins/") 231 .append(_pluginName) 232 .append("/_links-images") 233 .append(object.getPath().replaceAll(":", "%3A")) 234 .append("/_metadata/") 235 .append(metadataPath) 236 .append("_max").append(maxHeight).append("x").append(maxWidth) 237 .append("/").append(URLEncoder.encodePath(binary.getFilename())); 238 239 String path = getUri(resultPath.toString(), object, download, absolute, internal); 240 241 Map<String, String> params = new HashMap<>(); 242 params.put("objectId", object.getId()); 243 if (download) 244 { 245 params.put("download", "true"); 246 } 247 248 return URLEncoder.encodeURI(path, params); 249 } 250 catch (Exception e) 251 { 252 throw new IllegalStateException(e); 253 } 254 } 255 256 @Override 257 public String resolveBoundedImageAsBase64(String uri, int maxHeight, int maxWidth) 258 { 259 return resolveImageAsBase64(uri, 0, 0, maxHeight, maxWidth); 260 } 261 262 /** 263 * Get an image's bytes encoded as base64, optionally resized. 264 * @param uri the image URI. 265 * @param height the specified height. Ignored if negative. 266 * @param width the specified width. Ignored if negative. 267 * @param maxHeight the maximum image height. Ignored if height or width is specified. 268 * @param maxWidth the maximum image width. Ignored if height or width is specified. 269 * @return the image bytes encoded as base64. 270 */ 271 protected String resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth) 272 { 273 try 274 { 275 Request request = ContextHelper.getRequest(_context); 276 277 MetaInfo metaInfo = _getMetaInfo(uri, request); 278 279 MetadataAwareAmetysObject object = metaInfo.getAmetysObject(); 280 String metadataPath = metaInfo.getMetadataPath(); 281 282 if (object == null) 283 { 284 throw new IllegalStateException("Cannot resolve a local link to an unknown link for uri " + request.getRequestURI()); 285 } 286 287 BinaryMetadata binary = getBinaryMetadata(object, metadataPath); 288 289 try (InputStream dataIs = binary.getInputStream()) 290 { 291 return ImageResolverHelper.resolveImageAsBase64(dataIs, binary.getMimeType(), height, width, maxHeight, maxWidth); 292 } 293 } 294 catch (Exception e) 295 { 296 throw new IllegalStateException(e); 297 } 298 } 299 300 /** 301 * Get the URI prefix 302 * @param path the resource path 303 * @param object The object 304 * @param download true if the pointed resource is to be downloaded. 305 * @param absolute true if the url must be absolute 306 * @param internal true to get an internal URI. 307 * @return the URI prefix 308 */ 309 protected String getUri(String path, MetadataAwareAmetysObject object, boolean download, boolean absolute, boolean internal) 310 { 311 Request request = ContextHelper.getRequest(_context); 312 313 String siteName = null; 314 if (object instanceof SiteAwareAmetysObject) 315 { 316 siteName = ((SiteAwareAmetysObject) object).getSiteName(); 317 } 318 else 319 { 320 siteName = (String) request.getAttribute("siteName"); 321 322 if (siteName == null) 323 { 324 siteName = (String) request.getAttribute("site"); 325 } 326 } 327 328 if (internal) 329 { 330 return "cocoon://" + siteName + path; 331 } 332 else if (absolute) 333 { 334 if (_renderingContexthandler.getRenderingContext() == RenderingContext.FRONT) 335 { 336 Site site = _siteManager.getSite(siteName); 337 338 String[] aliases = site.getUrlAliases(); 339 return aliases[Math.abs(path.hashCode()) % aliases.length] + path; 340 } 341 342 return _prefixHandler.getAbsoluteUriPrefix() + "/" + siteName + path; 343 } 344 else 345 { 346 return _prefixHandler.getUriPrefix(siteName) + path; 347 } 348 } 349 350 @Override 351 public CHECK checkLink(String uri, boolean shortTest) 352 { 353 return CHECK.SUCCESS; 354 } 355 356 @Override 357 public I18nizableText getLabel(String uri) 358 { 359 Request request = ContextHelper.getRequest(_context); 360 361 MetaInfo metaInfo = _getMetaInfo(uri, request); 362 363 return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_METADATA_LABEL", Collections.singletonList(metaInfo.getMetadataPath())); 364 } 365 366 /** 367 * Get the binary metadata 368 * @param link the link 369 * @param path the metadata path 370 * @return the binary metadata 371 */ 372 protected BinaryMetadata getBinaryMetadata (MetadataAwareAmetysObject link, String path) 373 { 374 CompositeMetadata metadata = link.getMetadataHolder(); 375 376 List<String> pathElements = Arrays.asList(path.split("/")); 377 378 Iterator<String> it = pathElements.iterator(); 379 380 while (it.hasNext()) 381 { 382 String pathElement = it.next(); 383 384 if (it.hasNext()) 385 { 386 // not the last segment : it is a composite 387 metadata = metadata.getCompositeMetadata(pathElement); 388 } 389 else 390 { 391 if (metadata.getType(pathElement) != MetadataType.BINARY) 392 { 393 throw new UnsupportedOperationException("Only binary metadata are allowed"); 394 } 395 396 return metadata.getBinaryMetadata(pathElement); 397 } 398 } 399 400 throw new UnknownMetadataException("Unknown metadata " + path + " for link " + link.getName()); 401 } 402 403 404 /** 405 * Get metadata name and link. 406 * @param uri the metadata URI. 407 * @param request the request. 408 * @return the metadata info. 409 */ 410 protected MetaInfo _getMetaInfo(String uri, Request request) 411 { 412 MetaInfo info = new MetaInfo(); 413 414 Matcher matcher = _OBJECT_URI_PATTERN.matcher(uri); 415 416 // Test if the URI contains an object ID. 417 if (matcher.matches()) 418 { 419 info.setMetadataPath(matcher.group(1)); 420 String objectId = matcher.group(2); 421 422 MetadataAwareAmetysObject object = _resolver.resolveById(objectId); 423 info.setAmetysObject(object); 424 } 425 else 426 { 427 throw new IllegalStateException("Missing objectId parameter to resolve a local link for uri " + request.getRequestURI()); 428 } 429 430 return info; 431 } 432 433 /** 434 * Metadata information. 435 */ 436 protected class MetaInfo 437 { 438 private String _metadataPath; 439 private MetadataAwareAmetysObject _object; 440 441 /** 442 * Get the metadataName. 443 * @return the metadataName 444 */ 445 public String getMetadataPath() 446 { 447 return _metadataPath; 448 } 449 450 /** 451 * Set the metadataPath. 452 * @param metadataPath the metadata path to set 453 */ 454 public void setMetadataPath(String metadataPath) 455 { 456 this._metadataPath = metadataPath; 457 } 458 459 /** 460 * Get the object. 461 * @return the object 462 */ 463 public MetadataAwareAmetysObject getAmetysObject() 464 { 465 return _object; 466 } 467 468 /** 469 * Set the link. 470 * @param object the object to set 471 */ 472 public void setAmetysObject(MetadataAwareAmetysObject object) 473 { 474 this._object = object; 475 } 476 } 477} 478