Java tutorial
/* $Id: 9d9c5abc4fe5561616a960fb4bceee1cddb2e35e $ * * @license * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 io.coala.capability.online; import io.coala.bind.Binder; import io.coala.capability.configure.ConfiguringCapability; import io.coala.log.LogUtil; import io.coala.resource.ResourceStream; import io.coala.resource.ResourceStreamer; import io.coala.resource.ResourceType; import io.coala.web.HttpMethod; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.nio.charset.Charset; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import javax.inject.Inject; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.commons.io.IOUtils; import org.apache.http.Consts; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.fluent.Request; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.entity.AbstractHttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.Args; import org.apache.log4j.Logger; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; import rx.Subscriber; import rx.functions.Action0; import rx.schedulers.Schedulers; import com.fasterxml.jackson.annotation.JsonIgnore; /** * {@link FluentHCOnlineCapability} */ public class FluentHCOnlineCapability extends BasicOnlineCapability { /** */ private static final long serialVersionUID = 1L; /** */ private static final Logger LOG = LogUtil.getLogger(FluentHCOnlineCapability.class); private static boolean setup = false; @Inject public FluentHCOnlineCapability(final Binder binder) { super(binder); } @Override public void initialize() throws NoSuchAlgorithmException, KeyManagementException { synchronized (FluentHCOnlineCapability.class) { if (setup) return; if (!getBinder().inject(ConfiguringCapability.class).getProperty(TRUST_MANAGER_DISABLED_PROPERTY_KEY) .getBoolean(TRUST_MANAGER_DISABLED_PROPERTY_DEFAULT)) return; final SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(new KeyManager[0], new TrustManager[] { new DummyTrustManager() }, new SecureRandom()); SSLContext.setDefault(ctx); setup = true; } } /** * @param method * @param uri * @return */ @SuppressWarnings("rawtypes") public static Request getFluentRequest(final HttpMethod method, final URI uri, final Map.Entry... formData) { final Request request; switch (method) { case GET: request = Request.Get(toFormDataURI(uri, formData)); break; case HEAD: request = Request.Head(toFormDataURI(uri, formData)); break; case POST: request = Request.Post(uri); break; case PUT: request = Request.Put(uri); break; case DELETE: request = Request.Delete(toFormDataURI(uri, formData)); break; case OPTIONS: request = Request.Options(toFormDataURI(uri, formData)); break; case TRACE: request = Request.Trace(toFormDataURI(uri, formData)); break; default: throw new IllegalStateException("UNSUPPORTED: " + method); } return request.useExpectContinue().version(HttpVersion.HTTP_1_1); } public static HttpUriRequest getRequest(final HttpMethod method, final URI uri) { final HttpRequestBase request; switch (method) { case POST: request = new HttpPost(uri);// Request.Post(uri); break; case PUT: request = new HttpPut(uri);// Request.Put(uri); break; case DELETE: request = new HttpDelete(uri);// Request.Delete(uri); break; case GET: request = new HttpGet(uri);// Request.Get(uri); break; default: throw new IllegalStateException("UNSUPPORTED: " + method); } request.setProtocolVersion(HttpVersion.HTTP_1_1); final RequestConfig.Builder configBuilder = RequestConfig.custom(); // TODO read (additional) HTTP client settings from external config configBuilder.setExpectContinueEnabled(true); request.setConfig(configBuilder.build()); return request; } @SuppressWarnings("rawtypes") public static List<NameValuePair> toFormData(final Map.Entry... formData) { final List<NameValuePair> paramList = new ArrayList<>(); for (Map.Entry entry : formData) { final String name = entry.getKey().toString(); final String value = entry.getValue().toString(); paramList.add(new NameValuePair() { @Override public String getName() { return name; } @Override public String getValue() { return value; } }); } return paramList; } public static HttpEntity toFormEntity(final List<NameValuePair> paramList) { final ContentType contentType = ContentType.create(URLEncodedUtils.CONTENT_TYPE, Consts.ISO_8859_1); final Charset charset = contentType != null ? contentType.getCharset() : null; final String s = URLEncodedUtils.format(paramList, charset != null ? charset.name() : null); byte[] raw; try { raw = charset != null ? s.getBytes(charset.name()) : s.getBytes(); } catch (UnsupportedEncodingException ex) { raw = s.getBytes(); } return new ApacheInternalByteArrayEntity(raw, contentType); } public static HttpEntity toStreamEntity(final ResourceStream stream) { return new ApacheInternalInputStreamEntity(stream.getStream(), -1, ContentType.create(stream.getType().getMIMEType())); } @Override public ResourceStreamer request(final URI uri, final HttpMethod method, final ResourceType resultType, final ResourceStreamer content) { return doRequest(uri, method, resultType, content); } @Override public ResourceStreamer request(final URI uri, final HttpMethod method, final ResourceType resultType, final Map<String, ?> formData) { return doRequest(uri, method, resultType, null, formData.entrySet().toArray(new Map.Entry[formData.size()])); } @SuppressWarnings("rawtypes") @Override public ResourceStreamer request(final URI uri, final HttpMethod method, final ResourceType resultType, final Map.Entry... formData) { return doRequest(uri, method, resultType, null, formData); } private static void scheduleCountdown(final CountDownLatch latch) { Schedulers.io().createWorker().schedule(new Action0() { @Override public void call() { latch.countDown(); } }); } @SuppressWarnings("rawtypes") protected static ResourceStreamer doRequest(final URI uri, final HttpMethod method, final ResourceType resultType, final ResourceStreamer content, final Map.Entry... formData) { LOG.trace(method + " " + uri); return ResourceStreamer.from(Observable.create(new OnSubscribe<ResourceStream>() { @Override public void call(final Subscriber<? super ResourceStream> sub) { final CountDownLatch latch = new CountDownLatch(1); // final HttpUriRequest request = getRequest(method, // uri); // if (request instanceof HttpEntityEnclosingRequest) final Request request = getFluentRequest(method, uri, formData); final MyResponseHandler handler = new MyResponseHandler(request, resultType, sub, latch); if (method == HttpMethod.POST || method == HttpMethod.PUT) { if (formData != null && formData.length > 0) { if (content != null) LOG.warn("IGNORING NON-EMPTY CONTENT, prioritizing non-empty form data"); Schedulers.io().createWorker().schedule(new Action0() { @Override public void call() { execute(request, handler, sub, toFormEntity(toFormData(formData))); scheduleCountdown(latch); } }); } else if (content == null) { Schedulers.io().createWorker().schedule(new Action0() { @Override public void call() { execute(request, handler, sub, null); scheduleCountdown(latch); } }); } else { // TODO is this allowing parallel streaming? LOG.trace("POSTing with raw content stream..."); Schedulers.io().createWorker().schedule(new Action0() { @Override public void call() { content.getStreams().subscribe(new Observer<ResourceStream>() { @Override public void onCompleted() { sub.onCompleted(); scheduleCountdown(latch); } @Override public void onError(final Throwable e) { sub.onError(e); scheduleCountdown(latch); } @Override public void onNext(final ResourceStream is) { execute(request, handler, sub, toStreamEntity(is)); } }); } }); } } else // GET, OPTIONS, HEADER, TRACE, or DELETE { if (content != null) LOG.warn("IGNORING NON-EMPTY CONTENT, not applicable for HTTP request method: " + method); execute(request, handler, sub, null); sub.onCompleted(); scheduleCountdown(latch); } } })); } /** * @param request * @param handler * @param sub * @param entity */ protected static void execute(final Request request, final MyResponseHandler handler, final Subscriber<? super ResourceStream> sub, final HttpEntity entity) { try { if (entity != null) request.body(entity); request.execute().handleResponse(handler); } catch (final Throwable e) { sub.onError(e); } } /** * @param request * @param handler * @param sub * @param entity */ protected void execute(final HttpUriRequest request, final ResponseHandler<?> handler, final Observer<?> sub, final HttpEntity entity) { // Schedulers.io().createWorker().schedule(new Action0() // { // @Override // public void call() // { try { if (entity != null) ((HttpEntityEnclosingRequest) request).setEntity(entity); HttpClientBuilder.create().build().execute(request, handler); } catch (final Throwable e) { sub.onError(e); } // } // }); } /** * {@link DummyTrustManager} */ static class DummyTrustManager implements X509TrustManager { @Override public void checkClientTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException { } @Override public void checkServerTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } /** * {@link MyResponseHandler} */ static class MyResponseHandler implements ResponseHandler<Void> { /** */ private final Request request; /** */ private final ResourceType expectedType; /** */ @JsonIgnore private final Subscriber<? super ResourceStream> subscriber; /** */ private final CountDownLatch latch; /** * {@link MyResponseHandler} constructor * * @param requestPath * @param expectedType * @param subscriber * @param latch */ public MyResponseHandler(final Request request, final ResourceType expectedType, final Subscriber<? super ResourceStream> subscriber, final CountDownLatch latch) { this.request = request; this.expectedType = expectedType; this.subscriber = subscriber; this.latch = latch; } @Override public Void handleResponse(final HttpResponse response) throws ClientProtocolException, IOException { final StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() >= 300) throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); final HttpEntity entity = response.getEntity(); if (entity == null) { this.subscriber.onError(new ClientProtocolException("Response contains no content")); return null; } final ResourceType type = entity.getContentType() == null ? expectedType : ResourceType.ofMIMEType(entity.getContentType().getValue()); // FIXME copy to a new stream! try { subscriber.onNext(ResourceStream.of(entity.getContent(), type, request.toString())); /* * int secs = 0; while (latch.getCount() > 0) { if (secs > 0) LOG.trace( * "Blocking after response from " + request + "..."); latch.await(3, * TimeUnit.SECONDS); secs++; } */ } catch (final Exception e) { subscriber.onError(e); } return null; } } /** * {@link ApacheInternalByteArrayEntity} copied from * {@link org.apache.http.client.fluent.InternalByteArrayEntity} */ static class ApacheInternalByteArrayEntity extends AbstractHttpEntity implements Cloneable { private final byte[] b; private final int off, len; public ApacheInternalByteArrayEntity(final byte[] b, final ContentType contentType) { super(); Args.notNull(b, "Source byte array"); this.b = b; this.off = 0; this.len = this.b.length; if (contentType != null) { setContentType(contentType.toString()); } } public ApacheInternalByteArrayEntity(final byte[] b, final int off, final int len, final ContentType contentType) { super(); Args.notNull(b, "Source byte array"); if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) < 0) || ((off + len) > b.length)) { throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length); } this.b = b; this.off = off; this.len = len; if (contentType != null) { setContentType(contentType.toString()); } } public ApacheInternalByteArrayEntity(final byte[] b) { this(b, null); } public ApacheInternalByteArrayEntity(final byte[] b, final int off, final int len) { this(b, off, len, null); } public boolean isRepeatable() { return true; } public long getContentLength() { return this.len; } public InputStream getContent() { return new ByteArrayInputStream(this.b, this.off, this.len); } public void writeTo(final OutputStream outstream) throws IOException { Args.notNull(outstream, "Output stream"); outstream.write(this.b, this.off, this.len); outstream.flush(); } public boolean isStreaming() { return false; } } /** * {@link ApacheInternalInputStreamEntity} adapted from * {@link org.apache.http.client.fluent.InternalInputStreamEntity} */ static class ApacheInternalInputStreamEntity extends AbstractHttpEntity { private final InputStream content; private final long length; public ApacheInternalInputStreamEntity(final InputStream instream, final long length, final ContentType contentType) { super(); this.content = Args.notNull(instream, "Source input stream"); this.length = length; if (contentType != null) { setContentType(contentType.toString()); } } public boolean isRepeatable() { return false; } public long getContentLength() { return this.length; } public InputStream getContent() throws IOException { return this.content; } public void writeTo(final OutputStream outstream) throws IOException { Args.notNull(outstream, "Output stream"); final InputStream instream = this.content; try { if (this.length < 0) IOUtils.copy(instream, outstream); else // should not occur IOUtils.copyLarge(instream, outstream, 0, this.length); } finally { instream.close(); } } public boolean isStreaming() { return true; } } }