io.hummer.util.ws.AbstractNode.java Source code

Java tutorial

Introduction

Here is the source code for io.hummer.util.ws.AbstractNode.java

Source

/*
 * Project 'WS-Aggregation':
 * http://www.infosys.tuwien.ac.at/prototype/WS-Aggregation/
 *
 * Copyright 2010 Vienna University of Technology
 *
 * 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.hummer.util.ws;

import io.hummer.util.coll.CollectionsUtil;
import io.hummer.util.log.LogUtil;
import io.hummer.util.net.NetUtil;
import io.hummer.util.par.GlobalThreadPool;
import io.hummer.util.str.StringUtil;
import io.hummer.util.xml.XMLUtil;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.BindException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import javax.annotation.Resource;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;
import javax.ws.rs.Path;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.RuntimeDelegate;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.ws.BindingType;
import javax.xml.ws.Endpoint;
import javax.xml.ws.Provider;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.spi.http.HttpExchange;

import org.apache.commons.lang.NotImplementedException;
import org.apache.log4j.Logger;
import org.eclipse.jetty.jaxws2spi.JettyHttpContext;
import org.eclipse.jetty.jaxws2spi.JettyHttpServer;
import org.eclipse.jetty.jaxws2spi.JettyHttpServerProvider;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.log.Slf4jLog;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;

import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.net.httpserver.HttpHandler;
import com.sun.xml.ws.transport.http.HttpAdapter;
import com.sun.xml.ws.transport.http.server.ServerAdapter;

@SuppressWarnings("all")
@XmlRootElement
public abstract class AbstractNode implements IAbstractNode {

    @XmlElement
    private EndpointReference epr;
    @XmlElement
    private String wadlURL;

    @SuppressWarnings("all")
    private List<Handler> activeHandlers = new LinkedList<Handler>();
    @SuppressWarnings("all")
    private List<Handler> inactiveHandlers = new LinkedList<Handler>();
    private Endpoint endpoint;

    private static final XMLUtil xmlUtil = new XMLUtil();
    private static final StringUtil strUtil = new StringUtil();
    private static final NetUtil netUtil = new NetUtil();
    private static final CollectionsUtil collUtil = new CollectionsUtil();

    public static final Map<String, AbstractNode> deployedNodes = new HashMap<String, AbstractNode>();
    public static final Map<Integer, Server> servers = new HashMap<Integer, Server>();
    public static final Map<Integer, JettyHttpServer> httpServers = new HashMap<Integer, JettyHttpServer>();
    private static final Logger logger = LogUtil.getLogger(AbstractNode.class);
    private static final String SYSPROP_HTTP_SERVER_PROVIDER_CLASS = "com.sun.net.httpserver.HttpServerProvider";

    @XmlTransient
    @WebMethod(exclude = true)
    public EndpointReference getEPR() {
        return getEpr();
    }

    @WebMethod(exclude = true)
    public void setEPR(EndpointReference epr) {
        this.setEpr(epr);
    }

    @XmlTransient
    @WebMethod(exclude = true)
    public String getWadlURL() {
        return wadlURL;
    }

    @WebMethod(exclude = true)
    public void setWadlURL(String wadlURL) {
        this.wadlURL = wadlURL;
    }

    @WebServiceProvider
    @BindingType(value = HTTPBinding.HTTP_BINDING)
    public static class CrossdomainXML implements Provider<Source> {
        @Resource
        protected WebServiceContext wsContext;

        public Source invoke(Source request) {
            Source result = null;
            try {
                result = new DOMSource(xmlUtil.toElement("<?xml version=\"1.0\"?>"
                        + "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">"
                        + "<cross-domain-policy>" + "<allow-access-from domain=\"*\"/>"
                        + "</cross-domain-policy>"));
            } catch (Exception e) {
                logger.warn("Unexpected error.", e);
            }
            return result;
        }
    }

    @WebMethod
    @SOAPBinding(parameterStyle = ParameterStyle.BARE, style = Style.DOCUMENT, use = Use.LITERAL)
    public void terminate(@WebParam final TerminateRequest params) {
        new Thread() {
            public void run() {
                logger.info("Node " + this + " is terminating...");
                // actually, the Runtime Shutdown Hook should also cause the terminate task to be executed 
                // when the Java process dies, but let's do the job here too, since we assume that doing 
                // it twice is better than not doing it at all..
                try {
                    Runnable r = getTerminateTask(params);
                    if (r != null)
                        r.run();
                } catch (Exception e) {
                }
                try {
                    Thread.sleep(2500);
                } catch (InterruptedException e) {
                }
                System.exit(0);
            }
        }.start();
        return;
    }

    protected Runnable getTerminateTask(TerminateRequest params) {
        return null;
    }

    @WebMethod(exclude = true)
    public void deploy(String url) throws Exception {
        deploy(this, url);
    }

    @SuppressWarnings("all")
    public static void deploy(final Object service, String url, Handler<?>... handler)
            throws AbstractNodeException {

        long t1 = System.currentTimeMillis();

        try {

            URL u = new URL(url);

            if (strUtil.isEmpty(System.getProperty(SYSPROP_HTTP_SERVER_PROVIDER_CLASS))) {
                System.setProperty(SYSPROP_HTTP_SERVER_PROVIDER_CLASS, JettyHttpServerProvider.class.getName());
            }

            ContextHandlerCollection chc = new ContextHandlerCollection();

            // disable log output from Metro and Jetty
            java.util.logging.Logger.getAnonymousLogger().getParent().setLevel(Level.WARNING);
            Class<?> cls3 = org.eclipse.jetty.server.Server.class;
            Class<?> cls4 = org.eclipse.jetty.server.AbstractConnector.class;
            org.apache.log4j.Level lev3 = Logger.getLogger(cls3).getLevel();
            org.apache.log4j.Level lev4 = Logger.getLogger(cls4).getLevel();
            Logger.getLogger(cls3).setLevel(org.apache.log4j.Level.WARN);
            Logger.getLogger(cls4).setLevel(org.apache.log4j.Level.WARN);

            JettyHttpServer httpServer = httpServers.get(u.getPort());
            Server server = servers.get(u.getPort());
            if (httpServer == null) {
                org.eclipse.jetty.util.log.Log.setLog(new Slf4jLog());
                server = new Server(u.getPort());

                SelectChannelConnector connector = new SelectChannelConnector();
                connector.setPort(u.getPort());
                connector.setAcceptQueueSize(1000);
                connector.setThreadPool(new ExecutorThreadPool(GlobalThreadPool.getExecutorService()));
                server.setConnectors(new Connector[] { connector });

                server.setHandler(chc);

                httpServer = new JettyHttpServer(server, true);

                httpServers.put(u.getPort(), httpServer);
                servers.put(u.getPort(), server);

                if (!server.isStarted())
                    server.start();
            }

            JettyHttpContext wsContext1 = (JettyHttpContext) httpServer.createContext(u.getPath());
            Endpoint endpoint = Endpoint.create(service);
            if (service instanceof AbstractNode) {
                if (((AbstractNode) service).endpoint != null)
                    logger.warn("AbstractNode " + service + " has apparently been double-deployed, "
                            + "because there already exists an endpoint for this instance.");
                ((AbstractNode) service).endpoint = endpoint;
            }

            // add JAX-WS handlers (e.g., needed for TeCoS invocation intercepting...)
            List<Handler> handlers = endpoint.getBinding().getHandlerChain();
            handlers.addAll(Arrays.asList(handler));
            endpoint.getBinding().setHandlerChain(handlers);
            if (service instanceof AbstractNode) {
                AbstractNode a = (AbstractNode) service;
                for (Handler h : handlers)
                    if (!a.activeHandlers.contains(h))
                        a.activeHandlers.add(h);
            }

            Class<?> cls1 = org.eclipse.jetty.util.component.AbstractLifeCycle.class;
            Class<?> cls2 = org.eclipse.jetty.server.handler.ContextHandler.class;
            org.apache.log4j.Level lev1 = Logger.getLogger(cls1).getLevel();
            org.apache.log4j.Level lev2 = Logger.getLogger(cls2).getLevel();
            try {
                String bindUrl = u.getProtocol() + "://0.0.0.0:" + u.getPort() + u.getPath();
                if (u.getQuery() != null) {
                    bindUrl += "?" + u.getQuery();
                }
                Logger.getLogger(cls1).setLevel(org.apache.log4j.Level.OFF);
                Logger.getLogger(cls2).setLevel(org.apache.log4j.Level.WARN);
                logger.info("Binding service to " + bindUrl);
                endpoint.publish(bindUrl);
            } catch (Exception e) {
                if (e instanceof BindException || (e.getCause() != null && e.getCause() instanceof BindException)
                        || (e.getCause() != null && e.getCause().getCause() != null
                                && e.getCause().getCause() instanceof BindException)) {
                    /** we expect a BindException here, just swallow */
                } else {
                    logger.warn("Unexpected error.", e);
                }
            } finally {
                Logger.getLogger(cls1).setLevel(lev1);
                Logger.getLogger(cls2).setLevel(lev2);
                Logger.getLogger(cls3).setLevel(lev3);
                Logger.getLogger(cls4).setLevel(lev4);
            }

            Field f = endpoint.getClass().getDeclaredField("actualEndpoint");
            f.setAccessible(true);

            // DO NOT do this (the two lines below), because HttpEndpoint creates some nasty 
            // compile-time dependencies with respect to JAXWS-RT. At runtime, we can (hopefully) 
            // assume that this class is present, in all newer Sun JVM implementations..
            //HttpEndpoint httpEndpoint = (HttpEndpoint)f.get(e1);
            //httpEndpoint.publish(wsContext1);
            Object httpEndpoint = f.get(endpoint);
            httpEndpoint.getClass().getMethod("publish", Object.class).invoke(httpEndpoint, wsContext1);

            Endpoint e2 = Endpoint.create(new CrossdomainXML());
            JettyHttpContext wsContext2 = (JettyHttpContext) httpServer.createContext("/crossdomain.xml");
            e2.publish(wsContext2);

            // Also deploy as RESTful service..
            if (service instanceof AbstractNode) {
                AbstractNode node = (AbstractNode) service;
                if (node.isRESTfulService()) {
                    String path = u.getPath();
                    if (!path.contains("/"))
                        path = "/";
                    path = "/rest";
                    String wadlURL = netUtil.getUrlBeforePath(u) + path + "/application.wadl";
                    if (logger.isDebugEnabled())
                        logger.debug("Deploying node as RESTful service: " + wadlURL);
                    JettyHttpContext wsContext3 = (JettyHttpContext) httpServer.createContext(path);

                    ResourceConfig rc = new PackagesResourceConfig(service.getClass().getPackage().getName(),
                            AbstractNode.class.getPackage().getName());
                    HttpHandler h = RuntimeDelegate.getInstance().createEndpoint(rc, HttpHandler.class);
                    wsContext3.setHandler(h);
                    node.setWadlURL(wadlURL);
                }
                deployedNodes.put(url, node);
            }

            final HttpHandler h = wsContext1.getHandler();
            wsContext1.setHandler(new HttpHandler() {
                public void handle(com.sun.net.httpserver.HttpExchange ex) throws IOException {

                    if (!ex.getRequestMethod().equals("OPTIONS")) {
                        addCORSHeaders(ex);
                        h.handle(ex);
                        //System.out.println(ex.getRequestMethod() + ": " + ex.getResponseHeaders() + " - " + new HashMap<>(ex.getResponseHeaders()));
                        return;
                    }

                    if (ex.getRequestMethod().equals("OPTIONS")) {
                        addCORSHeaders(ex);
                        ex.sendResponseHeaders(200, -1);
                        ex.getResponseBody().close();
                        return;
                    }
                    //System.out.println(new HashMap<>(ex.getResponseHeaders()));
                }
            });

            // add shutdown task for this node
            if (service instanceof AbstractNode) {
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    public void run() {
                        try {
                            Runnable r = ((AbstractNode) service).getTerminateTask(null);
                            if (r != null)
                                r.run();
                        } catch (Exception e) {
                        }
                    }
                });
            }

        } catch (Exception e) {
            throw new AbstractNodeException(e);
        }

        long diff = System.currentTimeMillis() - t1;
        logger.info("Deployment took " + diff + "ms");

    }

    private static void addCORSHeaders(com.sun.net.httpserver.HttpExchange ex) {
        // Allow CORS policy (cross-domain requests, from Web browsers)
        ex.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
        ex.getResponseHeaders().add("Access-Control-Allow-Headers",
                "Origin, Content-Type, Accept, Transfer-Encoding, "
                        + "Accept-Encoding, Accept-Language, Connection, Cookie, Host, "
                        + "Referer, User-Agent, Server, Authorization, x-requested-with");
        ex.getResponseHeaders().add("Access-Control-Allow-Credentials", "true");
        ex.getResponseHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
        ex.getResponseHeaders().add("Access-Control-Max-Age", "1209600");
        ex.getResponseHeaders().add("Access-Control-Expose-Headers", "Location");
    }

    public static AbstractNode getDeployedNodeForResourceUri(UriInfo info) throws Exception {
        String absolute = info.getAbsolutePath().toString();
        URL u = new URL(absolute);
        absolute = absolute.replace("/rest/", "/");
        String path = u.getProtocol() + "://";
        absolute = absolute.substring(absolute.indexOf("://") + "://".length());
        path += absolute.substring(0, absolute.indexOf("/") + 1);
        absolute = absolute.substring(absolute.indexOf("/") + 1);
        path += absolute.substring(0, absolute.indexOf("/"));
        if (path.endsWith("REST")) {
            path = path.substring(0, path.length() - "REST".length());
        }
        AbstractNode n = deployedNodes.get(path);
        if (n == null) {
            n = deployedNodes.get(path + "?wsdl");
        }
        if (n == null) {
            if (logger.isDebugEnabled())
                logger.debug(path + " not contained in node list " + deployedNodes);
            if (deployedNodes.size() == 1) {
                n = deployedNodes.values().iterator().next();
            }
        }
        return n;
    }

    public static AbstractNode getDeployedNodeForEndpoint(Endpoint endpoint) {
        // TODO
        throw new NotImplementedException();
    }

    protected boolean isRESTfulService() {
        Class<?> c = getClass();
        do {
            if (c.isAnnotationPresent(Path.class))
                return true;
            for (Method m : c.getDeclaredMethods()) {
                if (m.isAnnotationPresent(Path.class))
                    return true;
            }
            c = c.getSuperclass();
        } while (c != null && c != Object.class);
        return false;
    }

    @WebMethod
    @SOAPBinding(parameterStyle = ParameterStyle.BARE, style = Style.DOCUMENT, use = Use.LITERAL)
    @SuppressWarnings("all")
    public void setHandlerActive(@WebParam final SetHandlerActiveRequest params) throws AbstractNodeException {
        try {

            Class<?> clazz = Class.forName(params.handlerSuperClass);
            List<Handler> fromList = params.active ? inactiveHandlers : activeHandlers;
            List<Handler> toList = params.active ? activeHandlers : inactiveHandlers;
            boolean done = false;
            for (int i = 0; i < fromList.size(); i++) {
                Handler<?> h = fromList.get(i);
                if (clazz.isAssignableFrom(h.getClass())) {
                    fromList.remove(i--);
                    if (!toList.contains(h)) {
                        toList.add(h);
                        done = true;
                    } else {
                        done = true;
                    }
                }
            }
            try {
                if (!done && params.active) {
                    Handler<?> h = (Handler<?>) clazz.newInstance();
                    boolean alreadyIncluded = false;
                    for (Handler h1 : activeHandlers) {
                        if (clazz.isAssignableFrom(h1.getClass()))
                            alreadyIncluded = true;
                    }
                    if (!alreadyIncluded)
                        activeHandlers.add(h);
                }
            } catch (Exception e) {
                endpoint.getBinding().setHandlerChain(activeHandlers);
                throw e;
            }
            endpoint.getBinding().setHandlerChain(activeHandlers);

        } catch (Exception e) {
            throw new AbstractNodeException(e);
        }
    }

    public static <T extends AbstractNode> T selectRandomAvailableNode(List<T> candidates) {
        if (candidates == null || candidates.isEmpty())
            return null;
        int numAttempts = 100;
        for (int i = 0; i < numAttempts; i++) {
            T node = collUtil.getRandom(candidates);
            if (node == null)
                return null;
            if (netUtil.isPortOpen(node.getEPR().getAddress())) {
                return node;
            }
        }
        throw new IllegalArgumentException(
                "Unable to determine available node in candidate list after " + numAttempts + " attempts.");
    }

    @XmlTransient
    protected EndpointReference getEpr() {
        return epr;
    }

    protected void setEpr(EndpointReference epr) {
        this.epr = epr;
    }

    @Override
    @WebMethod(exclude = true)
    public String toString() {
        if (getEPR() == null || getEPR().getAddress() == null)
            return super.toString();
        return "[" + getEPR().getAddress() + "]";
    }

    @Override
    @WebMethod(exclude = true)
    public boolean equals(Object o) {
        if (!(o instanceof AbstractNode))
            return false;
        AbstractNode n = (AbstractNode) o;
        if (n.getEPR() == null)
            return false;
        return n.getEPR().equals(this.getEPR());
    }

    @Override
    @WebMethod(exclude = true)
    public int hashCode() {
        if (getEpr() == null)
            return super.hashCode();
        return getEpr().hashCode();
    }

}