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.cms.transformation; 017 018import java.io.InputStream; 019import java.util.Arrays; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.Map; 024 025import org.apache.avalon.framework.context.Context; 026import org.apache.avalon.framework.context.ContextException; 027import org.apache.avalon.framework.context.Contextualizable; 028import org.apache.avalon.framework.logger.AbstractLogEnabled; 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; 034 035import org.ametys.cms.repository.Content; 036import org.ametys.cms.transformation.ConsistencyChecker.CHECK; 037import org.ametys.core.util.URLEncoder; 038import org.ametys.plugins.repository.AmetysObjectResolver; 039import org.ametys.plugins.repository.metadata.CompositeMetadata; 040import org.ametys.plugins.repository.metadata.File; 041import org.ametys.plugins.repository.metadata.Folder; 042import org.ametys.plugins.repository.metadata.Resource; 043import org.ametys.plugins.repository.metadata.RichText; 044import org.ametys.plugins.repository.metadata.UnknownMetadataException; 045import org.ametys.plugins.repository.version.VersionableAmetysObject; 046import org.ametys.runtime.i18n.I18nizableText; 047import org.ametys.runtime.workspace.WorkspaceMatcher; 048 049/** 050 * {@link URIResolver} for resources local to a Content. 051 */ 052public class LocalURIResolver extends AbstractLogEnabled implements URIResolver, Contextualizable, Serviceable 053{ 054 /** The context */ 055 protected Context _context; 056 /** The ametys object resolver */ 057 protected AmetysObjectResolver _ametysObjectResolver; 058 059 @Override 060 public void service(ServiceManager manager) throws ServiceException 061 { 062 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 063 } 064 065 @Override 066 public void contextualize(Context context) throws ContextException 067 { 068 _context = context; 069 } 070 071 @Override 072 public String getType() 073 { 074 return "local"; 075 } 076 077 @Override 078 public String resolve(String uri, boolean download, boolean absolute, boolean internal) 079 { 080 // uri are like content://UUID@metadata;data/file.ext 081 int i = uri.indexOf('@'); 082 int j = uri.indexOf(';', i); 083 String id = uri.substring(0, i); 084 String metadata = uri.substring(i + 1, j); 085 String path = uri.substring(j + 1); 086 087 try 088 { 089 Request request = ContextHelper.getRequest(_context); 090 091 String contentVersion = null; 092 Content content = (Content) request.getAttribute(Content.class.getName()); 093 if (content != null && id.equals(content.getId())) 094 { 095 contentVersion = ((VersionableAmetysObject) content).getRevision(); 096 } 097 098 StringBuilder resultPath = new StringBuilder(); 099 100 resultPath.append(getUriPrefix(download, absolute, internal)) 101 .append("/plugins/cms" + (download ? "/download/" : "/view/")) 102 .append(path); 103 104 Map<String, String> params = new HashMap<>(); 105 params.put("contentId", id); 106 params.put("metadata", metadata); 107 if (contentVersion != null) 108 { 109 params.put("contentVersion", contentVersion); 110 } 111 112 // Encode twice 113 String encodedPath = URLEncoder.encodePath(resultPath.toString()); 114 return URLEncoder.encodeURI(encodedPath, params); 115 } 116 catch (Exception e) 117 { 118 throw new IllegalStateException(e); 119 } 120 } 121 122 /** 123 * Get the URI prefix 124 * @param download true if the pointed resource is to be downloaded. 125 * @param absolute true if the url must be absolute 126 * @param internal true to get an internal URI. 127 * @return the URI prefix 128 */ 129 protected String getUriPrefix (boolean download, boolean absolute, boolean internal) 130 { 131 if (internal) 132 { 133 return "cocoon://"; 134 } 135 else 136 { 137 Request request = ContextHelper.getRequest(_context); 138 String workspaceURI = (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_URI); 139 String uriPrefix = request.getContextPath() + workspaceURI; 140 141 if (absolute && !uriPrefix.startsWith(request.getScheme())) 142 { 143 uriPrefix = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() != 80 ? ":" + request.getServerPort() : "") + uriPrefix; 144 } 145 146 return uriPrefix; 147 } 148 } 149 150 @Override 151 public String resolveImage(String uri, int height, int width, boolean download, boolean absolute, boolean internal) 152 { 153 return resolve(uri, download, absolute, internal); 154 } 155 156 @Override 157 public String resolveImageAsBase64(String uri, int height, int width) 158 { 159 return resolveImageAsBase64(uri, height, width, 0, 0, 0, 0); 160 } 161 162 @Override 163 public String resolveBoundedImage(String uri, int maxHeight, int maxWidth, boolean download, boolean absolute, boolean internal) 164 { 165 return resolve(uri, download, absolute, internal); 166 } 167 168 @Override 169 public String resolveBoundedImageAsBase64(String uri, int maxHeight, int maxWidth) 170 { 171 return resolveImageAsBase64(uri, 0, 0, maxHeight, maxWidth, 0, 0); 172 } 173 174 @Override 175 public String resolveCroppedImage(String uri, int cropHeight, int cropWidth, boolean download, boolean absolute, boolean internal) 176 { 177 return resolve(uri, download, absolute, internal); 178 } 179 180 @Override 181 public String resolveCroppedImageAsBase64(String uri, int cropHeight, int cropWidth) 182 { 183 return resolveImageAsBase64(uri, 0, 0, 0, 0, cropHeight, cropWidth); 184 } 185 186 /** 187 * Get an image's bytes encoded as base64, optionally resized. 188 * @param uri the image URI. 189 * @param height the specified height. Ignored if negative. 190 * @param width the specified width. Ignored if negative. 191 * @param maxHeight the maximum image height. Ignored if height or width is specified. 192 * @param maxWidth the maximum image width. Ignored if height or width is specified. 193 * @param cropHeight The cropping height. Ignored if negative. 194 * @param cropWidth The cropping width. Ignored if negative. 195 * @return the image bytes encoded as base64. 196 */ 197 protected String resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth) 198 { 199 // uri are like content://UUID@metadata;data/file.ext 200 int i = uri.indexOf('@'); 201 int j = uri.indexOf(';', i); 202 String id = uri.substring(0, i); 203 String metadata = uri.substring(i + 1, j); 204 String path = uri.substring(j + 1); 205 206 try 207 { 208 Request request = ContextHelper.getRequest(_context); 209 210 Content content = (Content) request.getAttribute(Content.class.getName()); 211 if (!content.getId().equals(id)) 212 { 213 content = _ametysObjectResolver.resolveById(id); 214 } 215 216 RichText richText = _getMeta(content.getMetadataHolder(), metadata); 217 File file = _getFile(richText.getAdditionalDataFolder(), path); 218 Resource resource = file.getResource(); 219 220 try (InputStream dataIs = resource.getInputStream()) 221 { 222 return ImageResolverHelper.resolveImageAsBase64(dataIs, resource.getMimeType(), height, width, maxHeight, maxWidth, cropHeight, cropWidth); 223 } 224 } 225 catch (Exception e) 226 { 227 throw new IllegalStateException(e); 228 } 229 } 230 231 @Override 232 public CHECK checkLink(String uri, boolean shortTest) 233 { 234 try 235 { 236 int i = uri.indexOf('@'); 237 int j = uri.indexOf(';', i); 238 239 if (i == -1 || j == -1) 240 { 241 getLogger().warn("Failed to check local URI: '" + uri + " does not respect the excepted format 'content://UUID@metadata;data/file.ext'"); 242 return CHECK.SERVER_ERROR; 243 } 244 245 String id = uri.substring(0, i); 246 String metadata = uri.substring(i + 1, j); 247 String fileName = uri.substring(j + 1); 248 249 Content content = _ametysObjectResolver.resolveById(id); 250 RichText richText = _getMeta(content.getMetadataHolder(), metadata); 251 252 richText.getAdditionalDataFolder().getFile(fileName); 253 254 return CHECK.SUCCESS; 255 } 256 catch (UnknownMetadataException e) 257 { 258 return CHECK.NOT_FOUND; 259 } 260 catch (Exception e) 261 { 262 throw new RuntimeException("Cannot check the uri '" + uri + "'", e); 263 } 264 } 265 266 @Override 267 public I18nizableText getLabel(String uri) 268 { 269 int i = uri.indexOf('@'); 270 int j = uri.indexOf(';', i); 271 272 String fileName = uri.substring(j + 1); 273 274 return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_LOCAL_LABEL", Collections.singletonList(fileName)); 275 } 276 277 /** 278 * Get the rich text meta 279 * @param meta The composite meta 280 * @param metadataName The metadata name (with /) 281 * @return The rich text meta 282 */ 283 protected RichText _getMeta(CompositeMetadata meta, String metadataName) 284 { 285 int pos = metadataName.indexOf("/"); 286 if (pos == -1) 287 { 288 return meta.getRichText(metadataName); 289 } 290 else 291 { 292 return _getMeta(meta.getCompositeMetadata(metadataName.substring(0, pos)), metadataName.substring(pos + 1)); 293 } 294 } 295 296 /** 297 * Get the file at the specified path in the given folder. 298 * @param folder The folder to search in. 299 * @param path The file path in the folder (can contain slashes). 300 * @return The file if found, null otherwise. 301 */ 302 protected File _getFile(Folder folder, String path) 303 { 304 File file = null; 305 306 Iterator<String> it = Arrays.asList(path.split("/")).iterator(); 307 308 Folder browsedFolder = folder; 309 while (it.hasNext()) 310 { 311 String pathElement = it.next(); 312 313 if (it.hasNext()) 314 { 315 // not the last segment : it is a composite 316 browsedFolder = browsedFolder.getFolder(pathElement); 317 } 318 else 319 { 320 file = browsedFolder.getFile(pathElement); 321 } 322 } 323 324 return file; 325 } 326}