001    package com.nimbusds.jose;
002    
003    
004    import java.io.UnsupportedEncodingException;
005    
006    import java.text.ParseException;
007    
008    import net.minidev.json.JSONObject;
009    
010    import net.jcip.annotations.Immutable;
011    
012    import com.nimbusds.jose.util.Base64URL;
013    import com.nimbusds.jose.util.JSONObjectUtils;
014    
015    
016    /**
017     * Payload with JSON object, string, byte array and Base64URL views. Represents
018     * the original object that was signed with JWS or encrypted with JWE. This 
019     * class is immutable.
020     *
021     * <p>Non-initial views are created on demand to conserve resources.
022     *
023     * <p>UTF-8 is the character set for all string from / to byte array 
024     * conversions.
025     *
026     * <p>Conversion relations:
027     *
028     * <pre>
029     * JSONObject <=> String <=> Base64URL
030     *                       <=> byte[]
031     * </pre>
032     *
033     * @author Vladimir Dzhuvinov
034     * @version $version$ (2012-10-23)
035     */
036    @Immutable
037    public class Payload {
038    
039    
040            /**
041             * Enumeration of the original data types used to create a 
042             * {@link Payload}.
043             */
044            public static enum Origin {
045            
046                    
047                    /**
048                     * The payload was created from a JSON object.
049                     */
050                    JSON,
051                    
052                    
053                    /**
054                     * The payload was created from a string.
055                     */
056                    STRING,
057                    
058                    
059                    /**
060                     * The payload was created from a byte array.
061                     */
062                    BYTE_ARRAY,
063                    
064                    
065                    /**
066                     * The payload was created from a Base64URL-encoded object.
067                     */
068                    BASE64URL;
069            }
070            
071    
072            /**
073             * UTF-8 is the character set for all string from / to byte array
074             * conversions.
075             */
076            private static final String CHARSET = "UTF-8";
077            
078            
079            /**
080             * The original payload data type.
081             */
082            private Origin origin;
083            
084            
085            /**
086             * The JSON object view.
087             */
088            private JSONObject jsonView = null;
089            
090            
091            /**
092             * The string view.
093             */
094            private String stringView = null;
095            
096            
097            /**
098             * The byte array view.
099             */
100            private byte[] bytesView = null;
101            
102            
103            /**
104             * The Base64URL view.
105             */
106            private Base64URL base64URLView = null;
107            
108            
109            /**
110             * Converts a byte array to a string using {@link #CHARSET}.
111             *
112             * @param bytes The byte array to convert. May be {@code null}.
113             *
114             * @return The resulting string, {@code null} if conversion failed.
115             */
116            private static String byteArrayToString(final byte[] bytes) {
117            
118                    if (bytes == null)
119                            return null;
120            
121                    try {
122                            return new String(bytes, CHARSET);
123                            
124                    } catch (UnsupportedEncodingException e) {
125                    
126                            // UTF-8 should always be supported
127                            return null;
128                    }
129            }
130            
131            
132            /**
133             * Converts a string to a byte array using {@link #CHARSET}.
134             *
135             * @param stirng The string to convert. May be {@code null}.
136             *
137             * @return The resulting byte array, {@code null} if conversion failed.
138             */
139            private static byte[] stringToByteArray(final String string) {
140            
141                    if (string == null)
142                            return null;
143            
144                    try {
145                            return string.getBytes(CHARSET);
146                            
147                    } catch (UnsupportedEncodingException e) {
148                    
149                            // UTF-8 should always be supported
150                            return null;
151                    }
152            }
153            
154            
155            /**
156             * Creates a new payload from the specified JSON object.
157             *
158             * @param json The JSON object representing the payload. Must not be
159             *             {@code null}.
160             */
161            public Payload(final JSONObject json) {
162            
163                    if (json == null)
164                            throw new IllegalArgumentException("The JSON object must not be null");
165                            
166                    jsonView = json;
167                    
168                    origin = Origin.JSON;
169            }
170            
171            
172            /**
173             * Creates a new payload from the specified string.
174             *
175             * @param string The string representing the payload. Must not be 
176             *               {@code null}.
177             */
178            public Payload(final String string) {
179            
180                    if (string == null)
181                            throw new IllegalArgumentException("The string must not be null");
182                            
183                    stringView = string;
184                    
185                    origin = Origin.STRING;
186            }
187            
188            
189            /**
190             * Creates a new payload from the specified byte array.
191             *
192             * @param bytes The byte array representing the payload. Must not be 
193             *              {@code null}.
194             */
195            public Payload(final byte[] bytes) {
196            
197                    if (bytes == null)
198                            throw new IllegalArgumentException("The byte array must not be null");
199                            
200                    bytesView = bytes;
201                    
202                    origin = Origin.BYTE_ARRAY;
203            }
204            
205            
206            /**
207             * Creates a new payload from the specified Base64URL-encoded object.
208             *
209             * @param base64URL The Base64URL-encoded object representing the 
210             *                  payload. Must not be {@code null}.
211             */
212            public Payload(final Base64URL base64URL) {
213            
214                    if (base64URL == null)
215                            throw new IllegalArgumentException("The Base64URL-encoded object must not be null");
216                            
217                    base64URLView = base64URL;
218                    
219                    origin = Origin.BASE64URL;
220            }
221            
222            
223            /**
224             * Gets the original data type used to create this payload.
225             *
226             * @return The payload origin.
227             */
228            public Origin getOrigin() {
229            
230                    return origin;
231            }
232            
233            
234            /**
235             * Returns a JSON object view of this payload.
236             *
237             * @return The JSON object view, {@code null} if the payload couldn't
238             *         be converted to a JSON object.
239             */
240            public JSONObject toJSONObject() {
241            
242                    if (jsonView != null)
243                            return jsonView;
244                    
245                    // Convert
246                    if (stringView != null) {
247                    
248                            try {
249                                    jsonView = JSONObjectUtils.parseJSONObject(stringView);
250                                    
251                            } catch (ParseException e) {
252                            
253                                    // jsonView remains null
254                            }
255                    }
256                    else if (bytesView != null) {
257                    
258                            stringView = byteArrayToString(bytesView);
259                            
260                            try {
261                                    jsonView = JSONObjectUtils.parseJSONObject(stringView);
262                                    
263                            } catch (ParseException e) {
264                            
265                                    // jsonView remains null
266                            }
267                    }
268                    else if (base64URLView != null) {
269                            
270                            stringView = base64URLView.decodeToString();
271                            
272                            try {
273                                    jsonView = JSONObjectUtils.parseJSONObject(stringView);
274                                    
275                            } catch (ParseException e) {
276                            
277                                    // jsonView remains null
278                            }
279                    }
280                    
281                    return jsonView;
282            }
283            
284            
285            /**
286             * Returns a string view of this payload.
287             *
288             * @return The string view.
289             */
290            public String toString() {
291            
292                    if (stringView != null)
293                            return stringView;
294    
295                    // Convert
296                    if (jsonView != null) {
297                    
298                            stringView = jsonView.toString();
299                    }
300                    else if (bytesView != null) {
301                    
302                            stringView = byteArrayToString(bytesView);
303                    }
304                    else if (base64URLView != null) {
305                            
306                            stringView = base64URLView.decodeToString();
307                    }
308                    
309                    return stringView;
310            }
311            
312            
313            /**
314             * Returns a byte array view of this payload.
315             *
316             * @return The byte array view.
317             */
318            public byte[] toBytes() {
319            
320                    if (bytesView != null)
321                            return bytesView;
322                    
323                    // Convert
324                    if (stringView != null) {
325                    
326                            bytesView = stringToByteArray(stringView);
327                    }               
328                    else if (jsonView != null) {
329                    
330                            stringView = jsonView.toString();
331                            bytesView = stringToByteArray(stringView);
332                    }
333                    else if (base64URLView != null) {
334                    
335                            bytesView = base64URLView.decode();
336                    }
337                    
338                    return bytesView;       
339            }
340            
341            
342            /**
343             * Returns a Base64URL view of this payload.
344             *
345             * @return The Base64URL view.
346             */
347            public Base64URL toBase64URL() {
348            
349                    if (base64URLView != null)
350                            return base64URLView;
351                    
352                    // Convert
353                    
354                    if (stringView != null) {
355                    
356                            base64URLView = Base64URL.encode(stringView);
357                    
358                    }
359                    else if (bytesView != null) {
360                    
361                            base64URLView = Base64URL.encode(bytesView);
362                            
363                    }
364                    else if (jsonView != null) {
365                    
366                            stringView = jsonView.toString();
367                            base64URLView = Base64URL.encode(stringView);
368                    }
369                    
370                    return base64URLView;
371            }
372    }