/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jumpmind.symmetric.transport.http;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.io.IOUtils;
import org.jumpmind.exception.IoException;
import org.jumpmind.symmetric.io.IoConstants;
import org.jumpmind.symmetric.model.ChannelMap;
import org.jumpmind.symmetric.model.Node;
import org.jumpmind.symmetric.service.IConfigurationService;
import org.jumpmind.symmetric.transport.IOutgoingWithResponseTransport;
import org.jumpmind.symmetric.util.SymmetricUtils;
import org.jumpmind.symmetric.web.WebConstants;
public class HttpOutgoingTransport implements IOutgoingWithResponseTransport {
static final String CRLF = "\r\n";
private String boundary;
private URL url;
private OutputStream os;
private BufferedWriter writer;
private BufferedReader reader;
private HttpURLConnection connection;
private int httpTimeout;
private boolean useCompression;
private int compressionStrategy;
private int compressionLevel;
private String basicAuthUsername;
private String basicAuthPassword;
private boolean streamOutputEnabled = false;
private int streamOutputChunkSize = 30720;
private boolean fileUpload = false;
public HttpOutgoingTransport(URL url, int httpTimeout, boolean useCompression,
int compressionStrategy, int compressionLevel, String basicAuthUsername,
String basicAuthPassword, boolean streamOutputEnabled, int streamOutputSize,
boolean fileUpload) {
this.url = url;
this.httpTimeout = httpTimeout;
this.useCompression = useCompression;
this.compressionLevel = compressionLevel;
this.compressionStrategy = compressionStrategy;
this.basicAuthUsername = basicAuthUsername;
this.basicAuthPassword = basicAuthPassword;
this.streamOutputChunkSize = streamOutputSize;
this.streamOutputEnabled = streamOutputEnabled;
this.fileUpload = fileUpload;
}
public void close() {
closeWriter(true);
closeOutputStream(true);
closeReader();
if (connection != null) {
connection.disconnect();
connection = null;
}
}
private void closeReader() {
if (reader != null) {
IOUtils.closeQuietly(reader);
reader = null;
}
}
private void closeOutputStream(boolean closeQuietly) {
if (os != null) {
try {
if (fileUpload) {
IOUtils.write(CRLF + "--" + boundary + "--" + CRLF, os);
}
os.flush();
} catch (IOException ex) {
throw new IoException(ex);
} finally {
if (closeQuietly) {
IOUtils.closeQuietly(os);
} else {
try {
os.close();
} catch (IOException ex) {
throw new IoException(ex);
}
}
os = null;
}
}
}
private void closeWriter(boolean closeQuietly) {
if (writer != null) {
try {
if (fileUpload) {
IOUtils.write(CRLF + "--" + boundary + "--" + CRLF, os);
}
writer.flush();
} catch (IOException ex) {
throw new IoException(ex);
} finally {
if (closeQuietly) {
IOUtils.closeQuietly(writer);
} else {
try {
writer.close();
} catch (IOException ex) {
throw new IoException(ex);
}
}
writer = null;
os = null;
}
}
}
/**
* Before streaming data to the remote node, make sure it is ok to. We have
* found that we can be more efficient on a push by relying on HTTP
* keep-alive.
*
* @throws IOException
* @throws {@link ConnectionRejectedException}
* @throws {@link AuthenticationException}
*/
private HttpURLConnection requestReservation() {
try {
connection = HttpTransportManager.openConnection(url, basicAuthUsername,
basicAuthPassword);
connection.setUseCaches(false);
connection.setConnectTimeout(httpTimeout);
connection.setReadTimeout(httpTimeout);
connection.setRequestMethod("HEAD");
SymmetricUtils.analyzeResponseCode(connection.getResponseCode());
} catch (IOException ex) {
throw new IoException(ex);
}
return connection;
}
public OutputStream openStream() {
try {
connection = HttpTransportManager.openConnection(url, basicAuthUsername,
basicAuthPassword);
if (streamOutputEnabled) {
connection.setChunkedStreamingMode(streamOutputChunkSize);
}
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.setConnectTimeout(httpTimeout);
connection.setReadTimeout(httpTimeout);
boundary = Long.toHexString(System.currentTimeMillis());
if (!fileUpload) {
connection.setRequestMethod("PUT");
connection.setRequestProperty("Accept-Encoding", "gzip");
if (useCompression) {
connection.addRequestProperty("Content-Type", "gzip"); // application/x-gzip?
}
} else {
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary="
+ boundary);
}
os = connection.getOutputStream();
if (!fileUpload && useCompression) {
os = new GZIPOutputStream(os) {
{
this.def.setLevel(compressionLevel);
this.def.setStrategy(compressionStrategy);
}
};
}
if (fileUpload) {
final String fileName = "file.zip";
IOUtils.write("--" + boundary + CRLF, os);
IOUtils.write("Content-Disposition: form-data; name=\"binaryFile\"; filename=\""
+ fileName + "\"" + CRLF, os);
IOUtils.write("Content-Type: " + URLConnection.guessContentTypeFromName(fileName)
+ CRLF, os);
IOUtils.write("Content-Transfer-Encoding: binary" + CRLF + CRLF, os);
os.flush();
}
return os;
} catch (IOException ex) {
throw new IoException(ex);
}
}
public BufferedWriter openWriter() {
try {
OutputStreamWriter wout = new OutputStreamWriter(openStream(), IoConstants.ENCODING);
writer = new BufferedWriter(wout);
return writer;
} catch (IOException ex) {
throw new IoException(ex);
}
}
public BufferedReader readResponse() throws IOException {
closeWriter(false);
closeOutputStream(false);
SymmetricUtils.analyzeResponseCode(connection.getResponseCode());
this.reader = HttpTransportManager.getReaderFrom(connection);
return this.reader;
}
public boolean isOpen() {
return connection != null;
}
public ChannelMap getSuspendIgnoreChannelLists(IConfigurationService configurationService, Node targetNode) {
HttpURLConnection connection = requestReservation();
// Connection contains remote suspend/ignore channels list if
// reservation was successful.
ChannelMap suspendIgnoreChannelsList = new ChannelMap();
String suspends = connection.getHeaderField(WebConstants.SUSPENDED_CHANNELS);
String ignores = connection.getHeaderField(WebConstants.IGNORED_CHANNELS);
suspendIgnoreChannelsList.addSuspendChannels(suspends);
suspendIgnoreChannelsList.addIgnoreChannels(ignores);
ChannelMap localSuspendIgnoreChannelsList = configurationService
.getSuspendIgnoreChannelLists(targetNode.getNodeId());
suspendIgnoreChannelsList.addSuspendChannels(
localSuspendIgnoreChannelsList.getSuspendChannels());
suspendIgnoreChannelsList.addIgnoreChannels(
localSuspendIgnoreChannelsList.getIgnoreChannels());
return suspendIgnoreChannelsList;
}
}