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 }