xhr4j/src/main/java/org/netbeans/html/xhr4j/LoadJSON.java
author Jaroslav Tulach <jtulach@netbeans.org>
Mon, 29 Feb 2016 05:25:31 +0100
branchxhr4j
changeset 1057 b547f8f663f5
parent 940 ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java@bdec4103bdb2
child 1059 c5223a12e761
permissions -rw-r--r--
#257849: xhr4j module uses java.net package to handle @OnReceive requests to workaround CORS limitations
jaroslav@37
     1
/**
jaroslav@358
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jaroslav@37
     3
 *
jaroslav@551
     4
 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
jaroslav@37
     5
 *
jaroslav@358
     6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
jaroslav@358
     7
 * Other names may be trademarks of their respective owners.
jaroslav@37
     8
 *
jaroslav@358
     9
 * The contents of this file are subject to the terms of either the GNU
jaroslav@358
    10
 * General Public License Version 2 only ("GPL") or the Common
jaroslav@358
    11
 * Development and Distribution License("CDDL") (collectively, the
jaroslav@358
    12
 * "License"). You may not use this file except in compliance with the
jaroslav@358
    13
 * License. You can obtain a copy of the License at
jaroslav@358
    14
 * http://www.netbeans.org/cddl-gplv2.html
jaroslav@358
    15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
jaroslav@358
    16
 * specific language governing permissions and limitations under the
jaroslav@358
    17
 * License.  When distributing the software, include this License Header
jaroslav@358
    18
 * Notice in each file and include the License file at
jaroslav@358
    19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
jaroslav@358
    20
 * particular file as subject to the "Classpath" exception as provided
jaroslav@358
    21
 * by Oracle in the GPL Version 2 section of the License file that
jaroslav@358
    22
 * accompanied this code. If applicable, add the following below the
jaroslav@358
    23
 * License Header, with the fields enclosed by brackets [] replaced by
jaroslav@358
    24
 * your own identifying information:
jaroslav@358
    25
 * "Portions Copyrighted [year] [name of copyright owner]"
jaroslav@358
    26
 *
jaroslav@358
    27
 * Contributor(s):
jaroslav@358
    28
 *
jaroslav@358
    29
 * The Original Software is NetBeans. The Initial Developer of the Original
jaroslav@551
    30
 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
jaroslav@358
    31
 *
jaroslav@358
    32
 * If you wish your version of this file to be governed by only the CDDL
jaroslav@358
    33
 * or only the GPL Version 2, indicate your decision by adding
jaroslav@358
    34
 * "[Contributor] elects to include this software in this distribution
jaroslav@358
    35
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
jaroslav@358
    36
 * single choice of license, a recipient has the option to distribute
jaroslav@358
    37
 * your version of this file under either the CDDL, the GPL Version 2 or
jaroslav@358
    38
 * to extend the choice of license to its licensees as provided above.
jaroslav@358
    39
 * However, if you add GPL Version 2 code and therefore, elected the GPL
jaroslav@358
    40
 * Version 2 license, then the option applies only if the new code is
jaroslav@358
    41
 * made subject to such option by the copyright holder.
jaroslav@37
    42
 */
jtulach@1057
    43
package org.netbeans.html.xhr4j;
jaroslav@37
    44
jaroslav@37
    45
import java.io.IOException;
jaroslav@60
    46
import java.io.InputStream;
jaroslav@37
    47
import java.io.InputStreamReader;
jaroslav@75
    48
import java.io.OutputStream;
jaroslav@37
    49
import java.io.PushbackInputStream;
jaroslav@37
    50
import java.io.Reader;
jtulach@1057
    51
import java.io.UnsupportedEncodingException;
jaroslav@74
    52
import java.net.HttpURLConnection;
jaroslav@37
    53
import java.net.URL;
jaroslav@74
    54
import java.net.URLConnection;
jtulach@1057
    55
import java.util.concurrent.Callable;
jaroslav@37
    56
import java.util.concurrent.Executor;
jaroslav@37
    57
import java.util.concurrent.Executors;
jaroslav@158
    58
import java.util.concurrent.ThreadFactory;
jaroslav@37
    59
import java.util.logging.Logger;
jaroslav@131
    60
import net.java.html.js.JavaScriptBody;
jtulach@940
    61
import org.netbeans.html.json.spi.JSONCall;
jaroslav@37
    62
jaroslav@54
    63
/** This is an implementation package - just
jaroslav@54
    64
 * include its JAR on classpath and use official {@link Context} API
jaroslav@54
    65
 * to access the functionality.
jaroslav@37
    66
 *
jtulach@790
    67
 * @author Jaroslav Tulach
jaroslav@37
    68
 */
jaroslav@37
    69
final class LoadJSON implements Runnable {
jaroslav@446
    70
    private static final Logger LOG = Logger.getLogger(LoadJSON.class.getName());
jaroslav@158
    71
    private static final Executor REQ = Executors.newCachedThreadPool(new ThreadFactory() {
jaroslav@158
    72
        @Override
jaroslav@158
    73
        public Thread newThread(Runnable runnable) {
jaroslav@158
    74
            Thread thread = Executors.defaultThreadFactory().newThread(runnable);
jaroslav@158
    75
            thread.setDaemon(true);
jtulach@1057
    76
            thread.setName("xhr4j daemon");
jaroslav@158
    77
            return thread;
jaroslav@158
    78
        }
jaroslav@158
    79
    });
jaroslav@37
    80
jaroslav@37
    81
    private final JSONCall call;
jaroslav@37
    82
    private final URL base;
jaroslav@37
    83
jaroslav@37
    84
jaroslav@37
    85
    private LoadJSON(JSONCall call) {
jaroslav@37
    86
        this.call = call;
jaroslav@446
    87
        this.base = null;
jaroslav@37
    88
    }
jaroslav@37
    89
jaroslav@37
    90
    public static void loadJSON(JSONCall call) {
jaroslav@258
    91
        assert !"WebSocket".equals(call.getMethod());
jaroslav@258
    92
        REQ.execute(new LoadJSON((call)));
jaroslav@37
    93
    }
jaroslav@37
    94
jaroslav@37
    95
    @Override
jaroslav@37
    96
    public void run() {
jaroslav@37
    97
        final String url;
jaroslav@446
    98
        Throwable error = null;
jaroslav@446
    99
        Object json = null;
jtulach@940
   100
jaroslav@37
   101
        if (call.isJSONP()) {
jaroslav@37
   102
            url = call.composeURL("dummy");
jaroslav@37
   103
        } else {
jaroslav@37
   104
            url = call.composeURL(null);
jaroslav@37
   105
        }
jaroslav@37
   106
        try {
jaroslav@37
   107
            final URL u = new URL(base, url.replace(" ", "%20"));
jaroslav@74
   108
            URLConnection conn = u.openConnection();
jaroslav@527
   109
            if (call.isDoOutput()) {
jaroslav@527
   110
                conn.setDoOutput(true);
jaroslav@527
   111
            }
jtulach@940
   112
            String h = call.getHeaders();
jtulach@940
   113
            if (h != null) {
jtulach@940
   114
                int pos = 0;
jtulach@940
   115
                while (pos < h.length()) {
jtulach@940
   116
                    int tagEnd = h.indexOf(':', pos);
jtulach@940
   117
                    if (tagEnd == -1) {
jtulach@940
   118
                        break;
jtulach@940
   119
                    }
jtulach@940
   120
                    int r = h.indexOf('\r', tagEnd);
jtulach@940
   121
                    int n = h.indexOf('\n', tagEnd);
jtulach@940
   122
                    if (r == -1) {
jtulach@940
   123
                        r = h.length();
jtulach@940
   124
                    }
jtulach@940
   125
                    if (n == -1) {
jtulach@940
   126
                        n = h.length();
jtulach@940
   127
                    }
jtulach@940
   128
                    String key = h.substring(pos, tagEnd).trim();
jtulach@940
   129
                    String val = h.substring(tagEnd + 1, Math.min(r, n)).trim();
jtulach@940
   130
                    conn.setRequestProperty(key, val);;
jtulach@940
   131
                    pos = Math.max(r, n);
jtulach@940
   132
                }
jtulach@940
   133
            }
jaroslav@527
   134
            if (call.getMethod() != null && conn instanceof HttpURLConnection) {
jaroslav@527
   135
                ((HttpURLConnection) conn).setRequestMethod(call.getMethod());
jaroslav@527
   136
            }
jaroslav@527
   137
            if (call.isDoOutput()) {
jaroslav@527
   138
                final OutputStream os = conn.getOutputStream();
jaroslav@527
   139
                call.writeData(os);
jaroslav@527
   140
                os.flush();
jaroslav@74
   141
            }
jaroslav@74
   142
            final PushbackInputStream is = new PushbackInputStream(
jaroslav@74
   143
                conn.getInputStream(), 1
jaroslav@74
   144
            );
jtulach@745
   145
            boolean[] arrayOrString = { false, false };
jtulach@745
   146
            detectJSONType(call.isJSONP(), is, arrayOrString);
jtulach@1057
   147
            String response = readStream(is);
jtulach@1057
   148
            if (call.isJSONP()) {
jtulach@1057
   149
                response = '(' + response;
jaroslav@73
   150
            }
jtulach@1057
   151
            json = new Result(response, arrayOrString[0], arrayOrString[1]);
jaroslav@73
   152
        } catch (IOException ex) {
jaroslav@37
   153
            error = ex;
jaroslav@37
   154
        } finally {
jaroslav@446
   155
            if (error != null) {
jaroslav@446
   156
                call.notifyError(error);
jaroslav@446
   157
            } else {
jaroslav@446
   158
                call.notifySuccess(json);
jaroslav@446
   159
            }
jaroslav@37
   160
        }
jaroslav@37
   161
    }
jaroslav@37
   162
jtulach@1057
   163
    private static final class Result implements Callable<Object> {
jtulach@1057
   164
        private final String response;
jtulach@1057
   165
        private final boolean array;
jtulach@1057
   166
        private final boolean plain;
jtulach@1057
   167
jtulach@1057
   168
        Result(String response, boolean array, boolean plain) {
jtulach@1057
   169
            this.response = response;
jtulach@1057
   170
            this.array = array;
jtulach@1057
   171
            this.plain = plain;
jtulach@1057
   172
        }
jtulach@1057
   173
jtulach@1057
   174
        @Override
jtulach@1057
   175
        public Object call() throws Exception {
jtulach@1057
   176
            if (plain) {
jtulach@1057
   177
                return response;
jtulach@1057
   178
            } else {
jtulach@1057
   179
                if (array) {
jtulach@1057
   180
                    Object r = parse(response);
jtulach@1057
   181
                    Object[] arr = r instanceof Object[] ? (Object[])r : new Object[] { r };
jtulach@1057
   182
                    for (int i = 0; i < arr.length; i++) {
jtulach@1057
   183
                        arr[i] = new JSObjToStr(response, arr[i]);
jtulach@1057
   184
                    }
jtulach@1057
   185
                    return arr;
jtulach@1057
   186
                } else {
jtulach@1057
   187
                    return new JSObjToStr(response, parse(response));
jtulach@1057
   188
                }
jtulach@1057
   189
            }
jtulach@1057
   190
        }
jtulach@1057
   191
    }
jtulach@1057
   192
    private static final class JSObjToStr {
jtulach@1057
   193
        final String str;
jtulach@1057
   194
        final Object obj;
jtulach@1057
   195
jtulach@1057
   196
        public JSObjToStr(Object str, Object obj) {
jtulach@1057
   197
            this.str = str == null ? "" : str.toString();
jtulach@1057
   198
            this.obj = obj;
jtulach@1057
   199
        }
jtulach@1057
   200
jtulach@1057
   201
        @Override
jtulach@1057
   202
        public String toString() {
jtulach@1057
   203
            return str;
jtulach@1057
   204
        }
jtulach@1057
   205
    }
jtulach@1057
   206
jtulach@1057
   207
    static String readStream(InputStream is) throws IOException, UnsupportedEncodingException {
jtulach@1057
   208
        Reader r = new InputStreamReader(is, "UTF-8");
jtulach@1057
   209
        StringBuilder sb = new StringBuilder();
jtulach@1057
   210
        char[] arr = new char[4096];
jtulach@1057
   211
        for (;;) {
jtulach@1057
   212
            int len = r.read(arr);
jtulach@1057
   213
            if (len == -1) {
jtulach@1057
   214
                break;
jtulach@1057
   215
            }
jtulach@1057
   216
            sb.append(arr, 0, len);
jtulach@1057
   217
        }
jtulach@1057
   218
        return sb.toString();
jtulach@1057
   219
    }
jtulach@1057
   220
jtulach@745
   221
    private static void detectJSONType(boolean skipAnything, final PushbackInputStream is, boolean[] arrayOrString) throws IOException {
jtulach@745
   222
        for (;;) {
jtulach@745
   223
            int ch = is.read();
jtulach@745
   224
            if (ch == -1) {
jtulach@745
   225
                arrayOrString[1] = true;
jtulach@745
   226
                break;
jtulach@745
   227
            }
jtulach@745
   228
            if (Character.isWhitespace(ch)) {
jtulach@745
   229
                continue;
jtulach@745
   230
            }
jtulach@745
   231
jtulach@745
   232
            if (ch == '[') {
jtulach@745
   233
                is.unread(ch);
jtulach@745
   234
                arrayOrString[0] = true;
jtulach@745
   235
                break;
jtulach@745
   236
            }
jtulach@745
   237
            if (ch == '{') {
jtulach@745
   238
                is.unread(ch);
jtulach@745
   239
                break;
jtulach@745
   240
            }
jtulach@745
   241
            if (!skipAnything) {
jtulach@745
   242
                is.unread(ch);
jtulach@745
   243
                arrayOrString[1] = true;
jtulach@745
   244
                break;
jtulach@745
   245
            }
jtulach@745
   246
        }
jtulach@745
   247
    }
jtulach@745
   248
jaroslav@446
   249
    @JavaScriptBody(args = {"object", "property"},
jtulach@1057
   250
        body
jtulach@1057
   251
        = "if (property === null) return object;\n"
jtulach@1057
   252
        + "if (object === null) return null;\n"
jtulach@1057
   253
        + "var p = object[property]; return p ? p : null;"
jaroslav@446
   254
    )
jaroslav@446
   255
    private static Object getProperty(Object object, String property) {
jaroslav@446
   256
        return null;
jaroslav@446
   257
    }
jtulach@940
   258
jtulach@1057
   259
    @JavaScriptBody(args = {"s"}, body = "return eval('(' + s + ')');")
jtulach@1057
   260
    static Object parse(String s) {
jtulach@1057
   261
        throw new IllegalStateException("No parser context for " + s);
jtulach@1057
   262
    }
jtulach@1057
   263
jtulach@1057
   264
    static void extractJSON(Object js, String[] props, Object[] values) {
jtulach@1057
   265
        if (js instanceof JSObjToStr) {
jtulach@1057
   266
            js = ((JSObjToStr)js).obj;
jtulach@1057
   267
        }
jtulach@1057
   268
        for (int i = 0; i < props.length; i++) {
jtulach@1057
   269
            values[i] = getProperty(js, props[i]);
jaroslav@60
   270
        }
jaroslav@60
   271
    }
jtulach@1057
   272
jaroslav@37
   273
}