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