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    }