001/*
002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Julien Carsique
018 *
019 */
020
021package org.nuxeo.launcher.monitoring;
022
023import java.io.BufferedReader;
024import java.io.BufferedWriter;
025import java.io.IOException;
026import java.io.InputStreamReader;
027import java.io.OutputStreamWriter;
028import java.net.HttpURLConnection;
029import java.net.MalformedURLException;
030import java.net.SocketTimeoutException;
031import java.net.URL;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.launcher.config.ConfigurationGenerator;
036
037/**
038 * HTTP client monitoring Nuxeo server starting status
039 *
040 * @see org.nuxeo.ecm.core.management.statuses.StatusServlet
041 * @since 5.5
042 */
043public class StatusServletClient {
044
045    private static final Log log = LogFactory.getLog(StatusServletClient.class);
046
047    protected static final String URL_PATTERN = "runningstatus";
048
049    protected static final String POST_PARAM = "info";
050
051    protected static final String POST_PARAM_STARTED = "started";
052
053    protected static final String POST_PARAM_SUMMARY = "summary";
054
055    private static final int TIMEOUT = 1000;
056
057    private static final int SUMMARY_TIMEOUT = 2000;
058
059    private URL url;
060
061    private HttpURLConnection server;
062
063    private int timeout;
064
065    private boolean startupFine = false;
066
067    private String key;
068
069    /**
070     * Set secure key used for connection
071     *
072     * @param key any {@link String}
073     */
074    public void setKey(String key) {
075        this.key = key;
076    }
077
078    public StatusServletClient(ConfigurationGenerator configurationGenerator) {
079        final String servletURL = configurationGenerator.getUserConfig().getProperty(
080                ConfigurationGenerator.PARAM_LOOPBACK_URL)
081                + "/" + URL_PATTERN;
082        try {
083            url = new URL(servletURL);
084        } catch (MalformedURLException e) {
085            log.error("Malformed URL: " + servletURL, e);
086        }
087    }
088
089    /**
090     * @return true if Nuxeo finished starting
091     */
092    public boolean isStarted() throws SocketTimeoutException {
093        timeout = TIMEOUT;
094        return post(POST_PARAM, POST_PARAM_STARTED);
095    }
096
097    private boolean post(String postParam, String postParamStarted) throws SocketTimeoutException {
098        return post(postParam, postParamStarted, null);
099    }
100
101    /**
102     * @return true if succeed to connect on StatusServlet
103     */
104    public boolean init() throws SocketTimeoutException {
105        try {
106            timeout = TIMEOUT;
107            connect("GET");
108        } catch (SocketTimeoutException e) {
109            throw e;
110        } catch (IOException e) {
111            return false;
112        } finally {
113            disconnect();
114        }
115        return true;
116    }
117
118    protected synchronized void disconnect() {
119        if (server != null) {
120            server.disconnect();
121            server = null;
122        }
123        notifyAll();
124    }
125
126    protected synchronized void connect(String method) throws IOException {
127        while (server != null) {
128            try {
129                wait();
130            } catch (InterruptedException e) {
131                Thread.currentThread().interrupt();
132                throw new RuntimeException(e);
133            }
134        }
135        server = (HttpURLConnection) url.openConnection();
136        server.setConnectTimeout(timeout);
137        server.setReadTimeout(timeout);
138        server.setDoInput(true);
139        server.setDoOutput(true);
140        server.setRequestMethod(method);
141        server.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
142        server.connect();
143    }
144
145    /**
146     * @return Nuxeo server startup summary (components loading status)
147     */
148    public String getStartupSummary() throws SocketTimeoutException {
149        timeout = SUMMARY_TIMEOUT;
150        StringBuilder sb = new StringBuilder();
151        startupFine = post(POST_PARAM, POST_PARAM_SUMMARY, sb);
152        return sb.toString();
153    }
154
155    protected boolean post(String param, String value, StringBuilder response) throws SocketTimeoutException {
156        String post = param + "=" + value;
157        post += "&key=" + key;
158        try {
159            connect("POST");
160            try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(server.getOutputStream()))) {
161                bw.write(post, 0, post.length());
162            }
163            return getResponse(response);
164        } catch (SocketTimeoutException e) {
165            throw e;
166        } catch (IOException e) {
167            return false;
168        } finally {
169            disconnect();
170        }
171    }
172
173    protected boolean getResponse(StringBuilder response) throws IOException {
174        try (BufferedReader s = new BufferedReader(new InputStreamReader(server.getInputStream()))) {
175            // First line is a status (true or false)
176            boolean answer = Boolean.parseBoolean(s.readLine());
177            String line;
178            // Next (if exists) is a response body
179            while ((line = s.readLine()) != null) {
180                if (response != null) {
181                    response.append(line).append('\n');
182                }
183            }
184            return answer;
185        }
186    }
187
188    /**
189     * Return detected status of Nuxeo server by last call to {@link #getStartupSummary()}
190     *
191     * @return true if everything is fine; false is there was any error or status is unknown
192     */
193    public boolean isStartupFine() {
194        return startupFine;
195    }
196}