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.Collections; 020import java.util.HashMap; 021import java.util.Map; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 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.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.cocoon.components.ContextHelper; 032import org.apache.cocoon.environment.Request; 033 034import org.ametys.cms.data.Binary; 035import org.ametys.cms.repository.Content; 036import org.ametys.cms.transformation.ConsistencyChecker.CHECK; 037import org.ametys.core.util.FilenameUtils; 038import org.ametys.core.util.URIUtils; 039import org.ametys.plugins.repository.AmetysObjectResolver; 040import org.ametys.plugins.repository.version.VersionableAmetysObject; 041import org.ametys.runtime.i18n.I18nizableText; 042import org.ametys.runtime.workspace.WorkspaceMatcher; 043 044/** 045 * {@link URIResolver} for type "attribute".<br> 046 * These links or images point to a file from the attribute of the current Content. 047 */ 048public class AttributeURIResolver extends AbstractURIResolver implements Serviceable, Contextualizable 049{ 050 private static final Pattern _OBJECT_URI_PATTERN = Pattern.compile("([^?]*)\\?objectId=(.*)"); 051 private static final Pattern _CONTENT_URI_PATTERN = Pattern.compile("([^?]*)\\?contentId=(.*)"); 052 053 /** The ametys object resolver */ 054 protected AmetysObjectResolver _resolver; 055 056 /** The context */ 057 protected Context _context; 058 059 @Override 060 public void contextualize(Context context) throws ContextException 061 { 062 _context = context; 063 } 064 065 @Override 066 public void service(ServiceManager manager) throws ServiceException 067 { 068 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 069 } 070 071 @Override 072 public String getType() 073 { 074 return "attribute"; 075 } 076 077 @Override 078 protected String _resolve(String uri, String uriArgument, boolean download, boolean absolute, boolean internal) 079 { 080 Request request = ContextHelper.getRequest(_context); 081 082 AttributeInfo info = _getAttributeInfo(uri, request); 083 084 Content content = info.getContent(); 085 String path = info.getPath(); 086 087 if (content == null) 088 { 089 throw new IllegalStateException("Cannot resolve a local link to an unknown content for uri " + request.getRequestURI()); 090 } 091 092 String contentVersion = ""; 093 if (content instanceof VersionableAmetysObject) 094 { 095 contentVersion = ((VersionableAmetysObject) content).getRevision(); 096 } 097 098 Binary binary = content.getValue(path); 099 100 String filename = FilenameUtils.encodeName(binary.getFilename()); 101 102 String baseName = org.apache.commons.io.FilenameUtils.getBaseName(filename); 103 String extension = org.apache.commons.io.FilenameUtils.getExtension(filename); 104 105 StringBuilder resultPath = new StringBuilder(); 106 107 resultPath.append("/_contents") 108 .append(FilenameUtils.encodePath(content.getPath())) 109 .append("/_attribute/") 110 .append(path) 111 .append("/") 112 .append(baseName) 113 .append(uriArgument) 114 .append(extension.isEmpty() ? "" : "." + extension); 115 116 String resultUri = getUri(resultPath.toString(), content, download, absolute, internal); 117 118 Map<String, String> params = new HashMap<>(); 119 params.put("objectId", content.getId()); 120 121 if (download) 122 { 123 params.put("download", "true"); 124 } 125 126 if (contentVersion != null) 127 { 128 params.put("contentVersion", contentVersion); 129 } 130 131 return internal ? URIUtils.buildURI(resultUri, params) : URIUtils.encodeURI(resultUri, params); 132 } 133 134 @Override 135 protected String resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth) 136 { 137 try 138 { 139 Request request = ContextHelper.getRequest(_context); 140 141 AttributeInfo info = _getAttributeInfo(uri, request); 142 143 Content content = info.getContent(); 144 String path = info.getPath(); 145 146 if (content == null) 147 { 148 throw new IllegalStateException("Cannot resolve a local link to an unknown content for uri " + request.getRequestURI()); 149 } 150 151 Binary binary = content.getValue(path); 152 153 try (InputStream dataIs = binary.getInputStream()) 154 { 155 return ImageResolverHelper.resolveImageAsBase64(dataIs, binary.getMimeType(), height, width, maxHeight, maxWidth, cropHeight, cropWidth); 156 } 157 } 158 catch (Exception e) 159 { 160 throw new IllegalStateException(e); 161 } 162 } 163 164 /** 165 * Get the URI prefix 166 * @param path the resource path 167 * @param object The object 168 * @param download true if the pointed resource is to be downloaded. 169 * @param absolute true if the url must be absolute 170 * @param internal true to get an internal URI. 171 * @return the URI prefix 172 */ 173 protected String getUri(String path, Content object, boolean download, boolean absolute, boolean internal) 174 { 175 if (internal) 176 { 177 return "cocoon://" + path; 178 } 179 else 180 { 181 Request request = ContextHelper.getRequest(_context); 182 String workspaceURI = (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_URI); 183 String uriPrefix = request.getContextPath() + workspaceURI; 184 185 if (absolute && !uriPrefix.startsWith(request.getScheme())) 186 { 187 uriPrefix = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() != 80 ? ":" + request.getServerPort() : "") + uriPrefix; 188 } 189 190 return uriPrefix + path; 191 } 192 } 193 194 @Override 195 public CHECK checkLink(String uri, boolean shortTest) 196 { 197 return CHECK.SUCCESS; 198 } 199 200 @Override 201 public I18nizableText getLabel(String uri) 202 { 203 Request request = ContextHelper.getRequest(_context); 204 205 AttributeInfo info = _getAttributeInfo(uri, request); 206 207 return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_METADATA_LABEL", Collections.singletonList(info.getPath())); 208 } 209 210 /** 211 * Get attribute path and content. 212 * @param uri the attribute URI. 213 * @param request the request. 214 * @return the attribute info. 215 */ 216 protected AttributeInfo _getAttributeInfo(String uri, Request request) 217 { 218 AttributeInfo info = new AttributeInfo(); 219 220 Matcher matcher = _OBJECT_URI_PATTERN.matcher(uri); 221 Matcher contentMatcher = _CONTENT_URI_PATTERN.matcher(uri); 222 223 // Test if the URI contains an object ID. 224 if (matcher.matches()) 225 { 226 info.setPath(matcher.group(1)); 227 String objectId = matcher.group(2); 228 229 Content object = _resolver.resolveById(objectId); 230 info.setContent(object); 231 } 232 else if (contentMatcher.matches()) 233 { 234 // Legacy: handle content ID. 235 info.setPath(contentMatcher.group(1)); 236 String objectId = contentMatcher.group(2); 237 238 Content object = _resolver.resolveById(objectId); 239 info.setContent(object); 240 } 241 else 242 { 243 // URI without object ID, take the content in the request attributes. 244 info.setPath(uri); 245 info.setContent((Content) request.getAttribute(Content.class.getName())); 246 } 247 248 return info; 249 } 250 251 /** 252 * Attribute information. 253 */ 254 protected class AttributeInfo 255 { 256 private String _path; 257 private Content _content; 258 259 /** 260 * Get the attribute path. 261 * @return the attribute path 262 */ 263 public String getPath() 264 { 265 return _path; 266 } 267 268 /** 269 * Set the attribute path. 270 * @param path the attribute path to set 271 */ 272 public void setPath(String path) 273 { 274 _path = path; 275 } 276 277 /** 278 * Get the content. 279 * @return the content 280 */ 281 public Content getContent() 282 { 283 return _content; 284 } 285 286 /** 287 * Set the content. 288 * @param content the {@link Content} to set 289 */ 290 public void setContent(Content content) 291 { 292 _content = content; 293 } 294 } 295}