001 package ezvcard.io.scribe; 002 003 import java.util.List; 004 005 import ezvcard.VCard; 006 import ezvcard.VCardDataType; 007 import ezvcard.VCardVersion; 008 import ezvcard.io.CannotParseException; 009 import ezvcard.io.html.HCardElement; 010 import ezvcard.io.json.JCardValue; 011 import ezvcard.io.xml.XCardElement; 012 import ezvcard.parameter.Encoding; 013 import ezvcard.parameter.MediaTypeParameter; 014 import ezvcard.parameter.VCardParameters; 015 import ezvcard.property.BinaryProperty; 016 import ezvcard.util.DataUri; 017 import ezvcard.util.org.apache.commons.codec.binary.Base64; 018 019 /* 020 Copyright (c) 2013, Michael Angstadt 021 All rights reserved. 022 023 Redistribution and use in source and binary forms, with or without 024 modification, are permitted provided that the following conditions are met: 025 026 1. Redistributions of source code must retain the above copyright notice, this 027 list of conditions and the following disclaimer. 028 2. Redistributions in binary form must reproduce the above copyright notice, 029 this list of conditions and the following disclaimer in the documentation 030 and/or other materials provided with the distribution. 031 032 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 033 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 034 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 035 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 036 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 037 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 038 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 039 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 040 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 041 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 042 */ 043 044 /** 045 * Marshals properties that have binary data. 046 * @author Michael Angstadt 047 * @param <T> the property class 048 * @param <U> the media type class 049 */ 050 public abstract class BinaryPropertyScribe<T extends BinaryProperty<U>, U extends MediaTypeParameter> extends VCardPropertyScribe<T> { 051 public BinaryPropertyScribe(Class<T> clazz, String propertyName) { 052 super(clazz, propertyName); 053 } 054 055 @Override 056 protected VCardDataType _defaultDataType(VCardVersion version) { 057 switch (version) { 058 case V2_1: 059 case V3_0: 060 return null; 061 case V4_0: 062 return VCardDataType.URI; 063 } 064 return null; 065 } 066 067 @Override 068 protected VCardDataType _dataType(T property, VCardVersion version) { 069 if (property.getUrl() != null) { 070 switch (version) { 071 case V2_1: 072 return VCardDataType.URL; 073 case V3_0: 074 case V4_0: 075 return VCardDataType.URI; 076 } 077 } 078 079 if (property.getData() != null) { 080 switch (version) { 081 case V2_1: 082 case V3_0: 083 return null; 084 case V4_0: 085 return VCardDataType.URI; 086 } 087 } 088 089 return _defaultDataType(version); 090 } 091 092 @Override 093 protected void _prepareParameters(T property, VCardParameters copy, VCardVersion version, VCard vcard) { 094 MediaTypeParameter contentType = property.getContentType(); 095 if (contentType == null) { 096 contentType = new MediaTypeParameter(null, null, null); 097 } 098 099 if (property.getUrl() != null) { 100 copy.setEncoding(null); 101 102 switch (version) { 103 case V2_1: 104 copy.setType(contentType.getValue()); 105 copy.setMediaType(null); 106 return; 107 case V3_0: 108 copy.setType(contentType.getValue()); 109 copy.setMediaType(null); 110 return; 111 case V4_0: 112 copy.setMediaType(contentType.getMediaType()); 113 return; 114 } 115 } 116 117 if (property.getData() != null) { 118 copy.setMediaType(null); 119 120 switch (version) { 121 case V2_1: 122 copy.setEncoding(Encoding.BASE64); 123 copy.setType(contentType.getValue()); 124 return; 125 case V3_0: 126 copy.setEncoding(Encoding.B); 127 copy.setType(contentType.getValue()); 128 return; 129 case V4_0: 130 copy.setEncoding(null); 131 //don't null out TYPE, it could be set to "home", "work", etc 132 return; 133 } 134 } 135 } 136 137 @Override 138 protected String _writeText(T property, VCardVersion version) { 139 return write(property, version); 140 } 141 142 @Override 143 protected T _parseText(String value, VCardDataType dataType, VCardVersion version, VCardParameters parameters, List<String> warnings) { 144 value = unescape(value); 145 return parse(value, dataType, parameters, version, warnings); 146 } 147 148 @Override 149 protected void _writeXml(T property, XCardElement parent) { 150 parent.append(VCardDataType.URI, write(property, parent.version())); 151 } 152 153 @Override 154 protected T _parseXml(XCardElement element, VCardParameters parameters, List<String> warnings) { 155 String value = element.first(VCardDataType.URI); 156 if (value != null) { 157 return parse(value, VCardDataType.URI, parameters, element.version(), warnings); 158 } 159 160 throw missingXmlElements(VCardDataType.URI); 161 } 162 163 @Override 164 protected T _parseHtml(HCardElement element, List<String> warnings) { 165 String elementName = element.tagName(); 166 if (!"object".equals(elementName)) { 167 throw new CannotParseException(1, elementName); 168 } 169 170 String data = element.absUrl("data"); 171 if (data.length() == 0) { 172 throw new CannotParseException(2); 173 } 174 175 try { 176 DataUri uri = new DataUri(data); 177 U mediaType = _buildMediaTypeObj(uri.getContentType()); 178 179 return _newInstance(uri.getData(), mediaType); 180 } catch (IllegalArgumentException e) { 181 //not a data URI 182 U mediaType = null; 183 String type = element.attr("type"); 184 if (type.length() > 0) { 185 mediaType = _buildMediaTypeObj(type); 186 } 187 188 return _newInstance(data, mediaType); 189 } 190 } 191 192 @Override 193 protected JCardValue _writeJson(T property) { 194 return JCardValue.single(write(property, VCardVersion.V4_0)); 195 } 196 197 @Override 198 protected T _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, List<String> warnings) { 199 String valueStr = value.asSingle(); 200 return parse(valueStr, dataType, parameters, VCardVersion.V4_0, warnings); 201 } 202 203 /** 204 * Called if the unmarshalling code cannot determine how to unmarshal the 205 * value. 206 * @param value the value 207 * @param version the version of the vCard 208 * @param warnings the warnings 209 * @param contentType the content type of the resource of null if unknown 210 */ 211 protected T cannotUnmarshalValue(String value, VCardVersion version, List<String> warnings, U contentType) { 212 switch (version) { 213 case V2_1: 214 case V3_0: 215 if (value.startsWith("http")) { 216 return _newInstance(value, contentType); 217 } 218 return _newInstance(Base64.decodeBase64(value), contentType); 219 case V4_0: 220 return _newInstance(value, contentType); 221 } 222 return null; 223 } 224 225 /** 226 * Builds a {@link MediaTypeParameter} object based on the information in 227 * the MEDIATYPE parameter or data URI of 4.0 vCards. 228 * @param mediaType the media type string (e.g. "image/jpeg") 229 * @return the parameter object 230 */ 231 protected abstract U _buildMediaTypeObj(String mediaType); 232 233 /** 234 * Builds a {@link MediaTypeParameter} object based on the value of the TYPE 235 * parameter in 2.1/3.0 vCards. 236 * @param type the TYPE value 237 * @return the parameter object 238 */ 239 protected abstract U _buildTypeObj(String type); 240 241 protected abstract T _newInstance(String uri, U contentType); 242 243 protected abstract T _newInstance(byte data[], U contentType); 244 245 protected U parseContentType(VCardParameters parameters, VCardVersion version) { 246 switch (version) { 247 case V2_1: 248 case V3_0: 249 //get the TYPE parameter 250 String type = parameters.getType(); 251 if (type != null) { 252 return _buildTypeObj(type); 253 } 254 break; 255 case V4_0: 256 //get the MEDIATYPE parameter 257 String mediaType = parameters.getMediaType(); 258 if (mediaType != null) { 259 return _buildMediaTypeObj(mediaType); 260 } 261 break; 262 } 263 return null; 264 } 265 266 private T parse(String value, VCardDataType dataType, VCardParameters parameters, VCardVersion version, List<String> warnings) { 267 U contentType = parseContentType(parameters, version); 268 269 switch (version) { 270 case V2_1: 271 case V3_0: 272 //parse as URL 273 if (dataType == VCardDataType.URL || dataType == VCardDataType.URI) { 274 return _newInstance(value, contentType); 275 } 276 277 //parse as binary 278 Encoding encodingSubType = parameters.getEncoding(); 279 if (encodingSubType == Encoding.BASE64 || encodingSubType == Encoding.B) { 280 return _newInstance(Base64.decodeBase64(value), contentType); 281 } 282 283 break; 284 case V4_0: 285 try { 286 //parse as data URI 287 DataUri uri = new DataUri(value); 288 contentType = _buildMediaTypeObj(uri.getContentType()); 289 return _newInstance(uri.getData(), contentType); 290 } catch (IllegalArgumentException e) { 291 //not a data URI 292 } 293 break; 294 } 295 296 return cannotUnmarshalValue(value, version, warnings, contentType); 297 } 298 299 private String write(T property, VCardVersion version) { 300 String url = property.getUrl(); 301 if (url != null) { 302 return url; 303 } 304 305 byte data[] = property.getData(); 306 if (data != null) { 307 switch (version) { 308 case V2_1: 309 case V3_0: 310 return new String(Base64.encodeBase64(data)); 311 case V4_0: 312 U contentType = property.getContentType(); 313 String mediaType = (contentType == null || contentType.getMediaType() == null) ? "application/octet-stream" : contentType.getMediaType(); 314 return new DataUri(mediaType, data).toString(); 315 } 316 } 317 318 return ""; 319 } 320 }