diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java index ae1f1938601..bd01c93dc0b 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.LowResourceMonitor; import org.eclipse.jetty.server.NCSARequestLog; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -36,6 +37,7 @@ import org.eclipse.jetty.server.handler.RequestLogHandler; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.TimerScheduler; public class LikeJettyXml { @@ -53,6 +55,8 @@ public class LikeJettyXml server.setDumpAfterStart(false); server.setDumpBeforeStop(false); + server.addBean(new TimerScheduler()); + // Setup JMX MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); server.addBean(mbContainer); @@ -120,6 +124,12 @@ public class LikeJettyXml requestLogHandler.setRequestLog(requestLog); server.setStopAtShutdown(true); + + LowResourceMonitor lowResourcesMonitor=new LowResourceMonitor(server); + lowResourcesMonitor.setLowResourcesIdleTimeout(1000); + lowResourcesMonitor.setMaxConnections(2); + lowResourcesMonitor.setPeriod(1200); + server.addBean(lowResourcesMonitor); server.start(); server.join(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java index 007d0e2b462..ccb15cf6424 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java @@ -80,14 +80,12 @@ public class AuthenticationProtocolHandler implements ProtocolHandler public void onComplete(Result result) { Request request = result.getRequest(); - HttpConversation conversation = client.getConversation(request.getConversationID(), false); - List listeners = conversation.getExchanges().peekFirst().getResponseListeners(); ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding()); if (result.isFailed()) { Throwable failure = result.getFailure(); LOG.debug("Authentication challenge failed {}", failure); - notifier.forwardFailureComplete(listeners, request, result.getRequestFailure(), response, result.getResponseFailure()); + forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure()); return; } @@ -95,7 +93,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler if (wwwAuthenticates.isEmpty()) { LOG.debug("Authentication challenge without WWW-Authenticate header"); - notifier.forwardFailureComplete(listeners, request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response)); + forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response)); return; } @@ -114,15 +112,16 @@ public class AuthenticationProtocolHandler implements ProtocolHandler if (authentication == null) { LOG.debug("No authentication available for {}", request); - notifier.forwardSuccessComplete(listeners, request, response); + forwardSuccessComplete(request, response); return; } + HttpConversation conversation = client.getConversation(request.getConversationID(), false); final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation); LOG.debug("Authentication result {}", authnResult); if (authnResult == null) { - notifier.forwardSuccessComplete(listeners, request, response); + forwardSuccessComplete(request, response); return; } @@ -138,6 +137,20 @@ public class AuthenticationProtocolHandler implements ProtocolHandler }).send(null); } + private void forwardSuccessComplete(Request request, Response response) + { + HttpConversation conversation = client.getConversation(request.getConversationID(), false); + conversation.updateResponseListeners(null); + notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response); + } + + private void forwardFailureComplete(Request request, Throwable requestFailure, Response response, Throwable responseFailure) + { + HttpConversation conversation = client.getConversation(request.getConversationID(), false); + conversation.updateResponseListeners(null); + notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure); + } + private List parseWWWAuthenticate(Response response) { // TODO: these should be ordered by strength diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java index d1d67f54610..3b17b5229aa 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java @@ -68,25 +68,28 @@ public class ContinueProtocolHandler implements ProtocolHandler // Mark the 100 Continue response as handled conversation.setAttribute(ATTRIBUTE, Boolean.TRUE); + // Reset the conversation listeners, since we are going to receive another response code + conversation.updateResponseListeners(null); + HttpExchange exchange = conversation.getExchanges().peekLast(); assert exchange.getResponse() == response; - List listeners = exchange.getResponseListeners(); switch (response.getStatus()) { case 100: { // All good, continue exchange.resetResponse(true); - conversation.setResponseListeners(listeners); exchange.proceed(true); break; } default: { - // Server either does not support 100 Continue, or it does and wants to refuse the request content + // Server either does not support 100 Continue, + // or it does and wants to refuse the request content, + // or we got some other HTTP status code like a redirect. + List listeners = exchange.getResponseListeners(); HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding()); notifier.forwardSuccess(listeners, contentResponse); - conversation.setResponseListeners(listeners); exchange.proceed(false); break; } @@ -99,6 +102,8 @@ public class ContinueProtocolHandler implements ProtocolHandler HttpConversation conversation = client.getConversation(response.getConversationID(), false); // Mark the 100 Continue response as handled conversation.setAttribute(ATTRIBUTE, Boolean.TRUE); + // Reset the conversation listeners to allow the conversation to be completed + conversation.updateResponseListeners(null); HttpExchange exchange = conversation.getExchanges().peekLast(); assert exchange.getResponse() == response; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index c96f0599343..6a61eb777d8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -64,6 +64,7 @@ import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -122,6 +123,7 @@ public class HttpClient extends ContainerLifeCycle private volatile Executor executor; private volatile ByteBufferPool byteBufferPool; private volatile Scheduler scheduler; + private volatile SocketAddressResolver resolver; private volatile SelectorManager selectorManager; private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION); private volatile boolean followRedirects = true; @@ -132,6 +134,7 @@ public class HttpClient extends ContainerLifeCycle private volatile int maxRedirects = 8; private volatile SocketAddress bindAddress; private volatile long connectTimeout = 15000; + private volatile long addressResolutionTimeout = 15000; private volatile long idleTimeout; private volatile boolean tcpNoDelay = true; private volatile boolean dispatchIO = true; @@ -198,6 +201,8 @@ public class HttpClient extends ContainerLifeCycle scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false); addBean(scheduler); + resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout()); + selectorManager = newSelectorManager(); selectorManager.setConnectTimeout(getConnectTimeout()); addBean(selectorManager); @@ -439,10 +444,10 @@ public class HttpClient extends ContainerLifeCycle */ public Destination getDestination(String scheme, String host, int port) { - return provideDestination(scheme, host, port); + return destinationFor(scheme, host, port); } - protected HttpDestination provideDestination(String scheme, String host, int port) + protected HttpDestination destinationFor(String scheme, String host, int port) { port = normalizePort(scheme, port); @@ -480,34 +485,48 @@ public class HttpClient extends ContainerLifeCycle if (!Arrays.asList("http", "https").contains(scheme)) throw new IllegalArgumentException("Invalid protocol " + scheme); - HttpDestination destination = provideDestination(scheme, request.getHost(), request.getPort()); + HttpDestination destination = destinationFor(scheme, request.getHost(), request.getPort()); destination.send(request, listeners); } - protected void newConnection(HttpDestination destination, Promise promise) + protected void newConnection(final HttpDestination destination, final Promise promise) { - SocketChannel channel = null; - try + Destination.Address address = destination.getConnectAddress(); + resolver.resolve(address.getHost(), address.getPort(), new Promise() { - channel = SocketChannel.open(); - SocketAddress bindAddress = getBindAddress(); - if (bindAddress != null) - channel.bind(bindAddress); - configure(channel); - channel.configureBlocking(false); - channel.connect(destination.getConnectAddress()); + @Override + public void succeeded(SocketAddress socketAddress) + { + SocketChannel channel = null; + try + { + channel = SocketChannel.open(); + SocketAddress bindAddress = getBindAddress(); + if (bindAddress != null) + channel.bind(bindAddress); + configure(channel); + channel.configureBlocking(false); + channel.connect(socketAddress); - Future result = new ConnectionCallback(destination, promise); - selectorManager.connect(channel, result); - } - // Must catch all exceptions, since some like - // UnresolvedAddressException are not IOExceptions. - catch (Exception x) - { - if (channel != null) - close(channel); - promise.failed(x); - } + Future futureConnection = new ConnectionCallback(destination, promise); + selectorManager.connect(channel, futureConnection); + } + // Must catch all exceptions, since some like + // UnresolvedAddressException are not IOExceptions. + catch (Throwable x) + { + if (channel != null) + close(channel); + promise.failed(x); + } + } + + @Override + public void failed(Throwable x) + { + promise.failed(x); + } + }); } protected void configure(SocketChannel channel) throws SocketException @@ -544,7 +563,7 @@ public class HttpClient extends ContainerLifeCycle protected void removeConversation(HttpConversation conversation) { - conversations.remove(conversation.id()); + conversations.remove(conversation.getID()); LOG.debug("{} removed", conversation); } @@ -599,6 +618,22 @@ public class HttpClient extends ContainerLifeCycle this.connectTimeout = connectTimeout; } + /** + * @return the timeout, in milliseconds, for the DNS resolution of host addresses + */ + public long getAddressResolutionTimeout() + { + return addressResolutionTimeout; + } + + /** + * @param addressResolutionTimeout the timeout, in milliseconds, for the DNS resolution of host addresses + */ + public void setAddressResolutionTimeout(long addressResolutionTimeout) + { + this.addressResolutionTimeout = addressResolutionTimeout; + } + /** * @return the max time a connection can be idle (that is, without traffic of bytes in either direction) */ diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index e291532df4c..64f1b22dfdd 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -117,11 +117,15 @@ public class HttpConnection extends AbstractConnection implements Connection } if (listener != null) listeners.add(listener); - send(request, listeners); + + HttpConversation conversation = client.getConversation(request.getConversationID(), true); + HttpExchange exchange = new HttpExchange(conversation, getDestination(), request, listeners); + send(exchange); } - public void send(Request request, List listeners) + public void send(HttpExchange exchange) { + Request request = exchange.getRequest(); normalizeRequest(request); // Save the old idle timeout to restore it @@ -129,10 +133,8 @@ public class HttpConnection extends AbstractConnection implements Connection idleTimeout = endPoint.getIdleTimeout(); endPoint.setIdleTimeout(request.getIdleTimeout()); - HttpConversation conversation = client.getConversation(request.getConversationID(), true); - HttpExchange exchange = new HttpExchange(conversation, this, request, listeners); - setExchange(exchange); - conversation.getExchanges().offer(exchange); + // Associate the exchange to the connection + associate(exchange); sender.send(exchange); } @@ -279,12 +281,21 @@ public class HttpConnection extends AbstractConnection implements Connection return exchange.get(); } - protected void setExchange(HttpExchange exchange) + protected void associate(HttpExchange exchange) { if (!this.exchange.compareAndSet(null, exchange)) throw new UnsupportedOperationException("Pipelined requests not supported"); - else - LOG.debug("{} associated to {}", exchange, this); + exchange.setConnection(this); + LOG.debug("{} associated to {}", exchange, this); + } + + protected HttpExchange disassociate() + { + HttpExchange exchange = this.exchange.getAndSet(null); + if (exchange != null) + exchange.setConnection(null); + LOG.debug("{} disassociated from {}", exchange, this); + return exchange; } @Override @@ -293,7 +304,7 @@ public class HttpConnection extends AbstractConnection implements Connection HttpExchange exchange = getExchange(); if (exchange != null) { - exchange.receive(); + receive(); } else { @@ -310,7 +321,7 @@ public class HttpConnection extends AbstractConnection implements Connection public void complete(HttpExchange exchange, boolean success) { - HttpExchange existing = this.exchange.getAndSet(null); + HttpExchange existing = disassociate(); if (existing == exchange) { exchange.awaitTermination(); @@ -356,13 +367,13 @@ public class HttpConnection extends AbstractConnection implements Connection } } - public boolean abort(HttpExchange exchange, Throwable cause) + public boolean abort(Throwable cause) { // We want the return value to be that of the response // because if the response has already successfully // arrived then we failed to abort the exchange - sender.abort(exchange, cause); - return receiver.abort(exchange, cause); + sender.abort(cause); + return receiver.abort(cause); } public void proceed(boolean proceed) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java index 233f291fdb7..6ab9a3a50d1 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java @@ -18,23 +18,20 @@ package org.eclipse.jetty.client; -import java.util.Collections; +import java.util.ArrayList; import java.util.Deque; -import java.util.Enumeration; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; -public class HttpConversation implements Attributes +public class HttpConversation extends AttributesMap { - private final Map attributes = new ConcurrentHashMap<>(); private final Deque exchanges = new ConcurrentLinkedDeque<>(); private final HttpClient client; private final long id; + private volatile boolean complete; private volatile List listeners; public HttpConversation(HttpClient client, long id) @@ -43,7 +40,7 @@ public class HttpConversation implements Attributes this.id = id; } - public long id() + public long getID() { return id; } @@ -53,55 +50,119 @@ public class HttpConversation implements Attributes return exchanges; } + /** + * Returns the list of response listeners that needs to be notified of response events. + * This list changes as the conversation proceeds, as follows: + *
    + *
  1. + * request R1 send => conversation.updateResponseListeners(null) + *
      + *
    • exchanges in conversation: E1
    • + *
    • listeners to be notified: E1.listeners
    • + *
    + *
  2. + *
  3. + * response R1 arrived, 401 => conversation.updateResponseListeners(AuthenticationProtocolHandler.listener) + *
      + *
    • exchanges in conversation: E1
    • + *
    • listeners to be notified: AuthenticationProtocolHandler.listener
    • + *
    + *
  4. + *
  5. + * request R2 send => conversation.updateResponseListeners(null) + *
      + *
    • exchanges in conversation: E1 + E2
    • + *
    • listeners to be notified: E2.listeners + E1.listeners
    • + *
    + *
  6. + *
  7. + * response R2 arrived, 302 => conversation.updateResponseListeners(RedirectProtocolHandler.listener) + *
      + *
    • exchanges in conversation: E1 + E2
    • + *
    • listeners to be notified: E2.listeners + RedirectProtocolHandler.listener
    • + *
    + *
  8. + *
  9. + * request R3 send => conversation.updateResponseListeners(null) + *
      + *
    • exchanges in conversation: E1 + E2 + E3
    • + *
    • listeners to be notified: E3.listeners + E1.listeners
    • + *
    + *
  10. + *
  11. + * response R3 arrived, 200 => conversation.updateResponseListeners(null) + *
      + *
    • exchanges in conversation: E1 + E2 + E3
    • + *
    • listeners to be notified: E3.listeners + E1.listeners
    • + *
    + *
  12. + *
+ * Basically the override conversation listener replaces the first exchange response listener, + * and we also notify the last exchange response listeners (if it's not also the first). + * + * This scheme allows for protocol handlers to not worry about other protocol handlers, or to worry + * too much about notifying the first exchange response listeners, but still allowing a protocol + * handler to perform completion activities while another protocol handler performs new ones (as an + * example, the {@link AuthenticationProtocolHandler} stores the successful authentication credentials + * while the {@link RedirectProtocolHandler} performs a redirect). + * + * @return the list of response listeners that needs to be notified of response events + */ public List getResponseListeners() { return listeners; } - public void setResponseListeners(List listeners) + /** + * Requests to update the response listener, eventually using the given override response listener, + * that must be notified instead of the first exchange response listeners. + * This works in conjunction with {@link #getResponseListeners()}, returning the appropriate response + * listeners that needs to be notified of response events. + * + * @param overrideListener the override response listener + */ + public void updateResponseListeners(Response.ResponseListener overrideListener) { - this.listeners = listeners; + // If we have no override listener, then the + // conversation may be completed at a later time + complete = overrideListener == null; + + // Create a new instance to avoid that iterating over the listeners + // will notify a listener that may send a new request and trigger + // another call to this method which will build different listeners + // which may be iterated over when the iteration continues. + listeners = new ArrayList<>(); + + HttpExchange firstExchange = exchanges.peekFirst(); + HttpExchange lastExchange = exchanges.peekLast(); + if (firstExchange == lastExchange) + { + if (overrideListener != null) + listeners.add(overrideListener); + else + listeners.addAll(firstExchange.getResponseListeners()); + } + else + { + // Order is important, we want to notify the last exchange first + listeners.addAll(lastExchange.getResponseListeners()); + if (overrideListener != null) + listeners.add(overrideListener); + else + listeners.addAll(firstExchange.getResponseListeners()); + } } public void complete() { - client.removeConversation(this); - } - - @Override - public Object getAttribute(String name) - { - return attributes.get(name); - } - - @Override - public void setAttribute(String name, Object attribute) - { - attributes.put(name, attribute); - } - - @Override - public void removeAttribute(String name) - { - attributes.remove(name); - } - - @Override - public Enumeration getAttributeNames() - { - return Collections.enumeration(attributes.keySet()); - } - - @Override - public void clearAttributes() - { - attributes.clear(); + if (complete) + client.removeConversation(this); } public boolean abort(Throwable cause) { HttpExchange exchange = exchanges.peekLast(); - return exchange != null && exchange.abort(cause); + return exchange.abort(cause); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 4463d519bdf..af4b8209000 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -19,13 +19,11 @@ package org.eclipse.jetty.client; import java.io.IOException; -import java.net.InetSocketAddress; import java.nio.channels.AsynchronousCloseException; import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -41,7 +39,6 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.util.BlockingArrayQueue; -import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; @@ -56,13 +53,13 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable private final HttpClient client; private final String scheme; private final String host; - private final InetSocketAddress address; - private final Queue requests; + private final Address address; + private final Queue exchanges; private final BlockingQueue idleConnections; private final BlockingQueue activeConnections; private final RequestNotifier requestNotifier; private final ResponseNotifier responseNotifier; - private final InetSocketAddress proxyAddress; + private final Address proxyAddress; private final HttpField hostField; public HttpDestination(HttpClient client, String scheme, String host, int port) @@ -70,11 +67,11 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable this.client = client; this.scheme = scheme; this.host = host; - this.address = new InetSocketAddress(host, port); + this.address = new Address(host, port); int maxRequestsQueued = client.getMaxRequestsQueuedPerDestination(); int capacity = Math.min(32, maxRequestsQueued); - this.requests = new BlockingArrayQueue<>(capacity, capacity, maxRequestsQueued); + this.exchanges = new BlockingArrayQueue<>(capacity, capacity, maxRequestsQueued); int maxConnections = client.getMaxConnectionsPerDestination(); capacity = Math.min(8, maxConnections); @@ -86,7 +83,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable ProxyConfiguration proxyConfig = client.getProxyConfiguration(); proxyAddress = proxyConfig != null && proxyConfig.matches(host, port) ? - new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()) : null; + new Address(proxyConfig.getHost(), proxyConfig.getPort()) : null; hostField = new HttpField(HttpHeader.HOST, host + ":" + port); } @@ -101,6 +98,16 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable return activeConnections; } + public RequestNotifier getRequestNotifier() + { + return requestNotifier; + } + + public ResponseNotifier getResponseNotifier() + { + return responseNotifier; + } + @Override public String getScheme() { @@ -121,7 +128,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable return address.getPort(); } - public InetSocketAddress getConnectAddress() + public Address getConnectAddress() { return isProxied() ? proxyAddress : address; } @@ -146,12 +153,14 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable if (port >= 0 && getPort() != port) throw new IllegalArgumentException("Invalid request port " + port + " for destination " + this); - RequestContext requestContext = new RequestContext(request, listeners); + HttpConversation conversation = client.getConversation(request.getConversationID(), true); + HttpExchange exchange = new HttpExchange(conversation, this, request, listeners); + if (client.isRunning()) { - if (requests.offer(requestContext)) + if (exchanges.offer(exchange)) { - if (!client.isRunning() && requests.remove(requestContext)) + if (!client.isRunning() && exchanges.remove(exchange)) { throw new RejectedExecutionException(client + " is stopping"); } @@ -175,14 +184,12 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable } } - public Future newConnection() + public void newConnection(Promise promise) { - FuturePromise result = new FuturePromise<>(); - newConnection(new ProxyPromise(result)); - return result; + createConnection(new ProxyPromise(promise)); } - protected void newConnection(Promise promise) + protected void createConnection(Promise promise) { client.newConnection(this, promise); } @@ -227,7 +234,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable @Override public void run() { - drain(x); + abort(x); } }); } @@ -236,7 +243,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable // Create a new connection, and pass a ProxyPromise to establish a proxy tunnel, if needed. // Differently from the case where the connection is created explicitly by applications, here // we need to do a bit more logging and keep track of the connection count in case of failures. - newConnection(new ProxyPromise(promise) + createConnection(new ProxyPromise(promise) { @Override public void succeeded(Connection connection) @@ -260,18 +267,11 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable } } - private void drain(Throwable x) + private void abort(Throwable cause) { - RequestContext requestContext; - while ((requestContext = requests.poll()) != null) - { - Request request = requestContext.request; - requestNotifier.notifyFailure(request, x); - List listeners = requestContext.listeners; - HttpResponse response = new HttpResponse(request, listeners); - responseNotifier.notifyFailure(listeners, response, x); - responseNotifier.notifyComplete(listeners, new Result(request, x, response, x)); - } + HttpExchange exchange; + while ((exchange = exchanges.poll()) != null) + abort(exchange, cause); } /** @@ -282,14 +282,15 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable *

If a request is waiting to be executed, it will be dequeued and executed by the new connection.

* * @param connection the new connection + * @param dispatch whether to dispatch the processing to another thread */ protected void process(Connection connection, boolean dispatch) { // Ugly cast, but lack of generic reification forces it final HttpConnection httpConnection = (HttpConnection)connection; - RequestContext requestContext = requests.poll(); - if (requestContext == null) + final HttpExchange exchange = exchanges.poll(); + if (exchange == null) { LOG.debug("{} idle", httpConnection); if (!idleConnections.offer(httpConnection)) @@ -306,13 +307,12 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable } else { - final Request request = requestContext.request; - final List listeners = requestContext.listeners; + final Request request = exchange.getRequest(); Throwable cause = request.getAbortCause(); if (cause != null) { - abort(request, listeners, cause); - LOG.debug("Aborted {} before processing", request); + abort(exchange, cause); + LOG.debug("Aborted before processing {}: {}", exchange, cause); } else { @@ -328,13 +328,13 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable @Override public void run() { - httpConnection.send(request, listeners); + httpConnection.send(exchange); } }); } else { - httpConnection.send(request, listeners); + httpConnection.send(exchange); } } } @@ -372,7 +372,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable // We need to execute queued requests even if this connection failed. // We may create a connection that is not needed, but it will eventually // idle timeout, so no worries - if (!requests.isEmpty()) + if (!exchanges.isEmpty()) { connection = acquire(); if (connection != null) @@ -391,36 +391,26 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable connection.close(); activeConnections.clear(); - drain(new AsynchronousCloseException()); + abort(new AsynchronousCloseException()); connectionCount.set(0); LOG.debug("Closed {}", this); } - public boolean abort(Request request, Throwable cause) + public boolean remove(HttpExchange exchange) { - for (RequestContext requestContext : requests) - { - if (requestContext.request == request) - { - if (requests.remove(requestContext)) - { - // We were able to remove the pair, so it won't be processed - abort(request, requestContext.listeners, cause); - LOG.debug("Aborted {} while queued", request); - return true; - } - } - } - return false; + return exchanges.remove(exchange); } - private void abort(Request request, List listeners, Throwable cause) + protected void abort(HttpExchange exchange, Throwable cause) { - HttpResponse response = new HttpResponse(request, listeners); - responseNotifier.notifyFailure(listeners, response, cause); - responseNotifier.notifyComplete(listeners, new Result(request, cause, response, cause)); + Request request = exchange.getRequest(); + HttpResponse response = exchange.getResponse(); + getRequestNotifier().notifyFailure(request, cause); + List listeners = exchange.getConversation().getResponseListeners(); + getResponseNotifier().notifyFailure(listeners, response, cause); + getResponseNotifier().notifyComplete(listeners, new Result(request, cause, response, cause)); } @Override @@ -432,7 +422,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable @Override public void dump(Appendable out, String indent) throws IOException { - ContainerLifeCycle.dumpObject(out, this + " - requests queued: " + requests.size()); + ContainerLifeCycle.dumpObject(out, this + " - requests queued: " + exchanges.size()); List connections = new ArrayList<>(); for (Connection connection : idleConnections) connections.add(connection + " - IDLE"); @@ -449,19 +439,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable getScheme(), getHost(), getPort(), - proxyAddress == null ? "" : " via " + proxyAddress.getHostString() + ":" + proxyAddress.getPort()); - } - - private static class RequestContext - { - private final Request request; - private final List listeners; - - private RequestContext(Request request, List listeners) - { - this.request = request; - this.listeners = listeners; - } + proxyAddress == null ? "" : " via " + proxyAddress.getHost() + ":" + proxyAddress.getPort()); } /** @@ -499,8 +477,8 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable private void tunnel(final Connection connection) { - String target = address.getHostString() + ":" + address.getPort(); - Request connect = client.newRequest(proxyAddress.getHostString(), proxyAddress.getPort()) + String target = address.getHost() + ":" + address.getPort(); + Request connect = client.newRequest(proxyAddress.getHost(), proxyAddress.getPort()) .scheme(HttpScheme.HTTP.asString()) .method(HttpMethod.CONNECT) .path(target) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java index fa01cf91161..615542141e1 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java @@ -36,21 +36,23 @@ public class HttpExchange private final AtomicInteger complete = new AtomicInteger(); private final CountDownLatch terminate = new CountDownLatch(2); private final HttpConversation conversation; - private final HttpConnection connection; + private final HttpDestination destination; private final Request request; private final List listeners; private final HttpResponse response; - private volatile boolean last; + private volatile HttpConnection connection; private volatile Throwable requestFailure; private volatile Throwable responseFailure; - public HttpExchange(HttpConversation conversation, HttpConnection connection, Request request, List listeners) + public HttpExchange(HttpConversation conversation, HttpDestination destination, Request request, List listeners) { this.conversation = conversation; - this.connection = connection; + this.destination = destination; this.request = request; this.listeners = listeners; this.response = new HttpResponse(request, listeners); + conversation.getExchanges().offer(this); + conversation.updateResponseListeners(null); } public HttpConversation getConversation() @@ -83,25 +85,9 @@ public class HttpExchange return responseFailure; } - /** - * @return whether this exchange is the last in the conversation - */ - public boolean isLast() + public void setConnection(HttpConnection connection) { - return last; - } - - /** - * @param last whether this exchange is the last in the conversation - */ - public void setLast(boolean last) - { - this.last = last; - } - - public void receive() - { - connection.receive(); + this.connection = connection; } public AtomicMarkableReference requestComplete(Throwable failure) @@ -177,8 +163,7 @@ public class HttpExchange { // Request and response completed LOG.debug("{} complete", this); - if (isLast()) - conversation.complete(); + conversation.complete(); } result = new Result(getRequest(), getRequestFailure(), getResponse(), getResponseFailure()); } @@ -188,10 +173,23 @@ public class HttpExchange public boolean abort(Throwable cause) { - LOG.debug("Aborting {} reason {}", this, cause); - boolean aborted = connection.abort(this, cause); - LOG.debug("Aborted {}: {}", this, aborted); - return aborted; + if (destination.remove(this)) + { + destination.abort(this, cause); + LOG.debug("Aborted while queued {}: {}", this, cause); + return true; + } + else + { + HttpConnection connection = this.connection; + // If there is no connection, this exchange is already completed + if (connection == null) + return false; + + boolean aborted = connection.abort(cause); + LOG.debug("Aborted while active ({}) {}: {}", aborted, this, cause); + return aborted; + } } public void resetResponse(boolean success) @@ -204,7 +202,9 @@ public class HttpExchange public void proceed(boolean proceed) { - connection.proceed(proceed); + HttpConnection connection = this.connection; + if (connection != null) + connection.proceed(proceed); } public void terminateRequest() diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index 10b7a2e116b..1af0caf1432 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -22,7 +22,6 @@ import java.io.EOFException; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -52,13 +51,11 @@ public class HttpReceiver implements HttpParser.ResponseHandler private final AtomicReference state = new AtomicReference<>(State.IDLE); private final HttpParser parser = new HttpParser(this); private final HttpConnection connection; - private final ResponseNotifier responseNotifier; private ContentDecoder decoder; public HttpReceiver(HttpConnection connection) { this.connection = connection; - this.responseNotifier = new ResponseNotifier(connection.getHttpClient()); } public void receive() @@ -146,41 +143,19 @@ public class HttpReceiver implements HttpParser.ResponseHandler response.version(version).status(status).reason(reason); // Probe the protocol handlers - HttpExchange initialExchange = conversation.getExchanges().peekFirst(); HttpClient client = connection.getHttpClient(); ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.getRequest(), response); - Response.Listener handlerListener = protocolHandler == null ? null : protocolHandler.getResponseListener(); - if (handlerListener == null) - { - exchange.setLast(true); - if (initialExchange == exchange) - { - conversation.setResponseListeners(exchange.getResponseListeners()); - } - else - { - List listeners = new ArrayList<>(exchange.getResponseListeners()); - listeners.addAll(initialExchange.getResponseListeners()); - conversation.setResponseListeners(listeners); - } - } - else + Response.Listener handlerListener = null; + if (protocolHandler != null) { + handlerListener = protocolHandler.getResponseListener(); LOG.debug("Found protocol handler {}", protocolHandler); - if (initialExchange == exchange) - { - conversation.setResponseListeners(Collections.singletonList(handlerListener)); - } - else - { - List listeners = new ArrayList<>(exchange.getResponseListeners()); - listeners.add(handlerListener); - conversation.setResponseListeners(listeners); - } } + exchange.getConversation().updateResponseListeners(handlerListener); LOG.debug("Receiving {}", response); - responseNotifier.notifyBegin(conversation.getResponseListeners(), response); + ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); + notifier.notifyBegin(conversation.getResponseListeners(), response); } } return false; @@ -197,7 +172,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler { HttpConversation conversation = exchange.getConversation(); HttpResponse response = exchange.getResponse(); - boolean process = responseNotifier.notifyHeader(conversation.getResponseListeners(), response, field); + ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); + boolean process = notifier.notifyHeader(conversation.getResponseListeners(), response, field); if (process) { response.getHeaders().add(field); @@ -250,7 +226,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler HttpConversation conversation = exchange.getConversation(); HttpResponse response = exchange.getResponse(); LOG.debug("Headers {}", response); - responseNotifier.notifyHeaders(conversation.getResponseListeners(), response); + ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); + notifier.notifyHeaders(conversation.getResponseListeners(), response); Enumeration contentEncodings = response.getHeaders().getValues(HttpHeader.CONTENT_ENCODING.asString(), ","); if (contentEncodings != null) @@ -292,7 +269,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler LOG.debug("{} {}: {} bytes", decoder, response, buffer.remaining()); } - responseNotifier.notifyContent(conversation.getResponseListeners(), response, buffer); + ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); + notifier.notifyContent(conversation.getResponseListeners(), response, buffer); } } return false; @@ -326,15 +304,15 @@ public class HttpReceiver implements HttpParser.ResponseHandler HttpResponse response = exchange.getResponse(); List listeners = exchange.getConversation().getResponseListeners(); - responseNotifier.notifySuccess(listeners, response); + ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); + notifier.notifySuccess(listeners, response); LOG.debug("Received {}", response); Result result = completion.getReference(); if (result != null) { connection.complete(exchange, !result.isFailed()); - - responseNotifier.notifyComplete(listeners, result); + notifier.notifyComplete(listeners, result); } return true; @@ -368,7 +346,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler HttpResponse response = exchange.getResponse(); HttpConversation conversation = exchange.getConversation(); - responseNotifier.notifyFailure(conversation.getResponseListeners(), response, failure); + ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); + notifier.notifyFailure(conversation.getResponseListeners(), response, failure); LOG.debug("Failed {} {}", response, failure); Result result = completion.getReference(); @@ -376,7 +355,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler { connection.complete(exchange, false); - responseNotifier.notifyComplete(conversation.getResponseListeners(), result); + notifier.notifyComplete(conversation.getResponseListeners(), result); } return true; @@ -411,7 +390,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler fail(new TimeoutException()); } - public boolean abort(HttpExchange exchange, Throwable cause) + public boolean abort(Throwable cause) { return fail(cause); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index c0c320eb255..a6646be194e 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -82,7 +82,7 @@ public class HttpRequest implements Request scheme = uri.getScheme(); host = uri.getHost(); port = client.normalizePort(scheme, uri.getPort()); - path = uri.getPath(); + path = uri.getRawPath(); String query = uri.getRawQuery(); if (query != null) { @@ -292,6 +292,13 @@ public class HttpRequest implements Request return this; } + @Override + public Request onRequestContent(ContentListener listener) + { + this.requestListeners.add(listener); + return this; + } + @Override public Request onRequestSuccess(SuccessListener listener) { @@ -468,8 +475,7 @@ public class HttpRequest implements Request public boolean abort(Throwable cause) { aborted = Objects.requireNonNull(cause); - if (client.provideDestination(getScheme(), getHost(), getPort()).abort(this, cause)) - return true; + // The conversation may be null if it is already completed HttpConversation conversation = client.getConversation(getConversationID(), false); return conversation != null && conversation.abort(cause); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java index ef8ece5901c..ba579a5b68a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java @@ -19,10 +19,8 @@ package org.eclipse.jetty.client; import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; -import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicMarkableReference; @@ -30,7 +28,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpHeader; @@ -51,16 +48,12 @@ public class HttpSender implements AsyncContentProvider.Listener private final AtomicReference sendState = new AtomicReference<>(SendState.IDLE); private final HttpGenerator generator = new HttpGenerator(); private final HttpConnection connection; - private final RequestNotifier requestNotifier; - private final ResponseNotifier responseNotifier; private Iterator contentIterator; private ContinueContentChunk continueContentChunk; public HttpSender(HttpConnection connection) { this.connection = connection; - this.requestNotifier = new RequestNotifier(connection.getHttpClient()); - this.responseNotifier = new ResponseNotifier(connection.getHttpClient()); } @Override @@ -108,20 +101,6 @@ public class HttpSender implements AsyncContentProvider.Listener if (!updateState(State.IDLE, State.BEGIN)) throw new IllegalStateException(); - // Arrange the listeners, so that if there is a request failure the proper listeners are notified - HttpConversation conversation = exchange.getConversation(); - HttpExchange initialExchange = conversation.getExchanges().peekFirst(); - if (initialExchange == exchange) - { - conversation.setResponseListeners(exchange.getResponseListeners()); - } - else - { - List listeners = new ArrayList<>(exchange.getResponseListeners()); - listeners.addAll(initialExchange.getResponseListeners()); - conversation.setResponseListeners(listeners); - } - Request request = exchange.getRequest(); Throwable cause = request.getAbortCause(); if (cause != null) @@ -131,7 +110,8 @@ public class HttpSender implements AsyncContentProvider.Listener else { LOG.debug("Sending {}", request); - requestNotifier.notifyBegin(request); + RequestNotifier notifier = connection.getDestination().getRequestNotifier(); + notifier.notifyBegin(request); ContentProvider content = request.getContent(); this.contentIterator = content == null ? Collections.emptyIterator() : content.iterator(); @@ -182,13 +162,16 @@ public class HttpSender implements AsyncContentProvider.Listener while (true) { - HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, contentChunk.content, contentChunk.lastContent); + ByteBuffer content = contentChunk.content; + final ByteBuffer contentBuffer = content == null ? null : content.slice(); + + HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, content, contentChunk.lastContent); switch (result) { case NEED_INFO: { - ContentProvider content = request.getContent(); - long contentLength = content == null ? -1 : content.getLength(); + ContentProvider requestContent = request.getContent(); + long contentLength = requestContent == null ? -1 : requestContent.getLength(); requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod().asString(), request.getPath()); break; } @@ -214,7 +197,8 @@ public class HttpSender implements AsyncContentProvider.Listener { if (!updateState(currentState, State.HEADERS)) continue; - requestNotifier.notifyHeaders(request); + RequestNotifier notifier = connection.getDestination().getRequestNotifier(); + notifier.notifyHeaders(request); break out; } case HEADERS: @@ -243,16 +227,9 @@ public class HttpSender implements AsyncContentProvider.Listener { LOG.debug("Write succeeded for {}", request); - if (!commit(request)) + if (!processWrite(request, contentBuffer, expecting100ContinueResponse)) return; - if (expecting100ContinueResponse) - { - LOG.debug("Expecting 100 Continue for {}", request); - continueContentChunk.signal(); - return; - } - send(); } @@ -269,7 +246,7 @@ public class HttpSender implements AsyncContentProvider.Listener continueContentChunk = new ContinueContentChunk(contentChunk); } - write(callback, header, chunk, expecting100ContinueResponse ? null : contentChunk.content); + write(callback, header, chunk, expecting100ContinueResponse ? null : content); if (callback.process()) { @@ -279,16 +256,9 @@ public class HttpSender implements AsyncContentProvider.Listener if (callback.isSucceeded()) { - if (!commit(request)) + if (!processWrite(request, contentBuffer, expecting100ContinueResponse)) return; - if (expecting100ContinueResponse) - { - LOG.debug("Expecting 100 Continue for {}", request); - continueContentChunk.signal(); - return; - } - // Send further content contentChunk = new ContentChunk(contentIterator); @@ -382,6 +352,27 @@ public class HttpSender implements AsyncContentProvider.Listener } } + private boolean processWrite(Request request, ByteBuffer content, boolean expecting100ContinueResponse) + { + if (!commit(request)) + return false; + + if (content != null) + { + RequestNotifier notifier = connection.getDestination().getRequestNotifier(); + notifier.notifyContent(request, content); + } + + if (expecting100ContinueResponse) + { + LOG.debug("Expecting 100 Continue for {}", request); + continueContentChunk.signal(); + return false; + } + + return true; + } + public void proceed(boolean proceed) { ContinueContentChunk contentChunk = continueContentChunk; @@ -461,7 +452,8 @@ public class HttpSender implements AsyncContentProvider.Listener if (!updateState(current, State.COMMIT)) continue; LOG.debug("Committed {}", request); - requestNotifier.notifyCommit(request); + RequestNotifier notifier = connection.getDestination().getRequestNotifier(); + notifier.notifyCommit(request); return true; case COMMIT: if (!updateState(current, State.COMMIT)) @@ -495,8 +487,9 @@ public class HttpSender implements AsyncContentProvider.Listener // It is important to notify completion *after* we reset because // the notification may trigger another request/response + HttpDestination destination = connection.getDestination(); Request request = exchange.getRequest(); - requestNotifier.notifySuccess(request); + destination.getRequestNotifier().notifySuccess(request); LOG.debug("Sent {}", request); Result result = completion.getReference(); @@ -505,7 +498,7 @@ public class HttpSender implements AsyncContentProvider.Listener connection.complete(exchange, !result.isFailed()); HttpConversation conversation = exchange.getConversation(); - responseNotifier.notifyComplete(conversation.getResponseListeners(), result); + destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result); } return true; @@ -533,8 +526,9 @@ public class HttpSender implements AsyncContentProvider.Listener exchange.terminateRequest(); + HttpDestination destination = connection.getDestination(); Request request = exchange.getRequest(); - requestNotifier.notifyFailure(request, failure); + destination.getRequestNotifier().notifyFailure(request, failure); LOG.debug("Failed {} {}", request, failure); Result result = completion.getReference(); @@ -551,13 +545,13 @@ public class HttpSender implements AsyncContentProvider.Listener connection.complete(exchange, false); HttpConversation conversation = exchange.getConversation(); - responseNotifier.notifyComplete(conversation.getResponseListeners(), result); + destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result); } return true; } - public boolean abort(HttpExchange exchange, Throwable cause) + public boolean abort(Throwable cause) { State current = state.get(); boolean abortable = isBeforeCommit(current) || diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java index 2e88dc08537..c158b294811 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java @@ -19,7 +19,10 @@ package org.eclipse.jetty.client; import java.net.URI; +import java.net.URISyntaxException; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -28,6 +31,14 @@ import org.eclipse.jetty.http.HttpMethod; public class RedirectProtocolHandler extends Response.Listener.Empty implements ProtocolHandler { + private static String SCHEME_REGEXP = "(^https?)"; + private static String AUTHORITY_REGEXP = "([^/\\?#]+)"; + // The location may be relative so the scheme://authority part may be missing + private static String DESTINATION_REGEXP = "(" + SCHEME_REGEXP + "://" + AUTHORITY_REGEXP + ")?"; + private static String PATH_REGEXP = "([^\\?#]*)"; + private static String QUERY_REGEXP = "([^#]*)"; + private static String FRAGMENT_REGEXP = "(.*)"; + private static Pattern URI_PATTERN = Pattern.compile(DESTINATION_REGEXP + PATH_REGEXP + QUERY_REGEXP + FRAGMENT_REGEXP); private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirects"; private final HttpClient client; @@ -66,37 +77,55 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements { Request request = result.getRequest(); Response response = result.getResponse(); - URI location = URI.create(response.getHeaders().get("location")); - int status = response.getStatus(); - switch (status) + String location = response.getHeaders().get("location"); + if (location != null) { - case 301: + URI newURI = sanitize(location); + if (newURI != null) { - if (request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.HEAD) - redirect(result, request.getMethod(), location); - else - fail(result, new HttpResponseException("HTTP protocol violation: received 301 for non GET or HEAD request", response)); - break; + if (!newURI.isAbsolute()) + newURI = request.getURI().resolve(newURI); + + int status = response.getStatus(); + switch (status) + { + case 301: + { + if (request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.HEAD) + redirect(result, request.getMethod(), newURI); + else + fail(result, new HttpResponseException("HTTP protocol violation: received 301 for non GET or HEAD request", response)); + break; + } + case 302: + case 303: + { + // Redirect must be done using GET + redirect(result, HttpMethod.GET, newURI); + break; + } + case 307: + { + // Keep same method + redirect(result, request.getMethod(), newURI); + break; + } + default: + { + fail(result, new HttpResponseException("Unhandled HTTP status code " + status, response)); + break; + } + } } - case 302: - case 303: + else { - // Redirect must be done using GET - redirect(result, HttpMethod.GET, location); - break; - } - case 307: - { - // Keep same method - redirect(result, request.getMethod(), location); - break; - } - default: - { - fail(result, new HttpResponseException("Unhandled HTTP status code " + status, response)); - break; + fail(result, new HttpResponseException("Malformed Location header " + location, response)); } } + else + { + fail(result, new HttpResponseException("Missing Location header " + location, response)); + } } else { @@ -104,6 +133,43 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements } } + private URI sanitize(String location) + { + // Redirects should be valid, absolute, URIs, with properly escaped paths and encoded + // query parameters. However, shit happens, and here we try our best to recover. + + try + { + // Direct hit first: if passes, we're good + return new URI(location); + } + catch (URISyntaxException x) + { + Matcher matcher = URI_PATTERN.matcher(location); + if (matcher.matches()) + { + String scheme = matcher.group(2); + String authority = matcher.group(3); + String path = matcher.group(4); + String query = matcher.group(5); + if (query.length() == 0) + query = null; + String fragment = matcher.group(6); + if (fragment.length() == 0) + fragment = null; + try + { + return new URI(scheme, authority, path, query, fragment); + } + catch (URISyntaxException xx) + { + // Give up + } + } + return null; + } + } + private void redirect(Result result, HttpMethod method, URI location) { final Request request = result.getRequest(); @@ -146,8 +212,8 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements Request request = result.getRequest(); Response response = result.getResponse(); HttpConversation conversation = client.getConversation(request.getConversationID(), false); - List listeners = conversation.getExchanges().peekFirst().getResponseListeners(); - // TODO: should we replay all events, or just the failure ? + conversation.updateResponseListeners(null); + List listeners = conversation.getResponseListeners(); notifier.notifyFailure(listeners, response, failure); notifier.notifyComplete(listeners, new Result(request, response, failure)); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java index 4e32e54a1f2..5c0c47945e7 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.client; +import java.nio.ByteBuffer; import java.util.List; import org.eclipse.jetty.client.api.Request; @@ -155,6 +156,36 @@ public class RequestNotifier } } + public void notifyContent(Request request, ByteBuffer content) + { + // Optimized to avoid allocations of iterator instances + List requestListeners = request.getRequestListeners(null); + for (int i = 0; i < requestListeners.size(); ++i) + { + Request.RequestListener listener = requestListeners.get(i); + if (listener instanceof Request.ContentListener) + notifyContent((Request.ContentListener)listener, request, content); + } + List listeners = client.getRequestListeners(); + for (int i = 0; i < listeners.size(); ++i) + { + Request.Listener listener = listeners.get(i); + notifyContent(listener, request, content); + } + } + + private void notifyContent(Request.ContentListener listener, Request request, ByteBuffer content) + { + try + { + listener.onContent(request, content); + } + catch (Exception x) + { + LOG.info("Exception while notifying listener " + listener, x); + } + } + public void notifySuccess(Request request) { // Optimized to avoid allocations of iterator instances diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java index 838b4073241..df001b2030e 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java @@ -18,13 +18,15 @@ package org.eclipse.jetty.client.api; +import org.eclipse.jetty.util.Promise; + /** * {@link Connection} represent a connection to a {@link Destination} and allow applications to send * requests via {@link #send(Request, Response.CompleteListener)}. *

* {@link Connection}s are normally pooled by {@link Destination}s, but unpooled {@link Connection}s * may be created by applications that want to do their own connection management via - * {@link Destination#newConnection()} and {@link Connection#close()}. + * {@link Destination#newConnection(Promise)} and {@link Connection#close()}. */ public interface Connection extends AutoCloseable { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java index c406e81ba35..bf09e7a88d1 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java @@ -18,16 +18,16 @@ package org.eclipse.jetty.client.api; -import java.util.concurrent.Future; - import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.Promise; /** * {@link Destination} represents the triple made of the {@link #getScheme}, the {@link #getHost} * and the {@link #getPort}. *

* {@link Destination} holds a pool of {@link Connection}s, but allows to create unpooled - * connections if the application wants full control over connection management via {@link #newConnection()}. + * connections if the application wants full control over connection management via {@link #newConnection(Promise)}. *

* {@link Destination}s may be obtained via {@link HttpClient#getDestination(String, String, int)} */ @@ -49,7 +49,40 @@ public interface Destination int getPort(); /** - * @return a future to a new, unpooled, {@link Connection} + * Creates asynchronously a new, unpooled, {@link Connection} that will be returned + * at a later time through the given {@link Promise}. + *

+ * Use {@link FuturePromise} to wait for the connection: + *

+     * Destination destination = ...;
+     * FuturePromise<Connection> futureConnection = new FuturePromise<>();
+     * destination.newConnection(futureConnection);
+     * Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
+     * 
+ * + * @param promise the promise of a new, unpooled, {@link Connection} */ - Future newConnection(); + void newConnection(Promise promise); + + public static class Address + { + private final String host; + private final int port; + + public Address(String host, int port) + { + this.host = host; + this.port = port; + } + + public String getHost() + { + return host; + } + + public int getPort() + { + return port; + } + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index 78f68a419b1..f5c6705d54e 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.client.api; import java.io.IOException; import java.net.URI; +import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.EventListener; import java.util.List; @@ -265,6 +266,12 @@ public interface Request */ Request onRequestCommit(CommitListener listener); + /** + * @param listener a listener for request content events + * @return this request object + */ + Request onRequestContent(ContentListener listener); + /** * @param listener a listener for request success event * @return this request object @@ -416,6 +423,19 @@ public interface Request public void onCommit(Request request); } + /** + * Listener for the request content event. + */ + public interface ContentListener extends RequestListener + { + /** + * Callback method invoked when a chunk of request content has been sent successfully. + * Changes to bytes in the given buffer have no effect, as the content has already been sent. + * @param request the request that has been committed + */ + public void onContent(Request request, ByteBuffer content); + } + /** * Listener for the request succeeded event. */ @@ -445,7 +465,7 @@ public interface Request /** * Listener for all request events. */ - public interface Listener extends QueuedListener, BeginListener, HeadersListener, CommitListener, SuccessListener, FailureListener + public interface Listener extends QueuedListener, BeginListener, HeadersListener, CommitListener, ContentListener, SuccessListener, FailureListener { /** * An empty implementation of {@link Listener} @@ -472,6 +492,11 @@ public interface Request { } + @Override + public void onContent(Request request, ByteBuffer content) + { + } + @Override public void onSuccess(Request request) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java index 64d0e77c504..e168e038392 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java @@ -23,6 +23,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.client.AsyncContentProvider; @@ -34,6 +35,10 @@ import org.eclipse.jetty.client.api.Response; * A {@link ContentProvider} that allows to add content after {@link Request#send(Response.CompleteListener)} * has been called, therefore providing the request content at a later time. *

+ * {@link DeferredContentProvider} can only be used in conjunction with + * {@link Request#send(Response.CompleteListener)} (and not with its blocking counterpart {@link Request#send()}) + * because it provides content asynchronously. + *

* The deferred content is provided once and then fully consumed. * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator * because the stream has been consumed on the first invocation. @@ -79,6 +84,7 @@ public class DeferredContentProvider implements AsyncContentProvider, AutoClosea private final Queue chunks = new ConcurrentLinkedQueue<>(); private final AtomicReference listener = new AtomicReference<>(); private final Iterator iterator = new DeferredContentProviderIterator(); + private final AtomicBoolean closed = new AtomicBoolean(); /** * Creates a new {@link DeferredContentProvider} with the given initial content @@ -124,8 +130,11 @@ public class DeferredContentProvider implements AsyncContentProvider, AutoClosea */ public void close() { - chunks.offer(CLOSE); - notifyListener(); + if (closed.compareAndSet(false, true)) + { + chunks.offer(CLOSE); + notifyListener(); + } } private void notifyListener() diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java new file mode 100644 index 00000000000..5becf700319 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java @@ -0,0 +1,132 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Iterator; + +import org.eclipse.jetty.client.AsyncContentProvider; +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; + +/** + * A {@link ContentProvider} that provides content asynchronously through an {@link OutputStream} + * similar to {@link DeferredContentProvider}. + *

+ * {@link OutputStreamContentProvider} can only be used in conjunction with + * {@link Request#send(Response.CompleteListener)} (and not with its blocking counterpart {@link Request#send()}) + * because it provides content asynchronously. + *

+ * The deferred content is provided once by writing to the {@link #getOutputStream() output stream} + * and then fully consumed. + * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator + * because the stream has been consumed on the first invocation. + * However, it is possible for subclasses to support multiple invocations of {@link #iterator()} + * by overriding {@link #write(ByteBuffer)} and {@link #close()}, copying the bytes and making them + * available for subsequent invocations. + *

+ * Content must be provided by writing to the {@link #getOutputStream() output stream}, that must be + * {@link OutputStream#close() closed} when all content has been provided. + *

+ * Example usage: + *

+ * HttpClient httpClient = ...;
+ *
+ * // Use try-with-resources to autoclose the output stream
+ * OutputStreamContentProvider content = new OutputStreamContentProvider();
+ * try (OutputStream output = content.getOutputStream())
+ * {
+ *     httpClient.newRequest("localhost", 8080)
+ *             .content(content)
+ *             .send(new Response.CompleteListener()
+ *             {
+ *                 @Override
+ *                 public void onComplete(Result result)
+ *                 {
+ *                     // Your logic here
+ *                 }
+ *             });
+ *
+ *     // At a later time...
+ *     output.write("some content".getBytes());
+ * }
+ * 
+ */ +public class OutputStreamContentProvider implements AsyncContentProvider +{ + private final DeferredContentProvider deferred = new DeferredContentProvider(); + private final OutputStream output = new DeferredOutputStream(); + + @Override + public long getLength() + { + return deferred.getLength(); + } + + @Override + public Iterator iterator() + { + return deferred.iterator(); + } + + @Override + public void setListener(Listener listener) + { + deferred.setListener(listener); + } + + public OutputStream getOutputStream() + { + return output; + } + + protected void write(ByteBuffer buffer) + { + deferred.offer(buffer); + } + + protected void close() + { + deferred.close(); + } + + private class DeferredOutputStream extends OutputStream + { + @Override + public void write(int b) throws IOException + { + write(new byte[]{(byte)b}, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + OutputStreamContentProvider.this.write(ByteBuffer.wrap(b, off, len)); + } + + @Override + public void close() throws IOException + { + OutputStreamContentProvider.this.close(); + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java index 7522f64ae94..7fe669c4235 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java @@ -19,6 +19,8 @@ package org.eclipse.jetty.client; import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.security.cert.CertificateException; import java.util.concurrent.ExecutionException; import javax.net.ssl.SSLHandshakeException; import javax.servlet.ServletException; @@ -32,16 +34,17 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.After; import org.junit.Before; import org.junit.Test; import static junit.framework.Assert.fail; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertThat; /** - * This test class runs tests to make sure that hostname verification (http://www.ietf.org/rfc/rfc2818.txt section 3 - * .1) is configurable in SslContextFactory and works as expected. + * This test class runs tests to make sure that hostname verification (http://www.ietf.org/rfc/rfc2818.txt + * section 3.1) is configurable in SslContextFactory and works as expected. */ public class HostnameVerificationTest { @@ -82,6 +85,14 @@ public class HostnameVerificationTest client.start(); } + @After + public void tearDown() throws Exception + { + client.stop(); + server.stop(); + server.join(); + } + /** * This test is supposed to verify that hostname verification works as described in: * http://www.ietf.org/rfc/rfc2818.txt section 3.1. It uses a certificate with a common name different to localhost @@ -98,10 +109,20 @@ public class HostnameVerificationTest client.GET(uri); fail("sending request to client should have failed with an Exception!"); } - catch (ExecutionException e) + catch (ExecutionException x) { - assertThat("We got a SSLHandshakeException as localhost doesn't match the hostname of the certificate", - e.getCause().getCause(), instanceOf(SSLHandshakeException.class)); + // The test may fail in 2 ways, since the CertificateException thrown because of the hostname + // verification failure is not rethrown immediately by the JDK SSL implementation, but only + // rethrown on the next read or write. + // Therefore this test may catch a SSLHandshakeException, or a ClosedChannelException. + // If it is the former, we verify that its cause is a CertificateException. + + // ExecutionException wraps an EofException that wraps the SSLHandshakeException + Throwable cause = x.getCause().getCause(); + if (cause instanceof SSLHandshakeException) + assertThat(cause.getCause().getCause(), instanceOf(CertificateException.class)); + else + assertThat(cause, instanceOf(ClosedChannelException.class)); } } @@ -114,7 +135,28 @@ public class HostnameVerificationTest @Test public void simpleGetWithHostnameVerificationDisabledTest() throws Exception { - sslContextFactory.setEndpointIdentificationAlgorithm(""); + sslContextFactory.setEndpointIdentificationAlgorithm(null); + String uri = "https://localhost:" + connector.getLocalPort() + "/"; + try + { + client.GET(uri); + } + catch (ExecutionException e) + { + fail("SSLHandshake should work just fine as hostname verification is disabled! " + e.getMessage()); + } + } + + /** + * This test has hostname verification disabled by setting trustAll to true and connecting, + * ssl handshake and sending the request should just work fine. + * + * @throws Exception + */ + @Test + public void trustAllDisablesHostnameVerificationTest() throws Exception + { + sslContextFactory.setTrustAll(true); String uri = "https://localhost:" + connector.getLocalPort() + "/"; try { diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index 8247c491234..80c3d0b9308 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -49,7 +49,6 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest @@ -104,7 +103,6 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest test_Authentication(new BasicAuthentication(uri, realm, "basic", "basic")); } - @Ignore @Test public void test_DigestAuthentication() throws Exception { @@ -135,6 +133,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest Assert.assertEquals(401, response.getStatus()); Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS)); client.getRequestListeners().remove(requestListener); + Assert.assertNull(client.getConversation(request.getConversationID(), false)); authenticationStore.addAuthentication(authentication); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java index 85792846b43..c49b37b97d7 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.toolchain.test.annotation.Slow; +import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.Assert; import org.junit.Test; @@ -43,7 +44,9 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe start(new EmptyServerHandler()); Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort()); - try (Connection connection = destination.newConnection().get(5, TimeUnit.SECONDS)) + FuturePromise futureConnection = new FuturePromise<>(); + destination.newConnection(futureConnection); + try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS)) { Request request = client.newRequest(destination.getHost(), destination.getPort()).scheme(scheme); FutureResponseListener listener = new FutureResponseListener(request); @@ -66,7 +69,9 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe start(new EmptyServerHandler()); Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort()); - Connection connection = destination.newConnection().get(5, TimeUnit.SECONDS); + FuturePromise futureConnection = new FuturePromise<>(); + destination.newConnection(futureConnection); + Connection connection = futureConnection.get(5, TimeUnit.SECONDS); Request request = client.newRequest(destination.getHost(), destination.getPort()).scheme(scheme); FutureResponseListener listener = new FutureResponseListener(request); connection.send(request, listener); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java index eb13ece5549..be0af57cc13 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java @@ -18,13 +18,12 @@ package org.eclipse.jetty.client; -import static org.junit.Assert.fail; - import java.io.IOException; +import java.net.URLDecoder; import java.nio.ByteBuffer; +import java.nio.channels.UnresolvedAddressException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -38,10 +37,13 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.fail; + public class HttpClientRedirectTest extends AbstractHttpClientServerTest { public HttpClientRedirectTest(SslContextFactory sslContextFactory) @@ -199,6 +201,62 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest Assert.assertTrue(response.getHeaders().containsKey(HttpHeader.LOCATION.asString())); } + @Test + public void testRelativeLocation() throws Exception + { + Response response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .path("/303/localhost/done?relative=true") + .timeout(5, TimeUnit.SECONDS) + .send(); + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString())); + } + + @Test + public void testAbsoluteURIPathWithSpaces() throws Exception + { + Response response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .path("/303/localhost/a+space?decode=true") + .timeout(5, TimeUnit.SECONDS) + .send(); + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString())); + } + + @Test + public void testRelativeURIPathWithSpaces() throws Exception + { + Response response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .path("/303/localhost/a+space?relative=true&decode=true") + .timeout(5, TimeUnit.SECONDS) + .send(); + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString())); + } + + @Test + public void testRedirectFailed() throws Exception + { + try + { + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .path("/303/doesNotExist/done") + .timeout(5, TimeUnit.SECONDS) + .send(); + } + catch (ExecutionException x) + { + Assert.assertThat(x.getCause(), Matchers.instanceOf(UnresolvedAddressException.class)); + } + } + private class RedirectHandler extends AbstractHandler { @Override @@ -212,10 +270,17 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest response.setStatus(status); String host = paths[2]; - response.setHeader("Location", request.getScheme() + "://" + host + ":" + request.getServerPort() + "/" + paths[3]); + String path = paths[3]; + boolean relative = Boolean.parseBoolean(request.getParameter("relative")); + String location = relative ? "" : request.getScheme() + "://" + host + ":" + request.getServerPort(); + location += "/" + path; - String close = request.getParameter("close"); - if (Boolean.parseBoolean(close)) + if (Boolean.parseBoolean(request.getParameter("decode"))) + location = URLDecoder.decode(location, "UTF-8"); + + response.setHeader("Location", location); + + if (Boolean.parseBoolean(request.getParameter("close"))) response.setHeader("Connection", "close"); } catch (NumberFormatException x) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java index 856172a20b8..ab7aaf13814 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java @@ -47,6 +47,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.client.util.InputStreamResponseListener; +import org.eclipse.jetty.client.util.OutputStreamContentProvider; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.annotation.Slow; @@ -366,6 +367,7 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest } }); + // Make sure we provide the content *after* the request has been "sent". Thread.sleep(1000); try (ByteArrayInputStream input = new ByteArrayInputStream(new byte[1024])) @@ -505,4 +507,46 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } + + @Test + public void testUploadWithOutputStream() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + IO.copy(request.getInputStream(), response.getOutputStream()); + } + }); + + final byte[] data = new byte[512]; + final CountDownLatch latch = new CountDownLatch(1); + OutputStreamContentProvider content = new OutputStreamContentProvider(); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .content(content) + .send(new BufferingResponseListener() + { + @Override + public void onComplete(Result result) + { + if (result.isSucceeded() && + result.getResponse().getStatus() == 200 && + Arrays.equals(data, getContent())) + latch.countDown(); + } + }); + + // Make sure we provide the content *after* the request has been "sent". + Thread.sleep(1000); + + try (OutputStream output = content.getOutputStream()) + { + output.write(data); + } + + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index bb9bea6d97e..225078050b0 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -28,6 +28,7 @@ import java.nio.channels.UnresolvedAddressException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -35,6 +36,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletException; @@ -285,6 +287,59 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertArrayEquals(content, response.getContent()); } + @Test + public void test_POST_WithContent_NotifiesRequestContentListener() throws Exception + { + final byte[] content = {0, 1, 2, 3}; + start(new EmptyServerHandler()); + + ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort()) + .onRequestContent(new Request.ContentListener() + { + @Override + public void onContent(Request request, ByteBuffer buffer) + { + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + if (!Arrays.equals(content, bytes)) + request.abort(new Exception()); + } + }) + .content(new BytesContentProvider(content)) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + } + + @Test + public void test_POST_WithContent_TracksProgress() throws Exception + { + start(new EmptyServerHandler()); + + final AtomicInteger progress = new AtomicInteger(); + ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort()) + .onRequestContent(new Request.ContentListener() + { + @Override + public void onContent(Request request, ByteBuffer buffer) + { + byte[] bytes = new byte[buffer.remaining()]; + Assert.assertEquals(1, bytes.length); + buffer.get(bytes); + Assert.assertEquals(bytes[0], progress.getAndIncrement()); + } + }) + .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4})) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(5, progress.get()); + } + @Test public void test_QueuedRequest_IsSent_WhenPreviousRequestSucceeded() throws Exception { diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java index 6ac176795ea..3297b80802e 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.annotation.Slow; +import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.hamcrest.Matchers; @@ -175,7 +176,9 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(1); Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort()); - try (Connection connection = destination.newConnection().get(5, TimeUnit.SECONDS)) + FuturePromise futureConnection = new FuturePromise<>(); + destination.newConnection(futureConnection); + try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS)) { Request request = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) @@ -203,7 +206,9 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(1); Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort()); - try (Connection connection = destination.newConnection().get(5, TimeUnit.SECONDS)) + FuturePromise futureConnection = new FuturePromise<>(); + destination.newConnection(futureConnection); + try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS)) { Request request = client.newRequest(destination.getHost(), destination.getPort()) .scheme(scheme) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java index 2cfdc3b665b..ad78b1ed968 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java @@ -74,9 +74,9 @@ public class HttpReceiverTest { HttpRequest request = new HttpRequest(client, URI.create("http://localhost")); FutureResponseListener listener = new FutureResponseListener(request); - HttpExchange exchange = new HttpExchange(conversation, connection, request, Collections.singletonList(listener)); + HttpExchange exchange = new HttpExchange(conversation, destination, request, Collections.singletonList(listener)); conversation.getExchanges().offer(exchange); - connection.setExchange(exchange); + connection.associate(exchange); exchange.requestComplete(null); exchange.terminateRequest(); return exchange; @@ -91,7 +91,7 @@ public class HttpReceiverTest "\r\n"); HttpExchange exchange = newExchange(); FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0); - exchange.receive(); + connection.receive(); Response response = listener.get(5, TimeUnit.SECONDS); Assert.assertNotNull(response); @@ -115,7 +115,7 @@ public class HttpReceiverTest content); HttpExchange exchange = newExchange(); FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0); - exchange.receive(); + connection.receive(); Response response = listener.get(5, TimeUnit.SECONDS); Assert.assertNotNull(response); @@ -142,9 +142,9 @@ public class HttpReceiverTest content1); HttpExchange exchange = newExchange(); FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0); - exchange.receive(); + connection.receive(); endPoint.setInputEOF(); - exchange.receive(); + connection.receive(); try { @@ -166,7 +166,7 @@ public class HttpReceiverTest "\r\n"); HttpExchange exchange = newExchange(); FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0); - exchange.receive(); + connection.receive(); // Simulate an idle timeout connection.idleTimeout(); @@ -190,7 +190,7 @@ public class HttpReceiverTest "\r\n"); HttpExchange exchange = newExchange(); FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0); - exchange.receive(); + connection.receive(); try { @@ -221,20 +221,20 @@ public class HttpReceiverTest "\r\n"); HttpExchange exchange = newExchange(); FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0); - exchange.receive(); + connection.receive(); endPoint.reset(); ByteBuffer buffer = ByteBuffer.wrap(gzip); int fragment = buffer.limit() - 1; buffer.limit(fragment); endPoint.setInput(buffer); - exchange.receive(); + connection.receive(); endPoint.reset(); buffer.limit(gzip.length); buffer.position(fragment); endPoint.setInput(buffer); - exchange.receive(); + connection.receive(); ContentResponse response = listener.get(5, TimeUnit.SECONDS); Assert.assertNotNull(response); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java index 293e772c387..5d1a2b12796 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.client; -import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; @@ -26,7 +25,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -36,11 +34,9 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.ByteBufferContentProvider; -import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.log.StdErrLog; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.Assert; import org.junit.Test; @@ -226,7 +222,6 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest } }); - StdErrLog.getLogger(HttpChannel.class).setHideStacks(true); final Throwable cause = new Exception(); try { @@ -254,24 +249,51 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest } catch (ExecutionException x) { - Throwable abort = x.getCause(); - if (abort instanceof EOFException) - { - // Server closed abruptly - System.err.println("C"); - } - else if (abort == cause) - { - // Expected - } - else - { - throw x; - } + Assert.assertSame(cause, x.getCause()); } - finally + } + + @Test + public void testAbortOnContent() throws Exception + { + start(new EmptyServerHandler() { - StdErrLog.getLogger(HttpChannel.class).setHideStacks(false); + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + super.handle(target, baseRequest, request, response); + IO.copy(request.getInputStream(), response.getOutputStream()); + } + }); + + final Throwable cause = new Exception(); + try + { + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onRequestContent(new Request.ContentListener() + { + @Override + public void onContent(Request request, ByteBuffer content) + { + request.abort(cause); + } + }) + .content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1})) + { + @Override + public long getLength() + { + return -1; + } + }) + .timeout(5, TimeUnit.SECONDS) + .send(); + Assert.fail(); + } + catch (ExecutionException x) + { + Assert.assertSame(cause, x.getCause()); } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java index eeb93dcac3d..cd7d09e0989 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.client.api; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.io.OutputStream; import java.net.HttpCookie; import java.net.URI; import java.nio.ByteBuffer; @@ -35,8 +36,10 @@ import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.client.util.InputStreamResponseListener; +import org.eclipse.jetty.client.util.OutputStreamContentProvider; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.FuturePromise; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -147,7 +150,9 @@ public class Usage client.start(); // Create an explicit connection, and use try-with-resources to manage it - try (Connection connection = client.getDestination("http", "localhost", 8080).newConnection().get(5, TimeUnit.SECONDS)) + FuturePromise futureConnection = new FuturePromise<>(); + client.getDestination("http", "localhost", 8080).newConnection(futureConnection); + try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS)) { Request request = client.newRequest("localhost", 8080); @@ -274,6 +279,33 @@ public class Usage Assert.assertEquals(200, response.getStatus()); } + @Test + public void testRequestOutputStream() throws Exception + { + HttpClient client = new HttpClient(); + client.start(); + + OutputStreamContentProvider content = new OutputStreamContentProvider(); + try (OutputStream output = content.getOutputStream()) + { + client.newRequest("localhost", 8080) + .content(content) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + Assert.assertEquals(200, result.getResponse().getStatus()); + } + }); + + output.write(new byte[1024]); + output.write(new byte[512]); + output.write(new byte[256]); + output.write(new byte[128]); + } + } + @Test public void testProxyUsage() throws Exception { diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini index 495b098c598..b927a2bddf6 100644 --- a/jetty-distribution/src/main/resources/start.ini +++ b/jetty-distribution/src/main/resources/start.ini @@ -200,4 +200,5 @@ etc/jetty-requestlog.xml # etc/jetty-stats.xml # etc/jetty-debug.xml # etc/jetty-ipaccess.xml +# etc/jetty-lowresources.xml #=========================================================== diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java index 7163989b0cd..bffa13ba415 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java @@ -57,11 +57,15 @@ import org.eclipse.jetty.util.thread.Scheduler; public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable { protected static final Logger LOG = Log.getLogger(SelectorManager.class); + /** + * The default connect timeout, in milliseconds + */ + public static final int DEFAULT_CONNECT_TIMEOUT = 15000; private final Executor executor; private final Scheduler scheduler; private final ManagedSelector[] _selectors; - private long _connectTimeout = 15000; + private long _connectTimeout = DEFAULT_CONNECT_TIMEOUT; private long _selectorIndex; protected SelectorManager(Executor executor, Scheduler scheduler) @@ -86,14 +90,24 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa return scheduler; } + /** + * Get the connect timeout + * + * @return the connect timeout (in milliseconds) + */ public long getConnectTimeout() { return _connectTimeout; } - public void setConnectTimeout(long connectTimeout) + /** + * Set the connect timeout (in milliseconds) + * + * @param milliseconds the number of milliseconds for the timeout + */ + public void setConnectTimeout(long milliseconds) { - _connectTimeout = connectTimeout; + _connectTimeout = milliseconds; } /** diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java index 84e6b87ae5f..b3c69074f88 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java @@ -51,9 +51,11 @@ import org.eclipse.jetty.util.log.Logger; * specific to a webapp). * * The context selected is based on classloaders. First - * we try looking in at the classloader that is associated - * with the current webapp context (if there is one). If - * not, we use the thread context classloader. + * we try looking at the thread context classloader if it is set, and walk its + * hierarchy, creating a context if none is found. If the thread context classloader + * is not set, then we use the classloader associated with the current Context. + * + * If there is no current context, or no classloader, we return null. * * Created: Fri Jun 27 09:26:40 2003 * @@ -79,9 +81,16 @@ public class ContextFactory implements ObjectFactory /** * Find or create a context which pertains to a classloader. * - * We use either the classloader for the current ContextHandler if - * we are handling a request, OR we use the thread context classloader - * if we are not processing a request. + * If the thread context classloader is set, we try to find an already-created naming context + * for it. If one does not exist, we walk its classloader hierarchy until one is found, or we + * run out of parent classloaders. In the latter case, we will create a new naming context associated + * with the original thread context classloader. + * + * If the thread context classloader is not set, we obtain the classloader from the current + * jetty Context, and look for an already-created naming context. + * + * If there is no current jetty Context, or it has no associated classloader, we + * return null. * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable) */ public Object getObjectInstance (Object obj, @@ -98,41 +107,89 @@ public class ContextFactory implements ObjectFactory return ctx; } - ClassLoader loader = null; - - loader = Thread.currentThread().getContextClassLoader(); - if (__log.isDebugEnabled() && loader != null) __log.debug("Using thread context classloader"); - - if (loader == null && ContextHandler.getCurrentContext() != null) + + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + ClassLoader loader = tccl; + //If the thread context classloader is set, then try its hierarchy to find a matching context + if (loader != null) { + if (__log.isDebugEnabled() && loader != null) __log.debug("Trying thread context classloader"); + while (ctx == null && loader != null) + { + ctx = getContextForClassLoader(loader); + if (ctx == null && loader != null) + loader = loader.getParent(); + } + + if (ctx == null) + { + ctx = newNamingContext(obj, tccl, env, name, nameCtx); + __contextMap.put (tccl, ctx); + if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+tccl); + } + return ctx; + } + + + //If trying thread context classloader hierarchy failed, try the + //classloader associated with the current context + if (ContextHandler.getCurrentContext() != null) + { + + if (__log.isDebugEnabled() && loader != null) __log.debug("Trying classloader of current org.eclipse.jetty.server.handler.ContextHandler"); loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader(); - if (__log.isDebugEnabled() && loader != null) __log.debug("Using classloader of current org.eclipse.jetty.server.handler.ContextHandler"); + ctx = (Context)__contextMap.get(loader); + + if (ctx == null && loader != null) + { + ctx = newNamingContext(obj, loader, env, name, nameCtx); + __contextMap.put (loader, ctx); + if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader); + } + + return ctx; } + return null; + } - //Get the context matching the classloader - ctx = (Context)__contextMap.get(loader); - //The map does not contain an entry for this classloader - if (ctx == null) - { - //Didn't find a context to match, make one - Reference ref = (Reference)obj; - StringRefAddr parserAddr = (StringRefAddr)ref.get("parser"); - String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent()); - NameParser parser = (NameParser)(parserClassName==null?null:loader.loadClass(parserClassName).newInstance()); + /** + * Create a new NamingContext. + * @param obj + * @param loader + * @param env + * @param name + * @param parentCtx + * @return + * @throws Exception + */ + public NamingContext newNamingContext(Object obj, ClassLoader loader, Hashtable env, Name name, Context parentCtx) + throws Exception + { + Reference ref = (Reference)obj; + StringRefAddr parserAddr = (StringRefAddr)ref.get("parser"); + String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent()); + NameParser parser = (NameParser)(parserClassName==null?null:loader.loadClass(parserClassName).newInstance()); - ctx = new NamingContext (env, - name.get(0), - (NamingContext)nameCtx, - parser); - if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader); - __contextMap.put (loader, ctx); - } - - return ctx; + return new NamingContext (env, + name.get(0), + (NamingContext)parentCtx, + parser); } + /** + * Find the naming Context for the given classloader + * @param loader + * @return + */ + public Context getContextForClassLoader(ClassLoader loader) + { + if (loader == null) + return null; + + return (Context)__contextMap.get(loader); + } /** diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java index cbdfd85103d..82e283d1463 100644 --- a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java +++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java @@ -41,10 +41,14 @@ import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.StringRefAddr; import javax.naming.spi.ObjectFactory; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; import org.eclipse.jetty.jndi.NamingContext; +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.junit.Ignore; import org.junit.Test; /** * @@ -68,70 +72,138 @@ public class TestJNDI } } - + + @Test - public void testIt() throws Exception + public void testThreadContextClassloaderAndCurrentContext() + throws Exception { - //set up some classloaders - Thread currentThread = Thread.currentThread(); - ClassLoader currentLoader = currentThread.getContextClassLoader(); - ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader); - ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader); + //create a jetty context, and start it so that its classloader it created + //and it is the current context + ClassLoader currentLoader = Thread.currentThread().getContextClassLoader(); + ContextHandler ch = new ContextHandler(); + URLClassLoader chLoader = new URLClassLoader(new URL[0], currentLoader); + ch.setClassLoader(chLoader); + + //Create another one + ContextHandler ch2 = new ContextHandler(); + URLClassLoader ch2Loader = new URLClassLoader(new URL[0], currentLoader); + ch2.setClassLoader(ch2Loader); try { - - //Uncomment to aid with debug - /* - javaRootURLContext.getRoot().addListener(new NamingContext.Listener() + ch.setContextPath("/ch"); + ch.addEventListener(new ServletContextListener() { - public void unbind(NamingContext ctx, Binding binding) + private Context comp; + private Object testObj = new Object(); + + public void contextInitialized(ServletContextEvent sce) { - System.err.println("java unbind "+binding+" from "+ctx.getName()); + try + { + InitialContext initCtx = new InitialContext(); + Context java = (Context)initCtx.lookup("java:"); + assertNotNull(java); + comp = (Context)initCtx.lookup("java:comp"); + assertNotNull(comp); + Context env = ((Context)comp).createSubcontext("env"); + assertNotNull(env); + env.bind("ch", testObj); + } + catch (Exception e) + { + throw new IllegalStateException(e); + } } - public Binding bind(NamingContext ctx, Binding binding) + public void contextDestroyed(ServletContextEvent sce) { - System.err.println("java bind "+binding+" to "+ctx.getName()); - return binding; + try + { + assertNotNull(comp); + assertEquals(testObj,comp.lookup("env/ch")); + comp.destroySubcontext("env"); + } + catch (Exception e) + { + throw new IllegalStateException(e); + } } }); + //Starting the context makes it current and creates a classloader for it + ch.start(); - localContextRoot.getRoot().addListener(new NamingContext.Listener() + + ch2.setContextPath("/ch2"); + ch2.addEventListener(new ServletContextListener() { - public void unbind(NamingContext ctx, Binding binding) - { - System.err.println("local unbind "+binding+" from "+ctx.getName()); - } + private Context comp; + private Object testObj = new Object(); - public Binding bind(NamingContext ctx, Binding binding) + public void contextInitialized(ServletContextEvent sce) { - System.err.println("local bind "+binding+" to "+ctx.getName()); - return binding; + try + { + InitialContext initCtx = new InitialContext(); + comp = (Context)initCtx.lookup("java:comp"); + assertNotNull(comp); + + //another context's bindings should not be visible + Context env = ((Context)comp).createSubcontext("env"); + try + { + env.lookup("ch"); + fail("java:comp/env visible from another context!"); + } + catch (NameNotFoundException e) + { + //expected + } + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + } + + public void contextDestroyed(ServletContextEvent sce) + { + try + { + assertNotNull(comp); + comp.destroySubcontext("env"); + } + catch (Exception e) + { + throw new IllegalStateException(e); + } } }); - */ + //make the new context the current one + ch2.start(); + } + finally + { + ch.stop(); + ch2.stop(); + Thread.currentThread().setContextClassLoader(currentLoader); + } + } + + @Test + public void testJavaNameParsing() throws Exception + { + Thread currentThread = Thread.currentThread(); + ClassLoader currentLoader = currentThread.getContextClassLoader(); + ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader); + + + //set the current thread's classloader + currentThread.setContextClassLoader(childLoader1); - - //set the current thread's classloader - currentThread.setContextClassLoader(childLoader1); - - InitialContext initCtxA = new InitialContext(); - initCtxA.bind ("blah", "123"); - assertEquals ("123", initCtxA.lookup("blah")); - - initCtxA.destroySubcontext("blah"); - try - { - initCtxA.lookup("blah"); - fail("context blah was not destroyed"); - } - catch (NameNotFoundException e) - { - //expected - } - - + try + { InitialContext initCtx = new InitialContext(); Context sub0 = (Context)initCtx.lookup("java:"); @@ -181,10 +253,74 @@ public class TestJNDI Context fee = ncontext.createSubcontext("fee"); fee.bind ("fi", "88"); - assertEquals("88", initCtxA.lookup("java:/fee/fi")); - assertEquals("88", initCtxA.lookup("java:/fee/fi/")); - assertTrue (initCtxA.lookup("java:/fee/") instanceof javax.naming.Context); + assertEquals("88", initCtx.lookup("java:/fee/fi")); + assertEquals("88", initCtx.lookup("java:/fee/fi/")); + assertTrue (initCtx.lookup("java:/fee/") instanceof javax.naming.Context); + } + finally + { + InitialContext ic = new InitialContext(); + Context java = (Context)ic.lookup("java:"); + java.destroySubcontext("fee"); + currentThread.setContextClassLoader(currentLoader); + } + } + + + @Test + public void testIt() throws Exception + { + //set up some classloaders + Thread currentThread = Thread.currentThread(); + ClassLoader currentLoader = currentThread.getContextClassLoader(); + ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader); + ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader); + + try + { + + //Uncomment to aid with debug + /* + javaRootURLContext.getRoot().addListener(new NamingContext.Listener() + { + public void unbind(NamingContext ctx, Binding binding) + { + System.err.println("java unbind "+binding+" from "+ctx.getName()); + } + + public Binding bind(NamingContext ctx, Binding binding) + { + System.err.println("java bind "+binding+" to "+ctx.getName()); + return binding; + } + }); + + localContextRoot.getRoot().addListener(new NamingContext.Listener() + { + public void unbind(NamingContext ctx, Binding binding) + { + System.err.println("local unbind "+binding+" from "+ctx.getName()); + } + + public Binding bind(NamingContext ctx, Binding binding) + { + System.err.println("local bind "+binding+" to "+ctx.getName()); + return binding; + } + }); + */ + + //Set up the tccl before doing any jndi operations + currentThread.setContextClassLoader(childLoader1); + InitialContext initCtx = new InitialContext(); + + //Test we can lookup the root java: naming tree + Context sub0 = (Context)initCtx.lookup("java:"); + assertNotNull(sub0); + + //Test that we cannot bind java:comp as it should + //already be bound try { Context sub1 = sub0.createSubcontext ("comp"); @@ -197,8 +333,10 @@ public class TestJNDI //check bindings at comp Context sub1 = (Context)initCtx.lookup("java:comp"); + assertNotNull(sub1); Context sub2 = sub1.createSubcontext ("env"); + assertNotNull(sub2); initCtx.bind ("java:comp/env/rubbish", "abc"); assertEquals ("abc", initCtx.lookup("java:comp/env/rubbish")); @@ -302,7 +440,6 @@ public class TestJNDI } - //test what happens when you close an initial context that was used initCtx.close(); } finally @@ -317,61 +454,7 @@ public class TestJNDI comp.destroySubcontext("env"); comp.unbind("crud"); comp.unbind("crud2"); - } - } - - - @Test - public void testParent() - throws Exception - { - //set up some classloaders - Thread currentThread = Thread.currentThread(); - ClassLoader parentLoader = currentThread.getContextClassLoader(); - ClassLoader childLoader1 = new URLClassLoader(new URL[0], parentLoader); - - try - { - //Test creating a comp for the parent loader does not leak to child - InitialContext initCtx = new InitialContext(); - Context comp = (Context)initCtx.lookup("java:comp"); - assertNotNull(comp); - - Context env = (Context)comp.createSubcontext("env"); - assertNotNull(env); - - env.bind("foo", "aaabbbcccddd"); - assertEquals("aaabbbcccddd", (String)initCtx.lookup("java:comp/env/foo")); - - //Change to child loader - currentThread.setContextClassLoader(childLoader1); - comp = (Context)initCtx.lookup("java:comp"); - - Context childEnv = (Context)comp.createSubcontext("env"); - assertNotSame(env, childEnv); - - childEnv.bind("foo", "eeefffggghhh"); - assertEquals("eeefffggghhh", (String)initCtx.lookup("java:comp/env/foo")); - - //Change back to parent - currentThread.setContextClassLoader(parentLoader); - assertEquals("aaabbbcccddd", (String)initCtx.lookup("java:comp/env/foo")); - - - } - finally - { - //make some effort to clean up - InitialContext ic = new InitialContext(); - currentThread.setContextClassLoader(parentLoader); - Context comp = (Context)ic.lookup("java:comp"); - comp.destroySubcontext("env"); - - currentThread.setContextClassLoader(childLoader1); - comp = (Context)ic.lookup("java:comp"); - comp.destroySubcontext("env"); - - + currentThread.setContextClassLoader(currentLoader); } } } diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java index 489f2f7b6bb..7fd65502209 100644 --- a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java +++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java @@ -224,6 +224,15 @@ public class TestLocalJNDI assertEquals("333", (String)o); assertEquals("333", ic.lookup(name)); ic.destroySubcontext("a"); + try + { + ic.lookup("a"); + fail("context a was not destroyed"); + } + catch (NameNotFoundException e) + { + //expected + } name = parser.parse(""); name.add("x"); diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java index d7ce310596e..e4bd0f851a1 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java @@ -25,7 +25,7 @@ import java.util.List; import org.codehaus.plexus.util.xml.Xpp3Dom; -import edu.emory.mathcs.backport.java.util.Arrays; +import java.util.Arrays; /** * OverlayConfig diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml index 9cf70d9efe5..8ef347dac22 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml @@ -3,7 +3,6 @@ org.eclipse.jetty.osgi jetty-osgi-project 9.0.0-SNAPSHOT - ../pom.xml 4.0.0 jetty-osgi-boot-jsp diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java index e9e9abddf3c..2521ed186dd 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java +++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java @@ -25,9 +25,12 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.StringTokenizer; +import java.util.regex.Pattern; -import org.eclipse.jetty.osgi.boot.OSGiAppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; import org.osgi.framework.Bundle; @@ -62,10 +65,10 @@ public class PluggableWebAppRegistrationCustomizerImpl implements WebappRegistra * @param provider * @return */ - private static Collection getTldBundles(OSGiAppProvider provider) + private static Collection getTldBundles(DeploymentManager deploymentManager) { String sysprop = System.getProperty(SYS_PROP_TLD_BUNDLES); - String att = (String) provider.getTldBundles(); + String att = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN); if (sysprop == null && att == null) { return Collections.emptySet(); } if (att == null) { @@ -89,9 +92,8 @@ public class PluggableWebAppRegistrationCustomizerImpl implements WebappRegistra * @return The location of the jars that contain tld files. Jasper will * discover them. */ - public URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper locatorHelper) throws Exception + public URL[] getJarsWithTlds(DeploymentManager deploymentManager, BundleFileLocatorHelper locatorHelper) throws Exception { - List urls = new ArrayList(); // naive way of finding those bundles. // lots of assumptions: for example we assume a single version of each // bundle that would contain tld files. @@ -102,13 +104,24 @@ public class PluggableWebAppRegistrationCustomizerImpl implements WebappRegistra // and mirroring those in the MANIFEST.MF Bundle[] bundles = FrameworkUtil.getBundle(PluggableWebAppRegistrationCustomizerImpl.class).getBundleContext().getBundles(); - Collection tldbundles = getTldBundles(provider); + HashSet urls = new HashSet(); + String tmp = System.getProperty(SYS_PROP_TLD_BUNDLES); //comma separated exact names + List sysNames = new ArrayList(); + if (tmp != null) + { + StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false); + while (tokenizer.hasMoreTokens()) + sysNames.add(tokenizer.nextToken()); + } + tmp = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN); //bundle name patterns + Pattern pattern = (tmp==null? null : Pattern.compile(tmp)); for (Bundle bundle : bundles) { - if (tldbundles.contains(bundle.getSymbolicName())) - { + if (sysNames.contains(bundle.getSymbolicName())) + registerTldBundle(locatorHelper, bundle, urls); + + if (pattern != null && pattern.matcher(bundle.getSymbolicName()).matches()) registerTldBundle(locatorHelper, bundle, urls); - } } return urls.toArray(new URL[urls.size()]); @@ -140,7 +153,7 @@ public class PluggableWebAppRegistrationCustomizerImpl implements WebappRegistra * @param urls * @throws Exception */ - private void registerTldBundle(BundleFileLocatorHelper locatorHelper, Bundle bundle, List urls) throws Exception + private void registerTldBundle(BundleFileLocatorHelper locatorHelper, Bundle bundle, Set urls) throws Exception { File jasperLocation = locatorHelper.getBundleInstallLocation(bundle); if (jasperLocation.isDirectory()) diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java index 2c337f25840..7b744b99cd3 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java +++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java @@ -31,8 +31,8 @@ import javax.servlet.jsp.JspFactory; import org.apache.jasper.Constants; import org.apache.jasper.compiler.Localizer; import org.apache.jasper.xmlparser.ParserUtils; +import org.eclipse.jetty.deploy.DeploymentManager; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; -import org.eclipse.jetty.osgi.boot.OSGiAppProvider; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; import org.eclipse.jetty.util.log.Log; @@ -55,6 +55,7 @@ import org.xml.sax.SAXException; public class WebappRegistrationCustomizerImpl implements WebappRegistrationCustomizer { private static final Logger LOG = Log.getLogger(WebappRegistrationCustomizerImpl.class); + /** * Default name of a class that belongs to the jstl bundle. From that class @@ -90,12 +91,10 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto { // sanity check: Class cl = getClass().getClassLoader().loadClass("org.apache.jasper.servlet.JspServlet"); - // System.err.println("found the jsp servlet: " + cl.getName()); } catch (Exception e) { - System.err.println("Unable to locate the JspServlet: jsp support unavailable."); - e.printStackTrace(); + LOG.warn("Unable to locate the JspServlet: jsp support unavailable.", e); return; } try @@ -115,7 +114,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto } catch (Exception e) { - LOG.warn("Unable to set the JspFactory: jsp support incomplete.",e); + LOG.warn("Unable to set the JspFactory: jsp support incomplete.", e); } } @@ -137,7 +136,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto * @return array of URLs * @throws Exception */ - public URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper locatorHelper) throws Exception + public URL[] getJarsWithTlds(DeploymentManager deployer, BundleFileLocatorHelper locatorHelper) throws Exception { ArrayList urls = new ArrayList(); @@ -216,7 +215,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto } catch (Exception e) { - LOG.warn(e); + e.printStackTrace(); } } diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java index 241b2e148e8..9741d21c7dd 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java +++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java @@ -17,7 +17,9 @@ // package org.eclipse.jetty.osgi.boot.jsp; -import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleDeployerHelper; + +import org.eclipse.jetty.osgi.boot.BundleWebAppProvider; +import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer; import org.eclipse.jetty.osgi.boot.jasper.PluggableWebAppRegistrationCustomizerImpl; import org.eclipse.jetty.osgi.boot.jasper.WebappRegistrationCustomizerImpl; import org.osgi.framework.BundleActivator; @@ -29,7 +31,7 @@ import org.osgi.framework.BundleContext; * called back by the host bundle. *

* It must be placed in the org.eclipse.jetty.osgi.boot.jsp package: this is - * because org.eclipse.jetty.osgi.boot.jsp is the sympbolic-name of this + * because org.eclipse.jetty.osgi.boot.jsp is the symbolic-name of this * fragment. From that name, the PackageadminTracker will call this class. IN a * different package it won't be called. *

@@ -42,8 +44,11 @@ public class FragmentActivator implements BundleActivator public void start(BundleContext context) throws Exception { System.setProperty("org.apache.jasper.compiler.disablejsr199", Boolean.TRUE.toString()); - WebBundleDeployerHelper.JSP_REGISTRATION_HELPERS.add(new WebappRegistrationCustomizerImpl()); - WebBundleDeployerHelper.JSP_REGISTRATION_HELPERS.add(new PluggableWebAppRegistrationCustomizerImpl()); + WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS.add(new WebappRegistrationCustomizerImpl()); + WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS.add(new PluggableWebAppRegistrationCustomizerImpl()); + //Put in the support for the tag libs + addTagLibSupport(); + } /** @@ -53,4 +58,12 @@ public class FragmentActivator implements BundleActivator { } + + public void addTagLibSupport () + { + String[] defaultConfigurations = new String[BundleWebAppProvider.getDefaultConfigurations().length+1]; + System.arraycopy(BundleWebAppProvider.getDefaultConfigurations(), 0, defaultConfigurations, 0, BundleWebAppProvider.getDefaultConfigurations().length); + defaultConfigurations[defaultConfigurations.length-1] = "org.eclipse.jetty.osgi.boot.jsp.TagLibOSGiConfiguration"; + BundleWebAppProvider.setDefaultConfigurations(defaultConfigurations); + } } diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java index b8e91701204..407b85628b2 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java +++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java @@ -25,6 +25,7 @@ import java.util.Enumeration; import java.util.LinkedHashSet; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -114,7 +115,7 @@ public class TagLibOSGiConfiguration extends TagLibConfiguration { atLeastOneTldFound = true; URL oriUrl = en.nextElement(); - URL url = DefaultFileLocatorHelper.getLocalURL(oriUrl); + URL url = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(oriUrl); Resource tldResource; try { diff --git a/jetty-osgi/jetty-osgi-boot-logback/pom.xml b/jetty-osgi/jetty-osgi-boot-logback/pom.xml deleted file mode 100644 index 3cdbb91d698..00000000000 --- a/jetty-osgi/jetty-osgi-boot-logback/pom.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - org.eclipse.jetty.osgi - jetty-osgi-project - 7.6.10-SNAPSHOT - ../pom.xml - - 4.0.0 - jetty-osgi-boot-logback - Jetty :: OSGi :: Boot Logback - Jetty OSGi Boot Logback bundle - - ${project.groupId}.boot.logback - - - - org.eclipse.jetty.osgi - jetty-osgi-boot - ${project.version} - provided - - - org.eclipse.osgi - org.eclipse.osgi - - - org.eclipse.jetty - jetty-webapp - - - org.eclipse.osgi - org.eclipse.osgi.services - - - org.slf4j - slf4j-api - - - org.slf4j - jcl-over-slf4j - - - org.slf4j - log4j-over-slf4j - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - artifact-jar - - jar - - - - test-jar - - test-jar - - - - - - target/classes/META-INF/MANIFEST.MF - - - - - org.apache.felix - maven-bundle-plugin - true - - - bundle-manifest - process-classes - - manifest - - - - - - org.eclipse.jetty.osgi.boot.logback;singleton:=true - Jetty-OSGi-Logback Integration - org.eclipse.jetty.osgi.boot - -ch.qos.logback.access.jetty;version="[0.9,1.1)";resolution:=optional, -ch.qos.logback.access.jetty.v7;version="[0.9,1.1)";resolution:=optional, -ch.qos.logback.*;version="[0.9,1.1)", -org.osgi.framework.*, -org.slf4j.*, -*;resolution:=optional - - -!org.eclipse.jetty.osgi.boot.logback.internal.*, -org.eclipse.jetty.osgi.boot.logback.*;version="${parsedVersion.osgiVersion}" - - <_nouses>true - - - - - org.codehaus.mojo - findbugs-maven-plugin - - org.eclipse.jetty.osgi.boot.logback.* - - - - - - - diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-deployer.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-deployer.xml index f3faa68b832..b03a648e3d6 100644 --- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-deployer.xml +++ b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-deployer.xml @@ -16,23 +16,6 @@ org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern .*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$ - - - - - - 0 - /contexts - - - - - - - - diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-nested.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-nested.xml deleted file mode 100644 index 48a0f1d495d..00000000000 --- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-nested.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - false - true - x-forwarded_for - sslclientcipher - sslsessionid - - - - - - - - - - - - diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml index 7df9ee2d003..1c998353866 100644 --- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml +++ b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml @@ -57,6 +57,20 @@ + + + + org.eclipse.jetty.webapp.FragmentConfiguration + + + org.eclipse.jetty.plus.webapp.EnvConfiguration + org.eclipse.jetty.plus.webapp.PlusConfiguration + org.eclipse.jetty.annotations.AnnotationConfiguration + + + + + java.naming.factory.initial diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 4b231a9ec4e..3f2bd766a33 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -3,7 +3,6 @@ org.eclipse.jetty.osgi jetty-osgi-project 9.0.0-SNAPSHOT - ../pom.xml 4.0.0 jetty-osgi-boot @@ -29,10 +28,6 @@ org.eclipse.jetty jetty-jmx - org.eclipse.osgi org.eclipse.osgi @@ -104,8 +99,9 @@ + org.eclipse.jetty.osgi.boot;singleton:=true org.eclipse.jetty.osgi.boot.JettyBootstrapActivator - org.eclipse.jetty.osgi.boot;version="${parsedVersion.osgiVersion}",org.eclipse.jetty.osgi.boot.utils,org.eclipse.jetty.osgi.nested;version="${parsedVersion.osgiVersion}" + org.eclipse.jetty.*;version="[9.0,10.0)" javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, javax.mail.internet;version="1.4.0";resolution:=optional, @@ -129,7 +125,7 @@ org.xml.sax.helpers, * - org.eclipse.jetty.*;version="9.0.0" + <_nouses>true diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java index 98ac22b6eb4..b4ae909819d 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java @@ -18,12 +18,16 @@ package org.eclipse.jetty.osgi.annotations; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler; import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler; import org.eclipse.jetty.annotations.ClassNameResolver; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.DiscoveredAnnotation; import org.eclipse.jetty.webapp.WebAppContext; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java new file mode 100644 index 00000000000..6f07480ab6b --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java @@ -0,0 +1,360 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.io.File; +import java.net.URL; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.osgi.boot.utils.OSGiClassLoader; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.JarResource; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.xml.XmlConfiguration; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; + + + + +/** + * AbstractContextProvider + * + * + */ +public abstract class AbstractContextProvider extends AbstractLifeCycle implements AppProvider +{ + private static final Logger LOG = Log.getLogger(AbstractContextProvider.class); + + private DeploymentManager _deploymentManager; + + + private ServerInstanceWrapper _serverWrapper; + + + + + /* ------------------------------------------------------------ */ + /** + * BundleApp + * + * + */ + public class OSGiApp extends AbstractOSGiApp + { + private String _contextFile; + private ContextHandler _contextHandler; + private boolean _configured = false; + + public OSGiApp(DeploymentManager manager, AppProvider provider, String originId, Bundle bundle, String contextFile) + { + super(manager, provider, bundle, originId); + _contextFile = contextFile; + } + + public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String contextFile, String originId) + { + super(manager, provider, bundle, properties, originId); + _contextFile = contextFile; + } + + public String getContextFile () + { + return _contextFile; + } + + public void setHandler(ContextHandler h) + { + _contextHandler = h; + } + + public ContextHandler createContextHandler() + throws Exception + { + configureContextHandler(); + return _contextHandler; + } + + public void configureContextHandler() + throws Exception + { + if (_configured) + return; + + _configured = true; + + //Override for bundle root may have been set + String bundleOverrideLocation = (String)_properties.get(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE); + if (bundleOverrideLocation == null) + bundleOverrideLocation = (String)_properties.get(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE); + + //Location on filesystem of bundle or the bundle override location + File bundleLocation = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(_bundle); + File root = (bundleOverrideLocation==null?bundleLocation:new File(bundleOverrideLocation)); + Resource rootResource = Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(root.toURI().toURL())); + + //try and make sure the rootResource is useable - if its a jar then make it a jar file url + if (rootResource.exists()&& !rootResource.isDirectory() && !rootResource.toString().startsWith("jar:")) + { + Resource jarResource = JarResource.newJarResource(rootResource); + if (jarResource.exists() && jarResource.isDirectory()) + rootResource = jarResource; + } + + //Set the base resource of the ContextHandler, if not already set, can also be overridden by the context xml file + if (_contextHandler != null && _contextHandler.getBaseResource() == null) + { + _contextHandler.setBaseResource(rootResource); + } + + //Use a classloader that knows about the common jetty parent loader, and also the bundle + OSGiClassLoader classLoader = new OSGiClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps(), _bundle); + + //if there is a context file, find it and apply it + if (_contextFile == null && _contextHandler == null) + throw new IllegalStateException("No context file or ContextHandler"); + + if (_contextFile != null) + { + //apply the contextFile, creating the ContextHandler, the DeploymentManager will register it in the ContextHandlerCollection + Resource res = null; + + //try to find the context file in the filesystem + if (_contextFile.startsWith("/")) + res = getFileAsResource(_contextFile); + + //try to find it relative to jetty home + if (res == null) + { + //See if the specific server we are related to has jetty.home set + String jettyHome = (String)getServerInstanceWrapper().getServer().getAttribute(OSGiServerConstants.JETTY_HOME); + if (jettyHome != null) + res = getFileAsResource(jettyHome, _contextFile); + + //try to see if a SystemProperty for jetty.home is set + if (res == null) + { + jettyHome = System.getProperty(OSGiServerConstants.JETTY_HOME); + + if (jettyHome != null) + { + if (jettyHome.startsWith("\"") || jettyHome.startsWith("'")) + jettyHome = jettyHome.substring(1); + if (jettyHome.endsWith("\"") || (jettyHome.endsWith("'"))) + jettyHome = jettyHome.substring(0,jettyHome.length()-1); + + res = getFileAsResource(jettyHome, _contextFile); + if (LOG.isDebugEnabled()) LOG.debug("jetty home context file:"+res); + } + } + } + + //try to find it relative to an override location that has been specified + if (res == null) + { + if (bundleOverrideLocation != null) + { + res = getFileAsResource(Resource.newResource(bundleOverrideLocation).getFile(), _contextFile); + if (LOG.isDebugEnabled()) LOG.debug("Bundle override location context file:"+res); + } + } + + //try to find it relative to the bundle in which it is being deployed + if (res == null) + { + if (_contextFile.startsWith("./")) + _contextFile = _contextFile.substring(1); + + if (!_contextFile.startsWith("/")) + _contextFile = "/" + _contextFile; + + URL contextURL = _bundle.getEntry(_contextFile); + if (contextURL != null) + res = Resource.newResource(contextURL); + } + + //apply the context xml file, either to an existing ContextHandler, or letting the + //it create the ContextHandler as necessary + if (res != null) + { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + LOG.debug("Context classloader = " + cl); + try + { + Thread.currentThread().setContextClassLoader(classLoader); + + XmlConfiguration xmlConfiguration = new XmlConfiguration(res.getInputStream()); + HashMap properties = new HashMap(); + //put the server instance in + properties.put("Server", getServerInstanceWrapper().getServer()); + //put in the location of the bundle root + properties.put("bundle.root", rootResource.toString()); + + // insert the bundle's location as a property. + xmlConfiguration.getProperties().putAll(properties); + + if (_contextHandler == null) + _contextHandler = (ContextHandler) xmlConfiguration.configure(); + else + xmlConfiguration.configure(_contextHandler); + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + } + + //Set up the class loader we created + _contextHandler.setClassLoader(classLoader); + + + //If a bundle/service property specifies context path, let it override the context xml + String contextPath = (String)_properties.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + if (contextPath == null) + contextPath = (String)_properties.get(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); + if (contextPath != null) + _contextHandler.setContextPath(contextPath); + + //osgi Enterprise Spec r4 p.427 + _contextHandler.setAttribute(OSGiWebappConstants.OSGI_BUNDLECONTEXT, _bundle.getBundleContext()); + + //make sure we protect also the osgi dirs specified by OSGi Enterprise spec + String[] targets = _contextHandler.getProtectedTargets(); + int length = (targets==null?0:targets.length); + + String[] updatedTargets = null; + if (targets != null) + { + updatedTargets = new String[length+OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; + System.arraycopy(targets, 0, updatedTargets, 0, length); + + } + else + updatedTargets = new String[OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; + System.arraycopy(OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS, 0, updatedTargets, length, OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length); + _contextHandler.setProtectedTargets(updatedTargets); + + } + + + private Resource getFileAsResource (String dir, String file) + { + Resource r = null; + try + { + File asFile = new File (dir, file); + if (asFile.exists()) + r = Resource.newResource(asFile); + } + catch (Exception e) + { + r = null; + } + return r; + } + + private Resource getFileAsResource (String file) + { + Resource r = null; + try + { + File asFile = new File (file); + if (asFile.exists()) + r = Resource.newResource(asFile); + } + catch (Exception e) + { + r = null; + } + return r; + } + + private Resource getFileAsResource (File dir, String file) + { + Resource r = null; + try + { + File asFile = new File (dir, file); + if (asFile.exists()) + r = Resource.newResource(asFile); + } + catch (Exception e) + { + r = null; + } + return r; + } + } + + /* ------------------------------------------------------------ */ + public AbstractContextProvider(ServerInstanceWrapper wrapper) + { + _serverWrapper = wrapper; + } + + + /* ------------------------------------------------------------ */ + public ServerInstanceWrapper getServerInstanceWrapper() + { + return _serverWrapper; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.deploy.AppProvider#createContextHandler(org.eclipse.jetty.deploy.App) + */ + public ContextHandler createContextHandler(App app) throws Exception + { + if (app == null) + return null; + if (!(app instanceof OSGiApp)) + throw new IllegalStateException(app+" is not a BundleApp"); + + //Create a ContextHandler suitable to deploy in OSGi + ContextHandler h = ((OSGiApp)app).createContextHandler(); + return h; + } + + /* ------------------------------------------------------------ */ + public void setDeploymentManager(DeploymentManager deploymentManager) + { + _deploymentManager = deploymentManager; + } + + /* ------------------------------------------------------------ */ + public DeploymentManager getDeploymentManager() + { + return _deploymentManager; + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java new file mode 100644 index 00000000000..be09d2cfd69 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java @@ -0,0 +1,120 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; + + + +/** + * AbstractBundleApp + * + * + */ +public abstract class AbstractOSGiApp extends App +{ + protected Bundle _bundle; + protected Dictionary _properties; + protected ServiceRegistration _registration; + + /* ------------------------------------------------------------ */ + public AbstractOSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, String originId) + { + super(manager, provider, originId); + _properties = bundle.getHeaders(); + _bundle = bundle; + } + /* ------------------------------------------------------------ */ + public AbstractOSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String originId) + { + super(manager, provider, originId); + _properties = properties; + _bundle = bundle; + } + + /* ------------------------------------------------------------ */ + public String getBundleSymbolicName() + { + return _bundle.getSymbolicName(); + } + + /* ------------------------------------------------------------ */ + public String getBundleVersionAsString() + { + if (_bundle.getVersion() == null) + return null; + return _bundle.getVersion().toString(); + } + + /* ------------------------------------------------------------ */ + public Bundle getBundle() + { + return _bundle; + } + + /* ------------------------------------------------------------ */ + public void setRegistration (ServiceRegistration registration) + { + _registration = registration; + } + + /* ------------------------------------------------------------ */ + public ServiceRegistration getRegistration () + { + return _registration; + } + + + /* ------------------------------------------------------------ */ + public void registerAsOSGiService() throws Exception + { + if (_registration == null) + { + Dictionary properties = new Hashtable(); + properties.put(OSGiWebappConstants.WATERMARK, OSGiWebappConstants.WATERMARK); + if (getBundleSymbolicName() != null) + properties.put(OSGiWebappConstants.OSGI_WEB_SYMBOLICNAME, getBundleSymbolicName()); + if (getBundleVersionAsString() != null) + properties.put(OSGiWebappConstants.OSGI_WEB_VERSION, getBundleVersionAsString()); + properties.put(OSGiWebappConstants.OSGI_WEB_CONTEXTPATH, getContextPath()); + ServiceRegistration rego = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(ContextHandler.class.getName(), getContextHandler(), properties); + setRegistration(rego); + } + } + + /* ------------------------------------------------------------ */ + protected void deregisterAsOSGiService() throws Exception + { + if (_registration == null) + return; + + _registration.unregister(); + _registration = null; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java new file mode 100644 index 00000000000..73da20f41c1 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java @@ -0,0 +1,552 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; +import org.eclipse.jetty.osgi.boot.internal.webapp.OSGiWebappClassLoader; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.xml.XmlConfiguration; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.packageadmin.PackageAdmin; + + + + +/** + * AbstractWebAppProvider + * + * + */ +public abstract class AbstractWebAppProvider extends AbstractLifeCycle implements AppProvider +{ + private static final Logger LOG = Log.getLogger(AbstractWebAppProvider.class); + + public static String __defaultConfigurations[] = { + "org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration", + "org.eclipse.jetty.webapp.WebXmlConfiguration", + "org.eclipse.jetty.osgi.boot.OSGiMetaInfConfiguration", + "org.eclipse.jetty.webapp.FragmentConfiguration", + "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//, + //"org.eclipse.jetty.osgi.boot.jsp.TagLibOSGiConfiguration" + }; + + public static void setDefaultConfigurations (String[] defaultConfigs) + { + __defaultConfigurations = defaultConfigs; + } + + public static String[] getDefaultConfigurations () + { + return __defaultConfigurations; + } + + + private boolean _parentLoaderPriority; + + private String _defaultsDescriptor; + + private boolean _extractWars = true; //See WebAppContext.extractWars + + private String _tldBundles; + + private DeploymentManager _deploymentManager; + + private String[] _configurationClasses; + + private ServerInstanceWrapper _serverWrapper; + + /* ------------------------------------------------------------ */ + /** + * OSGiApp + * + * + */ + public class OSGiApp extends AbstractOSGiApp + { + private String _contextPath; + private String _webAppPath; + private WebAppContext _webApp; + + public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, String originId) + { + super(manager, provider, bundle, originId); + } + + public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String originId) + { + super(manager, provider, bundle, properties, originId); + } + + public void setWebAppContext (WebAppContext webApp) + { + _webApp = webApp; + } + + public String getContextPath() + { + return _contextPath; + } + + public void setContextPath(String contextPath) + { + this._contextPath = contextPath; + } + + public String getBundlePath() + { + return _webAppPath; + } + + public void setWebAppPath(String path) + { + this._webAppPath = path; + } + + + public ContextHandler createContextHandler() + throws Exception + { + if (_webApp != null) + { + configureWebApp(); + return _webApp; + } + + createWebApp(); + return _webApp; + } + + + + protected void createWebApp () + throws Exception + { + _webApp = newWebApp(); + configureWebApp(); + } + + protected WebAppContext newWebApp () + { + WebAppContext webApp = new WebAppContext(); + webApp.setAttribute(OSGiWebappConstants.WATERMARK, OSGiWebappConstants.WATERMARK); + + //make sure we protect also the osgi dirs specified by OSGi Enterprise spec + String[] targets = webApp.getProtectedTargets(); + String[] updatedTargets = null; + if (targets != null) + { + updatedTargets = new String[targets.length+OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; + System.arraycopy(targets, 0, updatedTargets, 0, targets.length); + } + else + updatedTargets = new String[OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; + System.arraycopy(OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS, 0, updatedTargets, targets.length, OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length); + webApp.setProtectedTargets(updatedTargets); + + return webApp; + } + + + public void configureWebApp() + throws Exception + { + //TODO turn this around and let any context.xml file get applied first, and have the properties override + _webApp.setContextPath(_contextPath); + + //osgi Enterprise Spec r4 p.427 + _webApp.setAttribute(OSGiWebappConstants.OSGI_BUNDLECONTEXT, _bundle.getBundleContext()); + + String overrideBundleInstallLocation = (String)_properties.get(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE); + File bundleInstallLocation = + (overrideBundleInstallLocation == null + ? BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(_bundle) + : new File(overrideBundleInstallLocation)); + URL url = null; + + //if the path wasn't set or it was ., then it is the root of the bundle's installed location + if (_webAppPath == null || _webAppPath.length() == 0 || ".".equals(_webAppPath)) + { + url = bundleInstallLocation.toURI().toURL(); + } + else + { + //Get the location of the root of the webapp inside the installed bundle + if (_webAppPath.startsWith("/") || _webAppPath.startsWith("file:")) + { + url = new File(_webAppPath).toURI().toURL(); + } + else if (bundleInstallLocation != null && bundleInstallLocation.isDirectory()) + { + url = new File(bundleInstallLocation, _webAppPath).toURI().toURL(); + } + else if (bundleInstallLocation != null) + { + Enumeration urls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(_bundle, _webAppPath); + if (urls != null && urls.hasMoreElements()) + url = urls.nextElement(); + } + } + + if (url == null) + { + throw new IllegalArgumentException("Unable to locate " + _webAppPath + + " in " + + (bundleInstallLocation != null ? bundleInstallLocation.getAbsolutePath() : "unlocated bundle '" + _bundle.getSymbolicName()+ "'")); + } + + // converts bundleentry: protocol if necessary + _webApp.setWar(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(url).toString()); + + // Set up what has been configured on the provider + _webApp.setParentLoaderPriority(isParentLoaderPriority()); + _webApp.setExtractWAR(isExtract()); + if (getConfigurationClasses() != null) + _webApp.setConfigurationClasses(getConfigurationClasses()); + else + _webApp.setConfigurationClasses(__defaultConfigurations); + + if (getDefaultsDescriptor() != null) + _webApp.setDefaultsDescriptor(getDefaultsDescriptor()); + + //Set up configuration from manifest headers + //extra classpath + String tmp = (String)_properties.get(OSGiWebappConstants.JETTY_EXTRA_CLASSPATH); + if (tmp != null) + _webApp.setExtraClasspath(tmp); + + //web.xml + tmp = (String)_properties.get(OSGiWebappConstants.JETTY_WEB_XML_PATH); + if (tmp != null && tmp.trim().length() != 0) + { + File webXml = getFile (tmp, bundleInstallLocation); + if (webXml != null && webXml.exists()) + _webApp.setDescriptor(webXml.getAbsolutePath()); + } + + //webdefault.xml + tmp = (String)_properties.get(OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH); + if (tmp != null) + { + File defaultWebXml = getFile (tmp, bundleInstallLocation); + if (defaultWebXml != null && defaultWebXml.exists()) + _webApp.setDefaultsDescriptor(defaultWebXml.getAbsolutePath()); + } + + //Handle Require-TldBundle + //This is a comma separated list of names of bundles that contain tlds that this webapp uses. + //We add them to the webapp classloader. + String requireTldBundles = (String)_properties.get(OSGiWebappConstants.REQUIRE_TLD_BUNDLE); + String pathsToTldBundles = getPathsToRequiredBundles(requireTldBundles); + + + // make sure we provide access to all the jetty bundles by going + // through this bundle. + OSGiWebappClassLoader webAppLoader = new OSGiWebappClassLoader(_serverWrapper.getParentClassLoaderForWebapps(), _webApp, _bundle); + + if (pathsToTldBundles != null) + webAppLoader.addClassPath(pathsToTldBundles); + _webApp.setClassLoader(webAppLoader); + + + // apply any META-INF/context.xml file that is found to configure + // the webapp first + applyMetaInfContextXml(); + + // pass the value of the require tld bundle so that the TagLibOSGiConfiguration + // can pick it up. + _webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles); + + //Set up some attributes + // rfc66 + _webApp.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT, _bundle.getBundleContext()); + + // spring-dm-1.2.1 looks for the BundleContext as a different attribute. + // not a spec... but if we want to support + // org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext + // then we need to do this to: + _webApp.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), _bundle.getBundleContext()); + + // also pass the bundle directly. sometimes a bundle does not have a + // bundlecontext. + // it is still useful to have access to the Bundle from the servlet + // context. + _webApp.setAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE, _bundle); + } + + protected String getPathsToRequiredBundles (String requireTldBundles) + throws Exception + { + if (requireTldBundles == null) return null; + + ServiceReference ref = _bundle.getBundleContext().getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName()); + PackageAdmin packageAdmin = (ref == null) ? null : (PackageAdmin)_bundle.getBundleContext().getService(ref); + if (packageAdmin == null) + throw new IllegalStateException("Unable to get PackageAdmin reference to locate required Tld bundles"); + + StringBuilder paths = new StringBuilder(); + String[] symbNames = requireTldBundles.split(", "); + + for (String symbName : symbNames) + { + Bundle[] bs = packageAdmin.getBundles(symbName, null); + if (bs == null || bs.length == 0) + { + throw new IllegalArgumentException("Unable to locate the bundle '" + symbName + + "' specified by " + + OSGiWebappConstants.REQUIRE_TLD_BUNDLE + + " in manifest of " + + (_bundle == null ? "unknown" : _bundle.getSymbolicName())); + } + + File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bs[0]); + if (paths.length() > 0) paths.append(", "); + paths.append(f.toURI().toURL().toString()); + LOG.debug("getPathsToRequiredBundles: bundle path=" + bs[0].getLocation() + " uri=" + f.toURI()); + } + + return paths.toString(); + } + + + protected void applyMetaInfContextXml() + throws Exception + { + if (_bundle == null) return; + if (_webApp == null) return; + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + LOG.debug("Context classloader = " + cl); + try + { + + Thread.currentThread().setContextClassLoader(_webApp.getClassLoader()); + + //TODO replace this with getting the InputStream so we don't cache in URL + // find if there is a META-INF/context.xml file + URL contextXmlUrl = _bundle.getEntry("/META-INF/jetty-webapp-context.xml"); + if (contextXmlUrl == null) return; + + // Apply it just as the standard jetty ContextProvider would do + LOG.info("Applying " + contextXmlUrl + " to " + _webApp); + + XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXmlUrl); + HashMap properties = new HashMap(); + properties.put("Server", getDeploymentManager().getServer()); + xmlConfiguration.getProperties().putAll(properties); + xmlConfiguration.configure(_webApp); + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + + private File getFile (String file, File bundleInstall) + { + if (file == null) + return null; + + if (file.startsWith("/") || file.startsWith("file:/")) + return new File(file); + else + return new File(bundleInstall, file); + } + } + + /* ------------------------------------------------------------ */ + public AbstractWebAppProvider (ServerInstanceWrapper wrapper) + { + _serverWrapper = wrapper; + } + + + + /* ------------------------------------------------------------ */ + /** + * Get the parentLoaderPriority. + * + * @return the parentLoaderPriority + */ + public boolean isParentLoaderPriority() + { + return _parentLoaderPriority; + } + + /* ------------------------------------------------------------ */ + /** + * Set the parentLoaderPriority. + * + * @param parentLoaderPriority the parentLoaderPriority to set + */ + public void setParentLoaderPriority(boolean parentLoaderPriority) + { + _parentLoaderPriority = parentLoaderPriority; + } + + /* ------------------------------------------------------------ */ + /** + * Get the defaultsDescriptor. + * + * @return the defaultsDescriptor + */ + public String getDefaultsDescriptor() + { + return _defaultsDescriptor; + } + + /* ------------------------------------------------------------ */ + /** + * Set the defaultsDescriptor. + * + * @param defaultsDescriptor the defaultsDescriptor to set + */ + public void setDefaultsDescriptor(String defaultsDescriptor) + { + _defaultsDescriptor = defaultsDescriptor; + } + + + /* ------------------------------------------------------------ */ + public boolean isExtract() + { + return _extractWars; + } + + + /* ------------------------------------------------------------ */ + public void setExtract(boolean extract) + { + _extractWars = extract; + } + + + /* ------------------------------------------------------------ */ + /** + * @param tldBundles Comma separated list of bundles that contain tld jars + * that should be setup on the jetty instances created here. + */ + public void setTldBundles(String tldBundles) + { + _tldBundles = tldBundles; + } + + + /* ------------------------------------------------------------ */ + /** + * @return The list of bundles that contain tld jars that should be setup on + * the jetty instances created here. + */ + public String getTldBundles() + { + return _tldBundles; + } + + /* ------------------------------------------------------------ */ + /** + * @param configurations The configuration class names. + */ + public void setConfigurationClasses(String[] configurations) + { + _configurationClasses = configurations == null ? null : (String[]) configurations.clone(); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public String[] getConfigurationClasses() + { + return _configurationClasses; + } + + /* ------------------------------------------------------------ */ + public void setServerInstanceWrapper(ServerInstanceWrapper wrapper) + { + _serverWrapper = wrapper; + } + + public ServerInstanceWrapper getServerInstanceWrapper() + { + return _serverWrapper; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public DeploymentManager getDeploymentManager() + { + return _deploymentManager; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.deploy.AppProvider#setDeploymentManager(org.eclipse.jetty.deploy.DeploymentManager) + */ + public void setDeploymentManager(DeploymentManager deploymentManager) + { + _deploymentManager = deploymentManager; + } + + + /* ------------------------------------------------------------ */ + public ContextHandler createContextHandler(App app) throws Exception + { + if (app == null) + return null; + if (!(app instanceof OSGiApp)) + throw new IllegalStateException(app+" is not a BundleApp"); + + //Create a WebAppContext suitable to deploy in OSGi + ContextHandler ch = ((OSGiApp)app).createContextHandler(); + return ch; + } + + + /* ------------------------------------------------------------ */ + public static String getOriginId(Bundle contributor, String path) + { + return contributor.getSymbolicName() + "-" + contributor.getVersion().toString() + (path.startsWith("/") ? path : "/" + path); + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java new file mode 100644 index 00000000000..149aa99b80b --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java @@ -0,0 +1,169 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + + + +/** + * BundleContextProvider + * + * Handles deploying bundles that define a context xml file for configuring them. + * + * + */ +public class BundleContextProvider extends AbstractContextProvider implements BundleProvider +{ + private static final Logger LOG = Log.getLogger(AbstractContextProvider.class); + + + private Map _appMap = new HashMap(); + + private Map> _bundleMap = new HashMap>(); + + private ServiceRegistration _serviceRegForBundles; + + + + + /* ------------------------------------------------------------ */ + public BundleContextProvider(ServerInstanceWrapper wrapper) + { + super(wrapper); + } + + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + //register as an osgi service for deploying contexts defined in a bundle, advertising the name of the jetty Server instance we are related to + Dictionary properties = new Hashtable(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, getServerInstanceWrapper().getManagedServerName()); + _serviceRegForBundles = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(BundleProvider.class.getName(), this, properties); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + //unregister ourselves + if (_serviceRegForBundles != null) + { + try + { + _serviceRegForBundles.unregister(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + } + + + + + /* ------------------------------------------------------------ */ + /** + * @param bundle + * @param contextFiles + * @return + */ + public boolean bundleAdded (Bundle bundle) throws Exception + { + if (bundle == null) + return false; + + String contextFiles = (String)bundle.getHeaders().get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH); + if (contextFiles == null) + contextFiles = (String)bundle.getHeaders().get(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); + + if (contextFiles == null) + return false; + + boolean added = false; + //bundle defines JETTY_CONTEXT_FILE_PATH header, + //a comma separated list of context xml files that each define a ContextHandler + //TODO: (could be WebAppContexts) + String[] tmp = contextFiles.split(",;"); + for (String contextFile : tmp) + { + String originId = bundle.getSymbolicName() + "-" + bundle.getVersion().toString() + "-"+contextFile; + OSGiApp app = new OSGiApp(getDeploymentManager(), this, originId, bundle, contextFile); + _appMap.put(originId,app); + List apps = _bundleMap.get(bundle); + if (apps == null) + { + apps = new ArrayList(); + _bundleMap.put(bundle, apps); + } + apps.add(app); + getDeploymentManager().addApp(app); + } + + return added; //true if even 1 context from this bundle was added + } + + + /* ------------------------------------------------------------ */ + /** + * Bundle has been removed. If it was a context we deployed, undeploy it. + * @param bundle + * + * @return true if this was a context we had deployed, false otherwise + */ + public boolean bundleRemoved (Bundle bundle) throws Exception + { + List apps = _bundleMap.remove(bundle); + boolean removed = false; + if (apps != null) + { + for (App app:apps) + { + _appMap.remove(app.getOriginId()); + getDeploymentManager().removeApp(app); + removed = true; + } + } + return removed; //true if even 1 context was removed associated with this bundle + } + + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java new file mode 100644 index 00000000000..c87c071bf52 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java @@ -0,0 +1,28 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import org.osgi.framework.Bundle; + +public interface BundleProvider +{ + public boolean bundleAdded (Bundle bundle) throws Exception; + + public boolean bundleRemoved (Bundle bundle) throws Exception; +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java new file mode 100644 index 00000000000..2a5e6e3cd0d --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java @@ -0,0 +1,238 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; + + + +/** + * BundleWebAppProvider + * + * A Jetty Provider that knows how to deploy a WebApp contained inside a Bundle. + * + */ +public class BundleWebAppProvider extends AbstractWebAppProvider implements BundleProvider +{ + private static final Logger LOG = Log.getLogger(AbstractWebAppProvider.class); + + /** + * Map of Bundle to App. Used when a Bundle contains a webapp. + */ + private Map _bundleMap = new HashMap(); + + private ServiceRegistration _serviceRegForBundles; + + + /* ------------------------------------------------------------ */ + /** + * @param wrapper + */ + public BundleWebAppProvider (ServerInstanceWrapper wrapper) + { + super(wrapper); + } + + + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + //register as an osgi service for deploying bundles, advertising the name of the jetty Server instance we are related to + Dictionary properties = new Hashtable(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, getServerInstanceWrapper().getManagedServerName()); + _serviceRegForBundles = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(BundleProvider.class.getName(), this, properties); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + //unregister ourselves + if (_serviceRegForBundles != null) + { + try + { + _serviceRegForBundles.unregister(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + + super.doStop(); + } + + + + + + + /* ------------------------------------------------------------ */ + /** + * A bundle has been added that could be a webapp + * @param bundle + */ + public boolean bundleAdded (Bundle bundle) throws Exception + { + if (bundle == null) + return false; + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps()); + String contextPath = null; + try + { + Dictionary headers = bundle.getHeaders(); + + //does the bundle have a OSGiWebappConstants.JETTY_WAR_FOLDER_PATH + if (headers.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH) != null) + { + String base = (String)headers.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); + contextPath = getContextPath(bundle); + String originId = getOriginId(bundle, base); + + //TODO : we don't know whether an app is actually deployed, as deploymentManager swallows all + //exceptions inside the impl of addApp. Need to send the Event and also register as a service + //only if the deployment succeeded + OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle, originId); + app.setWebAppPath(base); + app.setContextPath(contextPath); + _bundleMap.put(bundle, app); + getDeploymentManager().addApp(app); + return true; + } + + + //does the bundle have a WEB-INF/web.xml + if (bundle.getEntry("/WEB-INF/web.xml") != null) + { + String base = "."; + contextPath = getContextPath(bundle); + String originId = getOriginId(bundle, base); + + OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle, originId); + app.setContextPath(contextPath); + app.setWebAppPath(base); + _bundleMap.put(bundle, app); + getDeploymentManager().addApp(app); + return true; + } + + //does the bundle define a OSGiWebappConstants.RFC66_WEB_CONTEXTPATH + if (headers.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH) != null) + { + //Could be a static webapp with no web.xml + String base = "."; + contextPath = (String)headers.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + String originId = getOriginId(bundle,base); + + OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle, originId); + app.setContextPath(contextPath); + app.setWebAppPath(base); + _bundleMap.put(bundle, app); + getDeploymentManager().addApp(app); + return true; + } + + return false; + } + catch (Exception e) + { + + throw e; + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + + + /* ------------------------------------------------------------ */ + /** + * Bundle has been removed. If it was a webapp we deployed, undeploy it. + * @param bundle + * + * @return true if this was a webapp we had deployed, false otherwise + */ + public boolean bundleRemoved (Bundle bundle) throws Exception + { + App app = _bundleMap.remove(bundle); + if (app != null) + { + getDeploymentManager().removeApp(app); + return true; + } + return false; + } + + + + + + /* ------------------------------------------------------------ */ + private static String getContextPath(Bundle bundle) + { + Dictionary headers = bundle.getHeaders(); + String contextPath = (String) headers.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + if (contextPath == null) + { + // extract from the last token of the bundle's location: + // (really ?could consider processing the symbolic name as an alternative + // the location will often reflect the version. + // maybe this is relevant when the file is a war) + String location = bundle.getLocation(); + String toks[] = location.replace('\\', '/').split("/"); + contextPath = toks[toks.length - 1]; + // remove .jar, .war etc: + int lastDot = contextPath.lastIndexOf('.'); + if (lastDot != -1) + contextPath = contextPath.substring(0, lastDot); + } + if (!contextPath.startsWith("/")) + contextPath = "/" + contextPath; + + return contextPath; + } + + + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java index 777ebdf608c..2ed6e7bf5e0 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java @@ -66,8 +66,6 @@ public class JettyBootstrapActivator implements BundleActivator private ServiceRegistration _registeredServer; - private Server _server; - private JettyContextHandlerServiceTracker _jettyContextHandlerTracker; private PackageAdminServiceTracker _packageAdminServiceTracker; @@ -95,20 +93,21 @@ public class JettyBootstrapActivator implements BundleActivator // should activate. _packageAdminServiceTracker = new PackageAdminServiceTracker(context); - // track Server instances that we should support as deployment targets + // track jetty Server instances that we should support as deployment targets _jettyServerServiceTracker = new JettyServerServiceTracker(); context.addServiceListener(_jettyServerServiceTracker, "(objectclass=" + Server.class.getName() + ")"); // track ContextHandler class instances and deploy them to one of the known Servers - _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker(_jettyServerServiceTracker); + _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker(); context.addServiceListener(_jettyContextHandlerTracker, "(objectclass=" + ContextHandler.class.getName() + ")"); // Create a default jetty instance right now. DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(context); // track Bundles and deploy those that represent webapps to one of the known Servers - _webBundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, new WebBundleTrackerCustomizer()); - _webBundleTracker.open(); + WebBundleTrackerCustomizer customizer = new WebBundleTrackerCustomizer(); + _webBundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, customizer); + customizer.setAndOpenWebBundleTracker(_webBundleTracker); } /** @@ -129,7 +128,6 @@ public class JettyBootstrapActivator implements BundleActivator } if (_jettyContextHandlerTracker != null) { - _jettyContextHandlerTracker.stop(); context.removeServiceListener(_jettyContextHandlerTracker); _jettyContextHandlerTracker = null; } @@ -163,10 +161,6 @@ public class JettyBootstrapActivator implements BundleActivator } finally { - if (_server != null) - { - _server.stop(); - } INSTANCE = null; } } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java deleted file mode 100644 index b23e3fa7969..00000000000 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java +++ /dev/null @@ -1,734 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.osgi.boot; - -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map.Entry; -import java.util.Set; - -import org.eclipse.jetty.deploy.App; -import org.eclipse.jetty.deploy.AppProvider; -import org.eclipse.jetty.deploy.DeploymentManager; -import org.eclipse.jetty.deploy.providers.ScanningAppProvider; -import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.Scanner; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.webapp.WebAppContext; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; -import org.osgi.framework.Constants; - -/** - * AppProvider for OSGi. Supports the configuration of ContextHandlers and - * WebApps. Extends the AbstractAppProvider to support the scanning of context - * files located outside of the bundles. - *

- * This provider must not be called outside of jetty.boot: it should always be - * called via the OSGi service listener. - *

- *

- * This provider supports the same set of parameters than the WebAppProvider as - * it supports the deployment of WebAppContexts. Except for the scanning of the - * webapps directory. - *

- *

- * When the parameter autoInstallOSGiBundles is set to true, OSGi bundles that - * are located in the monitored directory are installed and started after the - * framework as finished auto-starting all the other bundles. Warning: only use - * this for development. - *

- */ -public class OSGiAppProvider extends ScanningAppProvider implements AppProvider -{ - private static final Logger LOG = Log.getLogger(OSGiAppProvider.class); - - private boolean _extractWars = true; - - private boolean _parentLoaderPriority = false; - - private String _defaultsDescriptor; - - private String _tldBundles; - - private String[] _configurationClasses; - - private boolean _autoInstallOSGiBundles = true; - - // Keep track of the bundles that were installed and that are waiting for - // the - // framework to complete its initialization. - Set _pendingBundlesToStart = null; - - /** - * When a context file corresponds to a deployed bundle and is changed we - * reload the corresponding bundle. - */ - private static class Filter implements FilenameFilter - { - OSGiAppProvider _enclosedInstance; - - @Override - public boolean accept(File dir, String name) - { - File file = new File(dir, name); - if (fileMightBeAnOSGiBundle(file)) { return true; } - if (!file.isDirectory()) - { - String contextName = getDeployedAppName(name); - if (contextName != null) - { - App app = _enclosedInstance.getDeployedApps().get(contextName); - return app != null; - } - } - return false; - } - } - - /** - * @param contextFileName for example myContext.xml - * @return The context, for example: myContext; null if this was not a - * suitable contextFileName. - */ - private static String getDeployedAppName(String contextFileName) - { - String lowername = contextFileName.toLowerCase(Locale.ENGLISH); - if (lowername.endsWith(".xml")) - { - String contextName = contextFileName.substring(0, lowername.length() - ".xml".length()); - return contextName; - } - return null; - } - - /** - * Reading the display name of a webapp is really not sufficient for - * indexing the various deployed ContextHandlers. - * - * @param context - * @return - */ - private String getContextHandlerAppName(ContextHandler context) - { - String appName = context.getDisplayName(); - if (appName == null || appName.length() == 0 || getDeployedApps().containsKey(appName)) - { - if (context instanceof WebAppContext) - { - appName = ((WebAppContext) context).getContextPath(); - if (getDeployedApps().containsKey(appName)) - { - appName = "noDisplayName" + context.getClass().getSimpleName() + context.hashCode(); - } - } - else - { - appName = "noDisplayName" + context.getClass().getSimpleName() + context.hashCode(); - } - } - return appName; - } - - /** - * Default OSGiAppProvider constructed when none are defined in the - * jetty.xml configuration. - */ - public OSGiAppProvider() - { - super(new Filter()); - ((Filter) super._filenameFilter)._enclosedInstance = this; - } - - /** - * Default OSGiAppProvider constructed when none are defined in the - * jetty.xml configuration. - * - * @param contextsDir - */ - public OSGiAppProvider(File contextsDir) throws IOException - { - this(); - setMonitoredDirResource(Resource.newResource(contextsDir.toURI())); - } - - /** - * Returns the ContextHandler that was created by WebappRegistractionHelper - * - * @see AppProvider - */ - @Override - public ContextHandler createContextHandler(App app) throws Exception - { - // return pre-created Context - ContextHandler wah = app.getContextHandler(); - if (wah == null) - { - // for some reason it was not defined when the App was constructed. - // we don't support this situation at this point. - // once the WebAppRegistrationHelper is refactored, the code - // that creates the ContextHandler will actually be here. - throw new IllegalStateException("The App must be passed the " + "instance of the ContextHandler when it is constructed"); - } - if (_configurationClasses != null && wah instanceof WebAppContext) - { - ((WebAppContext) wah).setConfigurationClasses(_configurationClasses); - } - - if (_defaultsDescriptor != null) - ((WebAppContext) wah).setDefaultsDescriptor(_defaultsDescriptor); - return app.getContextHandler(); - } - - /** - * @see AppProvider - */ - @Override - public void setDeploymentManager(DeploymentManager deploymentManager) - { - super.setDeploymentManager(deploymentManager); - } - - private static String getOriginId(Bundle contributor, String pathInBundle) - { - return contributor.getSymbolicName() + "-" + contributor.getVersion().toString() + (pathInBundle.startsWith("/") ? pathInBundle : "/" + pathInBundle); - } - - /** - * @param context - * @throws Exception - */ - public void addContext(Bundle contributor, String pathInBundle, ContextHandler context) throws Exception - { - addContext(getOriginId(contributor, pathInBundle), context); - } - - /** - * @param context - * @throws Exception - */ - public void addContext(String originId, ContextHandler context) throws Exception - { - // TODO apply configuration specific to this provider - if (context instanceof WebAppContext) - { - ((WebAppContext) context).setExtractWAR(isExtract()); - } - - // wrap context as an App - App app = new App(getDeploymentManager(), this, originId, context); - String appName = getContextHandlerAppName(context); - getDeployedApps().put(appName, app); - getDeploymentManager().addApp(app); - } - - /** - * Called by the scanner of the context files directory. If we find the - * corresponding deployed App we reload it by returning the App. Otherwise - * we return null and nothing happens: presumably the corresponding OSGi - * webapp is not ready yet. - * - * @return the corresponding already deployed App so that it will be - * reloaded. Otherwise returns null. - */ - @Override - protected App createApp(String filename) - { - // find the corresponding bundle and ContextHandler or WebAppContext - // and reload the corresponding App. - // see the 2 pass of the refactoring of the WebAppRegistrationHelper. - String name = getDeployedAppName(filename); - if (name != null) { return getDeployedApps().get(name); } - return null; - } - - public void removeContext(ContextHandler context) throws Exception - { - String appName = getContextHandlerAppName(context); - App app = getDeployedApps().remove(context.getDisplayName()); - if (app == null) - { - // try harder to undeploy this context handler. - // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=330098 - appName = null; - for (Entry deployedApp : getDeployedApps().entrySet()) - { - if (deployedApp.getValue().getContextHandler() == context) - { - app = deployedApp.getValue(); - appName = deployedApp.getKey(); - break; - } - } - if (appName != null) - { - getDeployedApps().remove(appName); - } - } - if (app != null) - { - getDeploymentManager().removeApp(app); - } - } - - - /* ------------------------------------------------------------ */ - /** - * Get the parentLoaderPriority. - * - * @return the parentLoaderPriority - */ - public boolean isParentLoaderPriority() - { - return _parentLoaderPriority; - } - - /* ------------------------------------------------------------ */ - /** - * Set the parentLoaderPriority. - * - * @param parentLoaderPriority the parentLoaderPriority to set - */ - public void setParentLoaderPriority(boolean parentLoaderPriority) - { - _parentLoaderPriority = parentLoaderPriority; - } - - /* ------------------------------------------------------------ */ - /** - * Get the defaultsDescriptor. - * - * @return the defaultsDescriptor - */ - public String getDefaultsDescriptor() - { - return _defaultsDescriptor; - } - - /* ------------------------------------------------------------ */ - /** - * Set the defaultsDescriptor. - * - * @param defaultsDescriptor the defaultsDescriptor to set - */ - public void setDefaultsDescriptor(String defaultsDescriptor) - { - _defaultsDescriptor = defaultsDescriptor; - } - - /** - * The context xml directory. In fact it is the directory watched by the - * scanner. - */ - public File getContextXmlDirAsFile() - { - try - { - Resource monitoredDir = getMonitoredDirResource(); - if (monitoredDir == null) return null; - return monitoredDir.getFile(); - } - catch (IOException e) - { - LOG.warn(e); - return null; - } - } - - /* ------------------------------------------------------------ */ - /** - * The context xml directory. In fact it is the directory watched by the - * scanner. - */ - public String getContextXmlDir() - { - try - { - Resource monitoredDir = getMonitoredDirResource(); - if (monitoredDir == null) return null; - return monitoredDir.getFile().toURI().toString(); - } - catch (IOException e) - { - LOG.warn(e); - return null; - } - } - - public boolean isExtract() - { - return _extractWars; - } - - public void setExtract(boolean extract) - { - _extractWars = extract; - } - - /** - * @return true when this app provider locates osgi bundles and features in - * its monitored directory and installs them. By default true if - * there is a folder to monitor. - */ - public boolean isAutoInstallOSGiBundles() - { - return _autoInstallOSGiBundles; - } - - /** - * <autoInstallOSGiBundles>true</autoInstallOSGiBundles> - * - * @param installingOSGiBundles - */ - public void setAutoInstallOSGiBundles(boolean installingOSGiBundles) - { - _autoInstallOSGiBundles = installingOSGiBundles; - } - - /* ------------------------------------------------------------ */ - /** - * Set the directory in which to look for context XML files. - *

- * If a webapp call "foo/" or "foo.war" is discovered in the monitored - * directory, then the ContextXmlDir is examined to see if a foo.xml file - * exists. If it does, then this deployer will not deploy the webapp and the - * ContextProvider should be used to act on the foo.xml file. - *

- *

- * Also if this directory contains some osgi bundles, it will install them. - *

- * - * @see ContextProvider - * @param contextsDir - */ - public void setContextXmlDir(String contextsDir) - { - setMonitoredDirName(contextsDir); - } - - /** - * @param tldBundles Comma separated list of bundles that contain tld jars - * that should be setup on the jetty instances created here. - */ - public void setTldBundles(String tldBundles) - { - _tldBundles = tldBundles; - } - - /** - * @return The list of bundles that contain tld jars that should be setup on - * the jetty instances created here. - */ - public String getTldBundles() - { - return _tldBundles; - } - - /** - * @param configurations The configuration class names. - */ - public void setConfigurationClasses(String[] configurations) - { - _configurationClasses = configurations == null ? null : (String[]) configurations.clone(); - } - - /* ------------------------------------------------------------ */ - /** - * - */ - public String[] getConfigurationClasses() - { - return _configurationClasses; - } - - /** - * Overridden to install the OSGi bundles found in the monitored folder. - */ - @Override - protected void doStart() throws Exception - { - if (isAutoInstallOSGiBundles()) - { - if (getMonitoredDirResource() == null) - { - setAutoInstallOSGiBundles(false); - LOG.info("Disable autoInstallOSGiBundles as there is not contexts folder to monitor."); - } - else - { - File scandir = null; - try - { - scandir = getMonitoredDirResource().getFile(); - if (!scandir.exists() || !scandir.isDirectory()) - { - setAutoInstallOSGiBundles(false); - LOG.warn("Disable autoInstallOSGiBundles as the contexts folder '" + scandir.getAbsolutePath() + " does not exist."); - scandir = null; - } - } - catch (IOException ioe) - { - setAutoInstallOSGiBundles(false); - LOG.warn("Disable autoInstallOSGiBundles as the contexts folder '" + getMonitoredDirResource().getURI() + " does not exist."); - scandir = null; - } - if (scandir != null) - { - for (File file : scandir.listFiles()) - { - if (fileMightBeAnOSGiBundle(file)) - { - installBundle(file, false); - } - } - } - } - } - super.doStart(); - if (isAutoInstallOSGiBundles()) - { - Scanner.ScanCycleListener scanCycleListner = new AutoStartWhenFrameworkHasCompleted(this); - super.addScannerListener(scanCycleListner); - } - } - - /** - * When the file is a jar or a folder, we look if it looks like an OSGi - * bundle. In that case we install it and start it. - *

- * Really a simple trick to get going quickly with development. - *

- */ - @Override - protected void fileAdded(String filename) throws Exception - { - File file = new File(filename); - if (isAutoInstallOSGiBundles() && file.exists() && fileMightBeAnOSGiBundle(file)) - { - installBundle(file, true); - } - else - { - super.fileAdded(filename); - } - } - - /** - * @param file - * @return - */ - private static boolean fileMightBeAnOSGiBundle(File file) - { - if (file.isDirectory()) - { - if (new File(file, "META-INF/MANIFEST.MF").exists()) { return true; } - } - else if (file.getName().endsWith(".jar")) { return true; } - return false; - } - - @Override - protected void fileChanged(String filename) throws Exception - { - File file = new File(filename); - if (isAutoInstallOSGiBundles() && fileMightBeAnOSGiBundle(file)) - { - updateBundle(file); - } - else - { - super.fileChanged(filename); - } - } - - @Override - protected void fileRemoved(String filename) throws Exception - { - File file = new File(filename); - if (isAutoInstallOSGiBundles() && fileMightBeAnOSGiBundle(file)) - { - uninstallBundle(file); - } - else - { - super.fileRemoved(filename); - } - } - - /** - * Returns a bundle according to its location. In the version 1.6 of - * org.osgi.framework, BundleContext.getBundle(String) is what we want. - * However to support older versions of OSGi. We use our own local reference - * mechanism. - * - * @param location - * @return - */ - protected Bundle getBundle(BundleContext bc, String location) - { - // not available in older versions of OSGi: - // return bc.getBundle(location); - for (Bundle b : bc.getBundles()) - { - if (b.getLocation().equals(location)) { return b; } - } - return null; - } - - protected synchronized Bundle installBundle(File file, boolean start) - { - - try - { - BundleContext bc = JettyBootstrapActivator.getBundleContext(); - String location = file.toURI().toString(); - Bundle b = getBundle(bc, location); - if (b == null) - { - b = bc.installBundle(location); - } - if (b == null) - { - // not sure we will ever be here, - // most likely a BundleException was thrown - LOG.warn("The file " + location + " is not an OSGi bundle."); - return null; - } - if (start && b.getHeaders().get(Constants.FRAGMENT_HOST) == null) - { - // not a fragment, try to start it. if the framework has finished - // auto-starting. - if (!PackageAdminServiceTracker.INSTANCE.frameworkHasCompletedAutostarts()) - { - if (_pendingBundlesToStart == null) - { - _pendingBundlesToStart = new HashSet(); - } - _pendingBundlesToStart.add(b); - return null; - } - else - { - b.start(); - } - } - return b; - } - catch (BundleException e) - { - LOG.warn("Unable to " + (start ? "start" : "install") + " the bundle " + file.getAbsolutePath(), e); - } - return null; - } - - protected void uninstallBundle(File file) - { - try - { - Bundle b = getBundle(JettyBootstrapActivator.getBundleContext(), file.toURI().toString()); - b.stop(); - b.uninstall(); - } - catch (BundleException e) - { - LOG.warn("Unable to uninstall the bundle " + file.getAbsolutePath(), e); - } - } - - protected void updateBundle(File file) - { - try - { - Bundle b = getBundle(JettyBootstrapActivator.getBundleContext(), file.toURI().toString()); - if (b == null) - { - installBundle(file, true); - } - else if (b.getState() == Bundle.ACTIVE) - { - b.update(); - } - else - { - b.start(); - } - } - catch (BundleException e) - { - LOG.warn("Unable to update the bundle " + file.getAbsolutePath(), e); - } - } - -} - -/** - * At the end of each scan, if there are some bundles to be started, look if the - * framework has completed its autostart. In that case start those bundles. - */ -class AutoStartWhenFrameworkHasCompleted implements Scanner.ScanCycleListener -{ - private static final Logger LOG = Log.getLogger(AutoStartWhenFrameworkHasCompleted.class); - - private final OSGiAppProvider _appProvider; - - AutoStartWhenFrameworkHasCompleted(OSGiAppProvider appProvider) - { - _appProvider = appProvider; - } - - public void scanStarted(int cycle) throws Exception - { - } - - public void scanEnded(int cycle) throws Exception - { - if (_appProvider._pendingBundlesToStart != null && PackageAdminServiceTracker.INSTANCE.frameworkHasCompletedAutostarts()) - { - Iterator it = _appProvider._pendingBundlesToStart.iterator(); - while (it.hasNext()) - { - Bundle b = it.next(); - if (b.getHeaders().get(Constants.FRAGMENT_HOST) != null) - { - continue; - } - try - { - b.start(); - } - catch (BundleException e) - { - LOG.warn("Unable to start the bundle " + b.getLocation(), e); - } - - } - _appProvider._pendingBundlesToStart = null; - } - } - -} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java new file mode 100644 index 00000000000..5f9321ebc9a --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java @@ -0,0 +1,61 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.bindings.StandardDeployer; +import org.eclipse.jetty.deploy.graph.Node; +import org.eclipse.jetty.osgi.boot.utils.EventSender; + + +/** + * OSGiDeployer + * + * + */ +public class OSGiDeployer extends StandardDeployer +{ + + /* ------------------------------------------------------------ */ + public void processBinding(Node node, App app) throws Exception + { + //TODO how to NOT send this event if its not a webapp: + //OSGi Enterprise Spec only wants an event sent if its a webapp bundle (ie not a ContextHandler) + if (!(app instanceof AbstractOSGiApp)) + { + super.processBinding(node,app); + } + else + { + EventSender.getInstance().send(EventSender.DEPLOYING_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + try + { + super.processBinding(node,app); + ((AbstractOSGiApp)app).registerAsOSGiService(); + EventSender.getInstance().send(EventSender.DEPLOYED_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + } + catch (Exception e) + { + EventSender.getInstance().send(EventSender.FAILED_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + throw e; + } + } + + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java new file mode 100644 index 00000000000..bacc8ea9a03 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java @@ -0,0 +1,116 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; +import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.MetaInfConfiguration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; + +public class OSGiMetaInfConfiguration extends MetaInfConfiguration +{ + private static final Logger LOG = Log.getLogger(OSGiMetaInfConfiguration.class); + + + /** + * Inspect bundle fragments associated with the bundle of the webapp for web-fragment, resources, tlds. + * + * @see org.eclipse.jetty.webapp.MetaInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + public void preConfigure(final WebAppContext context) throws Exception + { + List frags = (List) context.getAttribute(METAINF_FRAGMENTS); + List resfrags = (List) context.getAttribute(METAINF_RESOURCES); + List tldfrags = (List) context.getAttribute(METAINF_TLDS); + + Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE)); + //TODO not convinced we need to do this, as we added any fragment jars to the MetaData.webInfJars in OSGiWebInfConfiguration, + //so surely the web-fragments and resources tlds etc can be discovered normally? + for (Bundle frag : fragments) + { + URL webFrag = frag.getEntry("/META-INF/web-fragment.xml"); + Enumeration resEnum = frag.findEntries("/META-INF/resources", "*", true); + Enumeration tldEnum = frag.findEntries("/META-INF", "*.tld", false); + if (webFrag != null || (resEnum != null && resEnum.hasMoreElements()) || (tldEnum != null && tldEnum.hasMoreElements())) + { + try + { + if (webFrag != null) + { + if (frags == null) + { + frags = new ArrayList(); + context.setAttribute(METAINF_FRAGMENTS, frags); + } + frags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(frag).toURI())); + } + if (resEnum != null && resEnum.hasMoreElements()) + { + URL resourcesEntry = frag.getEntry("/META-INF/resources/"); + if (resourcesEntry == null) + { + // probably we found some fragments to a + // bundle. + // those are already contributed. + // so we skip this. + } + else + { + if (resfrags == null) + { + resfrags = new ArrayList(); + context.setAttribute(METAINF_RESOURCES, resfrags); + } + resfrags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(resourcesEntry))); + } + } + if (tldEnum != null && tldEnum.hasMoreElements()) + { + if (tldfrags == null) + { + tldfrags = new ArrayList(); + context.setAttribute(METAINF_TLDS, tldfrags); + } + while (tldEnum.hasMoreElements()) + { + URL tldUrl = tldEnum.nextElement(); + tldfrags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(tldUrl))); + } + } + } + catch (Exception e) + { + LOG.warn("Unable to locate the bundle " + frag.getBundleId(), e); + } + } + } + + super.preConfigure(context); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java index 53b35c7fd6f..2f9df55ad82 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java @@ -23,6 +23,38 @@ package org.eclipse.jetty.osgi.boot; */ public class OSGiServerConstants { + /** + * Usual system property used as the hostname for a typical jetty + * configuration. + */ + public static final String JETTY_HOME = "jetty.home"; + + /** + * System property to point to a bundle that embeds a jetty configuration + * and that jetty configuration should be the default jetty server. First we + * look for jetty.home. If we don't find it then we look for this property. + */ + public static final String JETTY_HOME_BUNDLE = "jetty.home.bundle"; + + /** + * Usual system property used as the hostname for a typical jetty + * configuration. + */ + public static final String JETTY_HOST = "jetty.host"; + + /** + * Usual system property used as the port for http for a typical jetty + * configuration. + */ + public static final String JETTY_PORT = "jetty.port"; + + /** + * Usual system property used as the port for https for a typical jetty + * configuration. + */ + public static final String JETTY_PORT_SSL = "jetty.port.ssl"; + + //for managed jetty instances, name of the configuration parameters /** * PID of the jetty servers's ManagedFactory diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java new file mode 100644 index 00000000000..ac068741d09 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java @@ -0,0 +1,44 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.bindings.StandardUndeployer; +import org.eclipse.jetty.deploy.graph.Node; +import org.eclipse.jetty.osgi.boot.utils.EventSender; + + + + +/** + * OSGiUndeployer + * + * + */ +public class OSGiUndeployer extends StandardUndeployer +{ + /* ------------------------------------------------------------ */ + public void processBinding(Node node, App app) throws Exception + { + EventSender.getInstance().send(EventSender.UNDEPLOYING_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + super.processBinding(node,app); + EventSender.getInstance().send(EventSender.UNDEPLOYED_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + ((AbstractOSGiApp)app).deregisterAsOSGiService(); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java new file mode 100644 index 00000000000..80deac4fb59 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java @@ -0,0 +1,273 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.StringTokenizer; +import java.util.TreeMap; +import java.util.regex.Pattern; + +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; +import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceCollection; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebInfConfiguration; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; + + + +/** + * OSGiWebInfConfiguration + * + * Handle adding resources found in bundle fragments, and add them into the + */ +public class OSGiWebInfConfiguration extends WebInfConfiguration +{ + private static final Logger LOG = Log.getLogger(WebInfConfiguration.class); + + + public static final String CONTAINER_BUNDLE_PATTERN = "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern"; + + + /** + * Check to see if there have been any bundle symbolic names added of bundles that should be + * regarded as being on the container classpath, and scanned for fragments, tlds etc etc. + * This can be defined in: + *
    + *
  1. SystemProperty SYS_PROP_TLD_BUNDLES
  2. + *
  3. DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN
  4. + *
+ * + * We also allow individual bundles to specify particular bundles that might include TLDs via the Require-Tlds + * MANIFEST.MF header. This is processed in the TagLibOSGiConfiguration class. + * + * @see org.eclipse.jetty.webapp.WebInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + public void preConfigure(final WebAppContext context) throws Exception + { + super.preConfigure(context); + + //Check to see if there have been any bundle symbolic names added of bundles that should be + //regarded as being on the container classpath, and scanned for fragments, tlds etc etc. + //This can be defined in: + // 1. SystemProperty SYS_PROP_TLD_BUNDLES + // 2. DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN + String tmp = (String)context.getAttribute(CONTAINER_BUNDLE_PATTERN); + Pattern pattern = (tmp==null?null:Pattern.compile(tmp)); + List names = new ArrayList(); + tmp = System.getProperty("org.eclipse.jetty.osgi.tldbundles"); + if (tmp != null) + { + StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false); + while (tokenizer.hasMoreTokens()) + names.add(tokenizer.nextToken()); + } + + HashSet matchingResources = new HashSet(); + if ( !names.isEmpty() || pattern != null) + { + Bundle[] bundles = FrameworkUtil.getBundle(OSGiWebInfConfiguration.class).getBundleContext().getBundles(); + + for (Bundle bundle : bundles) + { + if (pattern != null) + { + // if bundle symbolic name matches the pattern + if (pattern.matcher(bundle.getSymbolicName()).matches()) + { + //get the file location of the jar and put it into the list of container jars that will be scanned for stuff (including tlds) + matchingResources.addAll(getBundleAsResource(bundle)); + } + } + if (names != null) + { + //if there is an explicit bundle name, then check if it matches + if (names.contains(bundle.getSymbolicName())) + matchingResources.addAll(getBundleAsResource(bundle)); + } + } + } + + for (Resource r:matchingResources) + { + context.getMetaData().addContainerJar(r); + } + } + + + + /** + * Consider the fragment bundles associated with the bundle of the webapp being deployed. + * + * + * @see org.eclipse.jetty.webapp.WebInfConfiguration#findJars(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + protected List findJars (WebAppContext context) + throws Exception + { + List mergedResources = new ArrayList(); + //get jars from WEB-INF/lib if there are any + List webInfJars = super.findJars(context); + if (webInfJars != null) + mergedResources.addAll(webInfJars); + + //add fragment jars as if in WEB-INF/lib of the associated webapp + Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE)); + for (Bundle frag : fragments) + { + File fragFile = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(frag); + mergedResources.add(Resource.newResource(fragFile.toURI())); + } + + return mergedResources; + } + + + /** + * Allow fragments to supply some resources that are added to the baseResource of the webapp. + * + * The resources can be either prepended or appended to the baseResource. + * + * @see org.eclipse.jetty.webapp.WebInfConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + public void configure(WebAppContext context) throws Exception + { + TreeMap patchResourcesPath = new TreeMap(); + TreeMap appendedResourcesPath = new TreeMap(); + + Bundle bundle = (Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE); + if (bundle != null) + { + //TODO anything we need to do to improve PackageAdminServiceTracker? + Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles(bundle); + if (fragments != null && fragments.length != 0) + { + // sorted extra resource base found in the fragments. + // the resources are either overriding the resourcebase found in the + // web-bundle + // or appended. + // amongst each resource we sort them according to the alphabetical + // order + // of the name of the internal folder and the symbolic name of the + // fragment. + // this is useful to make sure that the lookup path of those + // resource base defined by fragments is always the same. + // This natural order could be abused to define the order in which + // the base resources are + // looked up. + for (Bundle frag : fragments) + { + String fragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_FRAGMENT_FOLDER_PATH); + String patchFragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH); + if (fragFolder != null) + { + URL fragUrl = frag.getEntry(fragFolder); + if (fragUrl == null) { throw new IllegalArgumentException("Unable to locate " + fragFolder + + " inside " + + " the fragment '" + + frag.getSymbolicName() + + "'"); } + fragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(fragUrl); + String key = fragFolder.startsWith("/") ? fragFolder.substring(1) : fragFolder; + appendedResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(fragUrl)); + } + if (patchFragFolder != null) + { + URL patchFragUrl = frag.getEntry(patchFragFolder); + if (patchFragUrl == null) + { + throw new IllegalArgumentException("Unable to locate " + patchFragUrl + + " inside fragment '"+frag.getSymbolicName()+ "'"); + } + patchFragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(patchFragUrl); + String key = patchFragFolder.startsWith("/") ? patchFragFolder.substring(1) : patchFragFolder; + patchResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(patchFragUrl)); + } + } + if (!appendedResourcesPath.isEmpty()) + context.setAttribute(WebInfConfiguration.RESOURCE_URLS, new ArrayList(appendedResourcesPath.values())); + } + } + + super.configure(context); + + // place the patch resources at the beginning of the contexts's resource base + if (!patchResourcesPath.isEmpty()) + { + Resource[] resources = new Resource[1+patchResourcesPath.size()]; + ResourceCollection mergedResources = new ResourceCollection (patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()])); + System.arraycopy(patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()]), 0, resources, 0, patchResourcesPath.size()); + resources[resources.length-1] = context.getBaseResource(); + context.setBaseResource(new ResourceCollection(resources)); + } + + } + + + + /** + * Resolves the bundle. Usually that would be a single URL per bundle. But we do some more work if there are jars + * embedded in the bundle. + */ + private List getBundleAsResource(Bundle bundle) + throws Exception + { + List resources = new ArrayList(); + + File file = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle); + if (file.isDirectory()) + { + for (File f : file.listFiles()) + { + if (f.getName().endsWith(".jar") && f.isFile()) + { + resources.add(Resource.newResource(f)); + } + else if (f.isDirectory() && f.getName().equals("lib")) + { + for (File f2 : file.listFiles()) + { + if (f2.getName().endsWith(".jar") && f2.isFile()) + { + resources.add(Resource.newResource(f)); + } + } + } + } + resources.add(Resource.newResource(file)); //TODO really??? + } + else + { + resources.add(Resource.newResource(file)); + } + + return resources; + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java index 3cb21232ed5..1908eb2de5c 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java @@ -23,6 +23,20 @@ package org.eclipse.jetty.osgi.boot; */ public class OSGiWebappConstants { + /** service property osgi.web.symbolicname. See OSGi r4 */ + public static final String OSGI_WEB_SYMBOLICNAME = "osgi.web.symbolicname"; + + /** service property osgi.web.symbolicname. See OSGi r4 */ + public static final String OSGI_WEB_VERSION = "osgi.web.version"; + + /** service property osgi.web.contextpath. See OSGi r4 */ + public static final String OSGI_WEB_CONTEXTPATH = "osgi.web.contextpath"; + + /** See OSGi r4 p.427 */ + public static final String OSGI_BUNDLECONTEXT = "osgi-bundlecontext"; + + + /** url scheme to deploy war file as bundled webapp */ public static final String RFC66_WAR_URL_SCHEME = "war"; @@ -59,32 +73,60 @@ public class OSGiWebappConstants * this will override static resources with the same name in the web-bundle. */ public static final String JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH = "Jetty-WarPatchFragmentFolderPath"; - // OSGi ContextHandler service properties. - /** web app context path */ + + /** + * web app context path + * @deprecated see RFC66_WEB_CONTEXTPATH + */ public static final String SERVICE_PROP_CONTEXT_PATH = "contextPath"; - /** Path to the web application base folder */ + + /** + * Path to the web application base folder + * @deprecated see JETTY_WAR_FOLDER_PATH + */ public static final String SERVICE_PROP_WAR = "war"; - /** Extra classpath */ + /** + * Extra classpath + * @deprecated see JETTY_EXTRA_CLASSPATH + */ public static final String SERVICE_PROP_EXTRA_CLASSPATH = "extraClasspath"; + + public static final String JETTY_EXTRA_CLASSPATH = "Jetty-extraClasspath"; - /** jetty context file path */ + /** + * jetty context file path + * @deprecated see JETTY_CONTEXT_FILE_PATH + */ public static final String SERVICE_PROP_CONTEXT_FILE_PATH = "contextFilePath"; - /** web.xml file path */ + /** + * web.xml file path + * @deprecated see JETTY_WEB_XML_PATH + */ public static final String SERVICE_PROP_WEB_XML_PATH = "webXmlFilePath"; + + public static final String JETTY_WEB_XML_PATH = "Jetty-WebXmlFilePath"; - /** defaultweb.xml file path */ + /** + * defaultweb.xml file path + * @deprecated see JETTY_DEFAULT_WEB_XML_PATH + */ public static final String SERVICE_PROP_DEFAULT_WEB_XML_PATH = "defaultWebXmlFilePath"; + + public static final String JETTY_DEFAULT_WEB_XML_PATH = "Jetty-defaultWebXmlFilePath"; /** * path to the base folder that overrides the computed bundle installation * location if not null useful to install webapps or jetty context files * that are in fact not embedded in a bundle + * @deprecated see JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE */ public static final String SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE = "thisBundleInstall"; + public static final String JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE = "Jetty-bundleInstall"; + /** * Comma separated list of bundles that contain tld file used by the webapp. */ @@ -94,4 +136,14 @@ public class OSGiWebappConstants * Both the name of the manifest header and the name of the service property. */ public static final String SERVICE_PROP_REQUIRE_TLD_BUNDLE = REQUIRE_TLD_BUNDLE; + + public static final String WATERMARK = "o.e.j.o.b.watermark"; + + /** + * Set of extra dirs that must not be served by osgi webapps + */ + public static final String[] DEFAULT_PROTECTED_OSGI_TARGETS = {"/osgi-inf", "/osgi-opts"}; + + + } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java new file mode 100644 index 00000000000..b7630cb1882 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java @@ -0,0 +1,186 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + +/** + * ServiceContextProvider + * + * + */ +public class ServiceContextProvider extends AbstractContextProvider implements ServiceProvider +{ + private static final Logger LOG = Log.getLogger(AbstractContextProvider.class); + + private Map _serviceMap = new HashMap(); + + private ServiceRegistration _serviceRegForServices; + + + /** + * ServiceApp + * + * + */ + public class ServiceApp extends OSGiApp + { + public ServiceApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String contextFile, String originId) + { + super(manager, provider, bundle, properties, contextFile, originId); + } + + public ServiceApp(DeploymentManager manager, AppProvider provider, String originId, Bundle bundle, String contextFile) + { + super(manager, provider, originId, bundle, contextFile); + } + + @Override + public void registerAsOSGiService() throws Exception + { + //not applicable for apps that are already services + } + + @Override + protected void deregisterAsOSGiService() throws Exception + { + //not applicable for apps that are already services + } + } + + + + /* ------------------------------------------------------------ */ + public ServiceContextProvider(ServerInstanceWrapper wrapper) + { + super(wrapper); + } + + + /* ------------------------------------------------------------ */ + public boolean serviceAdded (ServiceReference serviceRef, ContextHandler context) + { + if (context == null || serviceRef == null) + return false; + + String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return false; //this service represents a contexthandler that has already been registered as a service by another of our deployers + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps()); + try + { + //See if there is a context file to apply to this pre-made context + String contextFile = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH); + if (contextFile == null) + contextFile = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); + + String[] keys = serviceRef.getPropertyKeys(); + Dictionary properties = new Hashtable(); + if (keys != null) + { + for (String key:keys) + properties.put(key, serviceRef.getProperty(key)); + } + Bundle bundle = serviceRef.getBundle(); + String originId = bundle.getSymbolicName() + "-" + bundle.getVersion().toString() + "-"+contextFile; + ServiceApp app = new ServiceApp(getDeploymentManager(), this, bundle, properties, contextFile, originId); + app.setHandler(context); //set the pre=made ContextHandler instance + _serviceMap.put(serviceRef, app); + getDeploymentManager().addApp(app); + return true; + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + + + /* ------------------------------------------------------------ */ + public boolean serviceRemoved (ServiceReference serviceRef, ContextHandler context) + { + + if (context == null || serviceRef == null) + return false; + + String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return false; //this service represents a contexthandler that will be deregistered as a service by another of our deployers + + App app = _serviceMap.remove(serviceRef); + if (app != null) + { + getDeploymentManager().removeApp(app); + return true; + } + + return false; + } + + + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + //register as an osgi service for deploying contexts defined in a bundle, advertising the name of the jetty Server instance we are related to + Dictionary properties = new Hashtable(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, getServerInstanceWrapper().getManagedServerName()); + + //register as an osgi service for deploying contexts, advertising the name of the jetty Server instance we are related to + _serviceRegForServices = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(ServiceProvider.class.getName(), this, properties); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + //unregister ourselves + if (_serviceRegForServices != null) + { + try + { + _serviceRegForServices.unregister(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + super.doStop(); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java new file mode 100644 index 00000000000..f2304c6b136 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java @@ -0,0 +1,29 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.osgi.framework.ServiceReference; + +public interface ServiceProvider +{ + public boolean serviceAdded (ServiceReference ref, ContextHandler handler) throws Exception; + + public boolean serviceRemoved (ServiceReference ref, ContextHandler handler) throws Exception; +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java new file mode 100644 index 00000000000..e3f97f09132 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java @@ -0,0 +1,248 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + + + +/** + * ServiceWebAppProvider + * + * Jetty Provider that knows how to deploy a WebApp that has been registered as an OSGi service. + * + */ +public class ServiceWebAppProvider extends AbstractWebAppProvider implements ServiceProvider +{ + private static final Logger LOG = Log.getLogger(AbstractWebAppProvider.class); + + + /** + * Map of ServiceRef to App. Used when it is an osgi service that is a WebAppContext. + */ + private Map _serviceMap = new HashMap(); + + private ServiceRegistration _serviceRegForServices; + + + /** + * ServiceApp + * + * + */ + public class ServiceApp extends OSGiApp + { + + public ServiceApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String originId) + { + super(manager, provider, bundle, properties, originId); + } + + public ServiceApp(DeploymentManager manager, AppProvider provider, Bundle bundle, String originId) + { + super(manager, provider, bundle, originId); + } + + @Override + public void registerAsOSGiService() throws Exception + { + //not applicable for apps that are already services + } + + @Override + protected void deregisterAsOSGiService() throws Exception + { + //not applicable for apps that are already services + } + } + + + + /* ------------------------------------------------------------ */ + /** + * @param wrapper + */ + public ServiceWebAppProvider (ServerInstanceWrapper wrapper) + { + super(wrapper); + } + + + /* ------------------------------------------------------------ */ + /** + * A webapp that was deployed as an osgi service has been added, + * and we want to deploy it. + * + * @param context the webapp + */ + public boolean serviceAdded (ServiceReference serviceRef, ContextHandler context) + { + if (context == null || !(context instanceof WebAppContext)) + return false; + + String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return false; //this service represents a webapp that has already been registered as a service by another of our deployers + + + WebAppContext webApp = (WebAppContext)context; + Dictionary properties = new Hashtable(); + + String contextPath = (String)serviceRef.getProperty(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + if (contextPath == null) + contextPath = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); + if (contextPath == null) + return false; //No context path + + String base = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); + if (base == null) + base = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_WAR); + + if (base == null) + return false; //No webapp base + + String webdefaultXml = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH); + if (webdefaultXml == null) + webdefaultXml = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_DEFAULT_WEB_XML_PATH); + if (webdefaultXml != null) + properties.put(OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH, webdefaultXml); + + String webXml = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_WEB_XML_PATH); + if (webXml == null) + webXml = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_WEB_XML_PATH); + if (webXml != null) + properties.put(OSGiWebappConstants.JETTY_WEB_XML_PATH, webXml); + + String extraClassPath = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_EXTRA_CLASSPATH); + if (extraClassPath == null) + extraClassPath = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH); + if (extraClassPath != null) + properties.put(OSGiWebappConstants.JETTY_EXTRA_CLASSPATH, extraClassPath); + + String bundleInstallOverride = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE); + if (bundleInstallOverride == null) + bundleInstallOverride = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE); + if (bundleInstallOverride != null) + properties.put(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE, bundleInstallOverride); + + String requiredTlds = (String)serviceRef.getProperty(OSGiWebappConstants.REQUIRE_TLD_BUNDLE); + if (requiredTlds == null) + requiredTlds = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE); + if (requiredTlds != null) + properties.put(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requiredTlds); + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps()); + try + { + String originId = getOriginId(serviceRef.getBundle(), base); + ServiceApp app = new ServiceApp(getDeploymentManager(), this, serviceRef.getBundle(), properties, originId); + app.setContextPath(contextPath); + app.setWebAppPath(base); + app.setWebAppContext(webApp); //set the pre=made webapp instance + _serviceMap.put(serviceRef, app); + getDeploymentManager().addApp(app); + return true; + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + + + + /* ------------------------------------------------------------ */ + /** + * @param context the webapp + */ + public boolean serviceRemoved (ServiceReference serviceRef, ContextHandler context) + { + if (context == null || !(context instanceof WebAppContext)) + return false; + + String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return false; //this service represents a contexthandler that will be deregistered as a service by another of our deployers + + App app = _serviceMap.remove(serviceRef); + if (app != null) + { + getDeploymentManager().removeApp(app); + return true; + } + return false; + } + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + //register as an osgi service for deploying bundles, advertising the name of the jetty Server instance we are related to + Dictionary properties = new Hashtable(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, getServerInstanceWrapper().getManagedServerName()); + + //register as an osgi service for deploying contexts (discovered as osgi services), advertising the name of the jetty Server instance we are related to + _serviceRegForServices = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(ServiceProvider.class.getName(), this, properties); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + //unregister ourselves + if (_serviceRegForServices != null) + { + try + { + _serviceRegForServices.unregister(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + super.doStop(); + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java index 5624d07e62d..bef4dcf3731 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java @@ -28,6 +28,7 @@ import java.util.StringTokenizer; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.log.Log; @@ -36,6 +37,9 @@ import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; /** + * DefaultJettyAtJettyHomeHelper + * + * * Called by the {@link JettyBootstrapActivator} during the starting of the * bundle. If the system property 'jetty.home' is defined and points to a * folder, then setup the corresponding jetty server. @@ -45,53 +49,22 @@ public class DefaultJettyAtJettyHomeHelper private static final Logger LOG = Log.getLogger(DefaultJettyAtJettyHomeHelper.class); /** - * contains a comma separated list of pathes to the etc/jetty-*.xml files - * used to configure jetty. By default the value is 'etc/jetty.xml' when the - * path is relative the file is resolved relatively to jettyhome. + * contains a comma separated list of paths to the etc/jetty-*.xml files */ - public static final String SYS_PROP_JETTY_ETC_FILES = OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS; + public static final String JETTY_ETC_FILES = OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS; - /** - * Usual system property used as the hostname for a typical jetty - * configuration. - */ - public static final String SYS_PROP_JETTY_HOME = "jetty.home"; - - /** - * System property to point to a bundle that embeds a jetty configuration - * and that jetty configuration should be the default jetty server. First we - * look for jetty.home. If we don't find it then we look for this property. - */ - public static final String SYS_PROP_JETTY_HOME_BUNDLE = "jetty.home.bundle"; - - /** - * Usual system property used as the hostname for a typical jetty - * configuration. - */ - public static final String SYS_PROP_JETTY_HOST = "jetty.host"; - - /** - * Usual system property used as the port for http for a typical jetty - * configuration. - */ - public static final String SYS_PROP_JETTY_PORT = "jetty.port"; - - /** - * Usual system property used as the port for https for a typical jetty - * configuration. - */ - public static final String SYS_PROP_JETTY_PORT_SSL = "jetty.port.ssl"; - /** * Set of config files to apply to a jetty Server instance if none are supplied by SYS_PROP_JETTY_ETC_FILES */ - public static final String DEFAULT_JETTY_ETC_FILES = "jetty.xml,jetty-selector.xml,jetty-deployer.xml"; + public static final String DEFAULT_JETTY_ETC_FILES = "etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-deployer.xml"; /** * Default location within bundle of a jetty home dir. */ - public static final String DEFAULT_JETTYHOME = "/jettyhome"; - + public static final String DEFAULT_JETTYHOME = "/jettyhome/"; + + + /* ------------------------------------------------------------ */ /** * Called by the JettyBootStrapActivator. If the system property jetty.home * is defined and points to a folder, creates a corresponding jetty @@ -116,8 +89,8 @@ public class DefaultJettyAtJettyHomeHelper */ public static void startJettyAtJettyHome(BundleContext bundleContext) throws Exception { - String jettyHomeSysProp = System.getProperty(SYS_PROP_JETTY_HOME); - String jettyHomeBundleSysProp = System.getProperty(SYS_PROP_JETTY_HOME_BUNDLE); + String jettyHomeSysProp = System.getProperty(OSGiServerConstants.JETTY_HOME); + String jettyHomeBundleSysProp = System.getProperty(OSGiServerConstants.JETTY_HOME_BUNDLE); File jettyHome = null; Bundle jettyHomeBundle = null; if (jettyHomeSysProp != null) @@ -174,17 +147,18 @@ public class DefaultJettyAtJettyHomeHelper // these properties usually are the ones passed to this type of // configuration. - setProperty(properties, SYS_PROP_JETTY_HOME, System.getProperty(SYS_PROP_JETTY_HOME)); - setProperty(properties, SYS_PROP_JETTY_HOST, System.getProperty(SYS_PROP_JETTY_HOST)); - setProperty(properties, SYS_PROP_JETTY_PORT, System.getProperty(SYS_PROP_JETTY_PORT)); - setProperty(properties, SYS_PROP_JETTY_PORT_SSL, System.getProperty(SYS_PROP_JETTY_PORT_SSL)); + setProperty(properties, OSGiServerConstants.JETTY_HOME, System.getProperty(OSGiServerConstants.JETTY_HOME)); + setProperty(properties, OSGiServerConstants.JETTY_HOST, System.getProperty(OSGiServerConstants.JETTY_HOST)); + setProperty(properties, OSGiServerConstants.JETTY_PORT, System.getProperty(OSGiServerConstants.JETTY_PORT)); + setProperty(properties, OSGiServerConstants.JETTY_PORT_SSL, System.getProperty(OSGiServerConstants.JETTY_PORT_SSL)); //register the Server instance as an OSGi service. bundleContext.registerService(Server.class.getName(), server, properties); - // hookNestedConnectorToBridgeServlet(server); - } - + + + + /* ------------------------------------------------------------ */ /** * Minimum setup for the location of the configuration files given a * jettyhome folder. Reads the system property jetty.etc.config.urls and @@ -196,7 +170,7 @@ public class DefaultJettyAtJettyHomeHelper */ private static String getJettyConfigurationURLs(File jettyhome) { - String jettyetc = System.getProperty(SYS_PROP_JETTY_ETC_FILES, "etc/jetty.xml"); + String jettyetc = System.getProperty(JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES); StringTokenizer tokenizer = new StringTokenizer(jettyetc, ";,", false); StringBuilder res = new StringBuilder(); while (tokenizer.hasMoreTokens()) @@ -218,7 +192,9 @@ public class DefaultJettyAtJettyHomeHelper } return res.toString(); } - + + + /* ------------------------------------------------------------ */ /** * Minimum setup for the location of the configuration files given a * configuration embedded inside a bundle. Reads the system property @@ -230,7 +206,7 @@ public class DefaultJettyAtJettyHomeHelper */ private static String getJettyConfigurationURLs(Bundle configurationBundle) { - String files = System.getProperty(SYS_PROP_JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES); + String files = System.getProperty(JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES); StringTokenizer tokenizer = new StringTokenizer(files, ";,", false); StringBuilder res = new StringBuilder(); @@ -246,36 +222,39 @@ public class DefaultJettyAtJettyHomeHelper else { //relative file path - Enumeration enUrls = BundleFileLocatorHelper.DEFAULT.findEntries(configurationBundle, etcFile); - + Enumeration enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, etcFile); + // default for org.eclipse.osgi.boot where we look inside // jettyhome for the default embedded configuration. // default inside jettyhome. this way fragments to the bundle // can define their own configuration. if ((enUrls == null || !enUrls.hasMoreElements())) { - String tmp = DEFAULT_JETTYHOME+"/etc/"+etcFile; - enUrls = BundleFileLocatorHelper.DEFAULT.findEntries(configurationBundle, tmp); - LOG.info("Configuring jetty with the default embedded configuration:" + "bundle: " + String tmp = DEFAULT_JETTYHOME+etcFile; + enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, tmp); + LOG.info("Configuring jetty from bundle: " + configurationBundle.getSymbolicName() - + " config: "+tmp); + + " with "+tmp); } if (enUrls == null || !enUrls.hasMoreElements()) { - LOG.warn("Unable to locate a jetty configuration file for " + etcFile); + throw new IllegalStateException ("Unable to locate a jetty configuration file for " + etcFile); } if (enUrls != null) { while (enUrls.hasMoreElements()) { - appendToCommaSeparatedList(res, enUrls.nextElement().toString()); + URL url = BundleFileLocatorHelperFactory.getFactory().getHelper().getFileURL(enUrls.nextElement()); + appendToCommaSeparatedList(res, url.toString()); } } } } return res.toString(); } - + + + /* ------------------------------------------------------------ */ private static void appendToCommaSeparatedList(StringBuilder buffer, String value) { if (buffer.length() != 0) @@ -284,7 +263,9 @@ public class DefaultJettyAtJettyHomeHelper } buffer.append(value); } - + + + /* ------------------------------------------------------------ */ private static void setProperty(Dictionary properties, String key, String value) { if (value != null) @@ -292,7 +273,9 @@ public class DefaultJettyAtJettyHomeHelper properties.put(key, value); } } - + + + /* ------------------------------------------------------------ */ /** * recursively substitute the ${sysprop} by their actual system property. * ${sysprop,defaultvalue} will use 'defaultvalue' as the value if no diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java index 10f442f9c4d..a7e9daaa2ef 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java @@ -31,16 +31,24 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; +import org.eclipse.jetty.deploy.AppLifeCycle; import org.eclipse.jetty.deploy.AppProvider; import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.deploy.bindings.StandardStarter; +import org.eclipse.jetty.deploy.bindings.StandardStopper; +import org.eclipse.jetty.osgi.boot.BundleContextProvider; +import org.eclipse.jetty.osgi.boot.BundleWebAppProvider; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; -import org.eclipse.jetty.osgi.boot.OSGiAppProvider; +import org.eclipse.jetty.osgi.boot.OSGiDeployer; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.OSGiUndeployer; +import org.eclipse.jetty.osgi.boot.ServiceContextProvider; +import org.eclipse.jetty.osgi.boot.ServiceWebAppProvider; import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper; -import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleDeployerHelper; +import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer; import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; -import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.util.IO; @@ -66,6 +74,8 @@ public class ServerInstanceWrapper public static final String PROPERTY_THIS_JETTY_XML_FOLDER_URL = "this.jetty.xml.parent.folder.url"; private static Logger LOG = Log.getLogger(ServerInstanceWrapper.class.getName()); + + private final String _managedServerName; @@ -74,7 +84,7 @@ public class ServerInstanceWrapper */ private Server _server; - private ContextHandlerCollection _ctxtHandler; + private ContextHandlerCollection _ctxtCollection; /** * This is the class loader that should be the parent classloader of any @@ -84,21 +94,22 @@ public class ServerInstanceWrapper private ClassLoader _commonParentClassLoaderForWebapps; private DeploymentManager _deploymentManager; - - private OSGiAppProvider _provider; - - private WebBundleDeployerHelper _webBundleDeployerHelper; - + + + /* ------------------------------------------------------------ */ public ServerInstanceWrapper(String managedServerName) { _managedServerName = managedServerName; } + /* ------------------------------------------------------------ */ public String getManagedServerName() { return _managedServerName; } - + + + /* ------------------------------------------------------------ */ /** * The classloader that should be the parent classloader for each webapp * deployed on this server. @@ -109,7 +120,9 @@ public class ServerInstanceWrapper { return _commonParentClassLoaderForWebapps; } - + + + /* ------------------------------------------------------------ */ /** * @return The deployment manager registered on this server. */ @@ -117,33 +130,28 @@ public class ServerInstanceWrapper { return _deploymentManager; } - + + + /* ------------------------------------------------------------ */ /** * @return The app provider registered on this server. */ - public OSGiAppProvider getOSGiAppProvider() - { - return _provider; - } - public Server getServer() { return _server; } - public WebBundleDeployerHelper getWebBundleDeployerHelp() - { - return _webBundleDeployerHelper; - } - + /* ------------------------------------------------------------ */ /** * @return The collection of context handlers */ public ContextHandlerCollection getContextHandlerCollection() { - return _ctxtHandler; + return _ctxtCollection; } - + + + /* ------------------------------------------------------------ */ public void start(Server server, Dictionary props) throws Exception { _server = server; @@ -158,20 +166,20 @@ public class ServerInstanceWrapper List shared = sharedURLs != null ? extractFiles(sharedURLs) : null; libExtClassLoader = LibExtClassLoaderHelper.createLibExtClassLoader(shared, null, server, JettyBootstrapActivator.class.getClassLoader()); + if (LOG.isDebugEnabled()) LOG.debug("LibExtClassLoader = "+libExtClassLoader); + Thread.currentThread().setContextClassLoader(libExtClassLoader); configure(server, props); init(); - // now that we have an app provider we can call the registration - // customizer. - URL[] jarsWithTlds = getJarsWithTlds(); _commonParentClassLoaderForWebapps = jarsWithTlds == null ? libExtClassLoader : new TldLocatableURLClassloader(libExtClassLoader, jarsWithTlds); + + if (LOG.isDebugEnabled()) LOG.debug("common classloader = "+_commonParentClassLoaderForWebapps); server.start(); - _webBundleDeployerHelper = new WebBundleDeployerHelper(this); } catch (Exception e) { @@ -194,7 +202,7 @@ public class ServerInstanceWrapper } } - + /* ------------------------------------------------------------ */ public void stop() { try @@ -209,7 +217,9 @@ public class ServerInstanceWrapper LOG.warn(e); } } - + + + /* ------------------------------------------------------------ */ /** * TODO: right now only the jetty-jsp bundle is scanned for common taglibs. * Should support a way to plug more bundles that contain taglibs. @@ -233,11 +243,19 @@ public class ServerInstanceWrapper */ private URL[] getJarsWithTlds() throws Exception { + + //Jars that are added onto the equivalent of the container classpath are: + // jstl jars: identified by the class WhenTag (and the boot-bundle manifest imports the jstl packages + // bundles identified by System property org.eclipse.jetty.osgi.tldbundles + // bundle symbolic name patterns defined in the DeploymentManager + // + // Any bundles mentioned in the Require-TldBundle manifest header of the webapp bundle MUST ALSO HAVE Import-Bundle + // in order to get them onto the classpath of the webapp. + ArrayList res = new ArrayList(); - WebBundleDeployerHelper.staticInit();// that is not looking great. - for (WebappRegistrationCustomizer regCustomizer : WebBundleDeployerHelper.JSP_REGISTRATION_HELPERS) + for (WebappRegistrationCustomizer regCustomizer : WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS) { - URL[] urls = regCustomizer.getJarsWithTlds(_provider, WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER); + URL[] urls = regCustomizer.getJarsWithTlds(_deploymentManager, BundleFileLocatorHelperFactory.getFactory().getHelper()); for (URL url : urls) { if (!res.contains(url)) res.add(url); @@ -248,7 +266,9 @@ public class ServerInstanceWrapper else return null; } - + + + /* ------------------------------------------------------------ */ private void configure(Server server, Dictionary props) throws Exception { String jettyConfigurationUrls = (String) props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS); @@ -256,15 +276,20 @@ public class ServerInstanceWrapper if (jettyConfigurations == null || jettyConfigurations.isEmpty()) { return; } Map id_map = new HashMap(); - //TODO need to put in the id of the server being configured + //Put in a mapping for the id "Server" and the name of the server as the instance being configured id_map.put("Server", server); + id_map.put((String)props.get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME), server); + Map properties = new HashMap(); Enumeration en = props.keys(); while (en.hasMoreElements()) { Object key = en.nextElement(); Object value = props.get(key); - properties.put(String.valueOf(key), String.valueOf(value)); + String keyStr = String.valueOf(key); + String valStr = String.valueOf(value); + properties.put(keyStr, valStr); + server.setAttribute(keyStr, valStr); } for (URL jettyConfiguration : jettyConfigurations) @@ -274,6 +299,11 @@ public class ServerInstanceWrapper { // Execute a Jetty configuration file Resource r = Resource.newResource(jettyConfiguration); + if (!r.exists()) + { + LOG.warn("File does not exist "+r); + continue; + } is = r.getInputStream(); XmlConfiguration config = new XmlConfiguration(is); config.getIdMap().putAll(id_map); @@ -316,45 +346,97 @@ public class ServerInstanceWrapper * * It is assumed the server has already been configured with the ContextHandlerCollection structure. * - * The server must have an instance of OSGiAppProvider. If one is not provided, it is created. */ private void init() { // Get the context handler - _ctxtHandler = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class); + _ctxtCollection = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class); - // get a deployerManager + if (_ctxtCollection == null) + throw new IllegalStateException("ERROR: No ContextHandlerCollection configured in Server"); + + List providerClassNames = new ArrayList(); + + // get a deployerManager and some providers Collection deployers = _server.getBeans(DeploymentManager.class); if (deployers != null && !deployers.isEmpty()) { _deploymentManager = deployers.iterator().next(); - + for (AppProvider provider : _deploymentManager.getAppProviders()) { - if (provider instanceof OSGiAppProvider) - { - _provider = (OSGiAppProvider) provider; - break; - } + providerClassNames.add(provider.getClass().getName()); } - if (_provider == null) + } + else + { + //add some kind of default + _deploymentManager = new DeploymentManager(); + _deploymentManager.setContexts(_ctxtCollection); + _server.addBean(_deploymentManager); + } + + _deploymentManager.setUseStandardBindings(false); + List deploymentLifeCycleBindings = new ArrayList(); + deploymentLifeCycleBindings.add(new OSGiDeployer()); + deploymentLifeCycleBindings.add(new StandardStarter()); + deploymentLifeCycleBindings.add(new StandardStopper()); + deploymentLifeCycleBindings.add(new OSGiUndeployer()); + _deploymentManager.setLifeCycleBindings(deploymentLifeCycleBindings); + + if (!providerClassNames.contains(BundleWebAppProvider.class.getName())) + { + // create it on the fly with reasonable default values. + try { - // create it on the fly with reasonable default values. - try - { - _provider = new OSGiAppProvider(); - _provider.setMonitoredDirResource(Resource.newResource(getDefaultOSGiContextsHome(new File(System.getProperty("jetty.home"))).toURI())); - } - catch (IOException e) - { - LOG.warn(e); - } - _deploymentManager.addAppProvider(_provider); + BundleWebAppProvider webAppProvider = new BundleWebAppProvider(this); + _deploymentManager.addAppProvider(webAppProvider); + } + catch (Exception e) + { + LOG.warn(e); } } - if (_ctxtHandler == null || _provider == null) throw new IllegalStateException("ERROR: No ContextHandlerCollection or OSGiAppProvider configured"); + if (!providerClassNames.contains(ServiceWebAppProvider.class.getName())) + { + // create it on the fly with reasonable default values. + try + { + ServiceWebAppProvider webAppProvider = new ServiceWebAppProvider(this); + _deploymentManager.addAppProvider(webAppProvider); + } + catch (Exception e) + { + LOG.warn(e); + } + } + if (!providerClassNames.contains(BundleContextProvider.class.getName())) + { + try + { + BundleContextProvider contextProvider = new BundleContextProvider(this); + _deploymentManager.addAppProvider(contextProvider); + } + catch (Exception e) + { + LOG.warn(e); + } + } + + if (!providerClassNames.contains(ServiceContextProvider.class.getName())) + { + try + { + ServiceContextProvider contextProvider = new ServiceContextProvider(this); + _deploymentManager.addAppProvider(contextProvider); + } + catch (Exception e) + { + LOG.warn(e); + } + } } /** @@ -381,10 +463,6 @@ public class ServerInstanceWrapper return new File(jettyHome, "/contexts"); } - File getOSGiContextsHome() - { - return _provider.getContextXmlDirAsFile(); - } /** * @return the urls in this string. @@ -398,7 +476,7 @@ public class ServerInstanceWrapper String tok = tokenizer.nextToken(); try { - urls.add(((DefaultFileLocatorHelper) WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER).getLocalURL(new URL(tok))); + urls.add(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(new URL(tok))); } catch (Throwable mfe) { @@ -422,7 +500,7 @@ public class ServerInstanceWrapper try { URL url = new URL(tok); - url = ((DefaultFileLocatorHelper) WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER).getFileURL(url); + url = BundleFileLocatorHelperFactory.getFactory().getHelper().getFileURL(url); if (url.getProtocol().equals("file")) { Resource res = Resource.newResource(url); diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java new file mode 100644 index 00000000000..197008d8f07 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java @@ -0,0 +1,58 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.webapp; + +import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * BundleFileLocatorHelperFactory + * + * Obtain a helper for locating files based on the bundle. + */ +public class BundleFileLocatorHelperFactory +{ + private static final Logger LOG = Log.getLogger(BundleFileLocatorHelperFactory.class); + + private static BundleFileLocatorHelperFactory _instance = new BundleFileLocatorHelperFactory(); + + private BundleFileLocatorHelperFactory() {} + + public static BundleFileLocatorHelperFactory getFactory() + { + return _instance; + } + + public BundleFileLocatorHelper getHelper() + { + BundleFileLocatorHelper helper = BundleFileLocatorHelper.DEFAULT; + try + { + //see if a fragment has supplied an alternative + helper = (BundleFileLocatorHelper) Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance(); + } + catch (Throwable t) + { + LOG.ignore(t); + } + return helper; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java index 502d972783e..1c5cd3e6c0a 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.osgi.boot.internal.webapp; + import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.webapp.WebAppContext; import org.osgi.framework.Bundle; diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java index 00dea2dfb71..6bd352da2ae 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java @@ -20,19 +20,25 @@ package org.eclipse.jetty.osgi.boot.internal.webapp; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import org.eclipse.jetty.osgi.boot.BundleWebAppProvider; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.osgi.boot.ServiceProvider; import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper; import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry; import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.webapp.WebAppContext; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -40,107 +46,83 @@ import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; /** - * When a {@link ContextHandler} service is activated we look into it and if the - * corresponding webapp is actually not configured then we go and register it. - *

- * The idea is to always go through this class when we deploy a new webapp on - * jetty. - *

- *

- * We are exposing each web-application as an OSGi service. This lets us update - * the webapps and stop/start them directly at the OSGi layer. It also give us - * many ways to declare those services: Declarative Services for example.
- * It is a bit different from the way the HttpService works where we would have - * a WebappService and we woud register a webapp onto it.
- * It does not go against RFC-66 nor does it prevent us from supporting the - * WebappContainer semantics. - *

+ * JettyContextHandlerServiceTracker + * + * When a {@link ContextHandler} is activated as an osgi service we find a jetty deployer + * for it. The ContextHandler could be either a WebAppContext or any other derivative of + * ContextHandler. + * + * ContextHandlers and WebApps can also be deployed into jetty without creating them as + * osgi services. Instead, they can be deployed via manifest headers inside bundles. See + * {@link WebBundleTrackerCustomizer}. */ public class JettyContextHandlerServiceTracker implements ServiceListener { - private static Logger __logger = Log.getLogger(WebBundleDeployerHelper.class.getName()); - - /** New style: ability to manage multiple jetty instances */ - private final IManagedJettyServerRegistry _registry; - - /** The context-handler to deactivate indexed by context handler */ - private Map _indexByServiceReference = new HashMap(); - - /** - * The index is the bundle-symbolic-name/path/to/context/file when there is - * such thing - */ - private Map _indexByContextFile = new HashMap(); - - /** in charge of detecting changes in the osgi contexts home folder. */ - private Scanner _scanner; + private static Logger LOG = Log.getLogger(JettyContextHandlerServiceTracker.class); + + public static final String FILTER = "(objectclass=" + ServiceProvider.class.getName() + ")"; + + //track all instances of deployers of webapps as bundles + ServiceTracker _serviceTracker; + + + + /* ------------------------------------------------------------ */ /** * @param registry */ - public JettyContextHandlerServiceTracker(IManagedJettyServerRegistry registry) throws Exception + public JettyContextHandlerServiceTracker() throws Exception { - _registry = registry; + //track all instances of deployers of webapps + Bundle myBundle = FrameworkUtil.getBundle(this.getClass()); + _serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null); + _serviceTracker.open(); } - public void stop() throws Exception - { - if (_scanner != null) - { - _scanner.stop(); - } - // the class that created the server is also in charge of stopping it. - // nothing to stop in the WebappRegistrationHelper - - } + + /* ------------------------------------------------------------ */ /** - * @param contextHome Parent folder where the context files can override the - * context files defined in the web bundles: equivalent to the - * contexts folder in a traditional jetty installation. when - * null, just do nothing. + * @param managedServerName + * @return */ - protected void setupContextHomeScanner(File contextHome) throws IOException + public Map getDeployers(String managedServerName) { - if (contextHome == null) { return; } - final String osgiContextHomeFolderCanonicalPath = contextHome.getCanonicalPath(); - _scanner = new Scanner(); - _scanner.setRecursive(true); - _scanner.setReportExistingFilesOnStartup(false); - _scanner.addListener(new Scanner.DiscreteListener() + if (managedServerName == null) + managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME; + + Map candidates = new HashMap(); + + ServiceReference[] references = _serviceTracker.getServiceReferences(); + if (references != null) { - public void fileAdded(String filename) throws Exception + for (ServiceReference ref:references) { - // adding a file does not create a new app, - // it just reloads it with the new custom file. - // well, if the file does not define a context handler, - // then in fact it does remove it. - reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); + String name = (String)ref.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + if (managedServerName.equalsIgnoreCase(name)) + { + ServiceProvider candidate = (ServiceProvider)_serviceTracker.getService(ref); + if (candidate != null) + candidates.put(ref, candidate); + } } - - public void fileChanged(String filename) throws Exception - { - reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); - } - - public void fileRemoved(String filename) throws Exception - { - // removing a file does not remove the app: - // it just goes back to the default embedded in the bundle. - // well, if there was no default then it does remove it. - reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); - } - }); - + } + return candidates; } + /* ------------------------------------------------------------ */ /** * Receives notification that a service has had a lifecycle change. * * @param ev The ServiceEvent object. */ + /** + * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent) + */ public void serviceChanged(ServiceEvent ev) { ServiceReference sr = ev.getServiceReference(); @@ -149,16 +131,31 @@ public class JettyContextHandlerServiceTracker implements ServiceListener case ServiceEvent.MODIFIED: case ServiceEvent.UNREGISTERING: { - ContextHandler ctxtHandler = unregisterInIndex(ev.getServiceReference()); - if (ctxtHandler != null && !ctxtHandler.isStopped()) + BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext(); + ContextHandler contextHandler = (ContextHandler) context.getService(sr); + + //if this was not a service that another of our deployers may have deployed (in which case they will undeploy it) + String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK); + + //Get a jetty deployer targetted to the named server instance, or the default one if not named + //The individual deployer will decide if it can remove the context or not + String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + Map candidates = getDeployers(serverName); + if (candidates != null) { - try + boolean removed = false; + Iterator> itor = candidates.entrySet().iterator(); + while (!removed && itor.hasNext()) { - getWebBundleDeployerHelp(sr).unregister(ctxtHandler); - } - catch (Exception e) - { - __logger.warn(e); + Entry e = itor.next(); + try + { + removed = e.getValue().serviceRemoved(sr, contextHandler); + } + catch (Exception x) + { + LOG.warn("Error undeploying service representing jetty context ", x); + } } } } @@ -181,210 +178,34 @@ public class JettyContextHandlerServiceTracker implements ServiceListener // is configured elsewhere. return; } - String contextFilePath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); - if (contextHandler instanceof WebAppContext && contextFilePath == null) - // it could be a web-application that will in fact be configured - // via a context file. - // that case is identified by the fact that the contextFilePath - // is not null - // in that case we must use the register context methods. + String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return; //one of our deployers just registered the context as an OSGi service, so we can ignore it + + //Get a jetty deployer targetted to the named server instance, or the default one if not named + String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + Map candidates = getDeployers(serverName); + if (candidates != null) { - WebAppContext webapp = (WebAppContext) contextHandler; - String contextPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); - if (contextPath == null) + boolean added = false; + Iterator> itor = candidates.entrySet().iterator(); + while (!added && itor.hasNext()) { - contextPath = webapp.getContextPath(); - } - String webXmlPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WEB_XML_PATH); - if (webXmlPath == null) - { - webXmlPath = webapp.getDescriptor(); - } - String defaultWebXmlPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_DEFAULT_WEB_XML_PATH); - if (defaultWebXmlPath == null) - { - String jettyHome = System.getProperty(DefaultJettyAtJettyHomeHelper.SYS_PROP_JETTY_HOME); - if (jettyHome != null) + Entry e = itor.next(); + try { - File etc = new File(jettyHome, "etc"); - if (etc.exists() && etc.isDirectory()) - { - File webDefault = new File(etc, "webdefault.xml"); - if (webDefault.exists()) - defaultWebXmlPath = webDefault.getAbsolutePath(); - else - defaultWebXmlPath = webapp.getDefaultsDescriptor(); - } - else - defaultWebXmlPath = webapp.getDefaultsDescriptor(); + added = e.getValue().serviceAdded(sr, contextHandler); + if (added && LOG.isDebugEnabled()) + LOG.debug("Provider "+e.getValue()+" deployed "+contextHandler); } - } - String war = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WAR); - try - { - IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr); - if (deployerHelper == null) + catch (Exception x) { - + LOG.warn("Error deploying service representing jetty context", x); } - else - { - WebAppContext handler = deployerHelper.registerWebapplication(contributor, - war, - contextPath, - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH), - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE), - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE), - webXmlPath, defaultWebXmlPath, webapp); - if (handler != null) - { - registerInIndex(handler, sr); - } - } - } - catch (Throwable e) - { - __logger.warn(e); - } - } - else - { - // consider this just an empty skeleton: - if (contextFilePath == null) { throw new IllegalArgumentException("the property contextFilePath is required"); } - try - { - IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr); - if (deployerHelper == null) - { - // more warnings? - } - else - { - if (Boolean.TRUE.toString().equals(sr.getProperty(IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE))) - { - contextHandler = null; - } - ContextHandler handler = deployerHelper.registerContext(contributor, - contextFilePath, - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH), - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE), - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE), - contextHandler); - if (handler != null) - { - registerInIndex(handler, sr); - } - } - } - catch (Throwable e) - { - __logger.warn(e); } } + break; } - break; } } - - private void registerInIndex(ContextHandler handler, ServiceReference sr) - { - _indexByServiceReference.put(sr, handler); - String key = getSymbolicNameAndContextFileKey(sr); - if (key != null) - { - _indexByContextFile.put(key, sr); - } - } - - /** - * Returns the ContextHandler to stop. - * - * @param reg - * @return the ContextHandler to stop. - */ - private ContextHandler unregisterInIndex(ServiceReference sr) - { - ContextHandler handler = _indexByServiceReference.remove(sr); - String key = getSymbolicNameAndContextFileKey(sr); - if (key != null) - { - _indexByContextFile.remove(key); - } - if (handler == null) - { - // a warning? - return null; - } - return handler; - } - - /** - * @param sr - * @return The key for a context file within the osgi contexts home folder. - */ - private String getSymbolicNameAndContextFileKey(ServiceReference sr) - { - String contextFilePath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); - if (contextFilePath != null) { return sr.getBundle().getSymbolicName() + "/" + contextFilePath; } - return null; - } - - /** - * Called by the scanner when one of the context files is changed. - * - * @param contextFileFully - */ - public void reloadJettyContextHandler(String canonicalNameOfFileChanged, String osgiContextHomeFolderCanonicalPath) - { - String key = getNormalizedRelativePath(canonicalNameOfFileChanged, osgiContextHomeFolderCanonicalPath); - if (key == null) { return; } - ServiceReference sr = _indexByContextFile.get(key); - if (sr == null) - { - // nothing to do? - return; - } - serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, sr)); - } - - /** - * @param canFilename - * @return - */ - private String getNormalizedRelativePath(String canFilename, String osgiContextHomeFolderCanonicalPath) - { - if (!canFilename.startsWith(osgiContextHomeFolderCanonicalPath)) - { - // why are we here: this does not look like a child of the osgi - // contexts home. - // warning? - return null; - } - return canFilename.substring(osgiContextHomeFolderCanonicalPath.length()).replace('\\', '/'); - } - - /** - * @return The server on which this webapp is meant to be deployed - */ - private ServerInstanceWrapper getServerInstanceWrapper(String managedServerName) - { - if (_registry == null) { return null; } - if (managedServerName == null) - { - managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME; - } - ServerInstanceWrapper wrapper = _registry.getServerInstanceWrapper(managedServerName); - // System.err.println("Returning " + managedServerName + " = " + - // wrapper); - return wrapper; - } - - private IWebBundleDeployerHelper getWebBundleDeployerHelp(ServiceReference sr) - { - if (_registry == null) { return null; } - String managedServerName = (String) sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); - ServerInstanceWrapper wrapper = getServerInstanceWrapper(managedServerName); - return wrapper != null ? wrapper.getWebBundleDeployerHelp() : null; - } - } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java index 77b07a719d0..5f7e6443d95 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java @@ -34,6 +34,7 @@ import java.util.jar.JarFile; import javax.servlet.http.HttpServlet; import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; +import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperFactory; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -86,12 +87,12 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe * @param contributor The bundle that defines this web-application. * @throws IOException */ - public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor, BundleClassLoaderHelper bundleClassLoaderHelper) + public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor) throws IOException { super(parent, context); _contributor = contributor; - _osgiBundleClassLoader = bundleClassLoaderHelper.getBundleClassLoader(contributor); + _osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(contributor); } /** diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java deleted file mode 100644 index 7ed6b7e81ef..00000000000 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java +++ /dev/null @@ -1,909 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.osgi.boot.internal.webapp; - - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.TreeMap; - -import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; -import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; -import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; -import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; -import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; -import org.eclipse.jetty.osgi.boot.utils.internal.DefaultBundleClassLoaderHelper; -import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; -import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.resource.ResourceCollection; -import org.eclipse.jetty.webapp.FragmentConfiguration; -import org.eclipse.jetty.webapp.TagLibConfiguration; -import org.eclipse.jetty.webapp.WebAppContext; -import org.eclipse.jetty.webapp.WebInfConfiguration; -import org.eclipse.jetty.xml.XmlConfiguration; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleReference; -import org.osgi.service.packageadmin.PackageAdmin; -import org.osgi.util.tracker.ServiceTracker; -import org.xml.sax.SAXException; - -/** - * Bridges the jetty deployers with the OSGi lifecycle where applications are - * managed inside OSGi-bundles. - *

- * This class should be called as a consequence of the activation of a new - * service that is a ContextHandler.
- * This way the new webapps are exposed as OSGi services. - *

- *

- * Helper methods to register a bundle that is a web-application or a context. - *

- * Limitations: - *
    - *
  • support for jarred webapps is somewhat limited.
  • - *
- */ -public class WebBundleDeployerHelper implements IWebBundleDeployerHelper -{ - - private static Logger __logger = Log.getLogger(WebBundleDeployerHelper.class.getName()); - - private static boolean INITIALIZED = false; - - /** - * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports - * equinox and apache-felix fragment bundles that are specific to an OSGi - * implementation should set a different implementation. - */ - public static BundleClassLoaderHelper BUNDLE_CLASS_LOADER_HELPER = null; - - /** - * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports - * equinox and apache-felix fragment bundles that are specific to an OSGi - * implementation should set a different implementation. - */ - public static BundleFileLocatorHelper BUNDLE_FILE_LOCATOR_HELPER = null; - - /** - * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports - * equinox and apache-felix fragment bundles that are specific to an OSGi - * implementation should set a different implementation. - *

- * Several of those objects can be added here: For example we could have an - * optional fragment that setups a specific implementation of JSF for the - * whole of jetty-osgi. - *

- */ - public static Collection JSP_REGISTRATION_HELPERS = new ArrayList(); - - /** - * this class loader loads the jars inside {$jetty.home}/lib/ext it is meant - * as a migration path and for jars that are not OSGi ready. also gives - * access to the jsp jars. - */ - // private URLClassLoader _libExtClassLoader; - - private ServerInstanceWrapper _wrapper; - - public WebBundleDeployerHelper(ServerInstanceWrapper wrapper) - { - staticInit(); - _wrapper = wrapper; - } - - // Inject the customizing classes that might be defined in fragment bundles. - public static synchronized void staticInit() - { - if (!INITIALIZED) - { - INITIALIZED = true; - // setup the custom BundleClassLoaderHelper - try - { - BUNDLE_CLASS_LOADER_HELPER = (BundleClassLoaderHelper) Class.forName(BundleClassLoaderHelper.CLASS_NAME).newInstance(); - } - catch (Throwable t) - { - // System.err.println("support for equinox and felix"); - BUNDLE_CLASS_LOADER_HELPER = new DefaultBundleClassLoaderHelper(); - } - // setup the custom FileLocatorHelper - try - { - BUNDLE_FILE_LOCATOR_HELPER = (BundleFileLocatorHelper) Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance(); - } - catch (Throwable t) - { - // System.err.println("no jsp/jasper support"); - BUNDLE_FILE_LOCATOR_HELPER = new DefaultFileLocatorHelper(); - } - } - } - - /** - * Deploy a new web application on the jetty server. - * - * @param bundle The bundle - * @param webappFolderPath The path to the root of the webapp. Must be a - * path relative to bundle; either an absolute path. - * @param contextPath The context path. Must start with "/" - * @param extraClasspath - * @param overrideBundleInstallLocation - * @param requireTldBundle The list of bundles's symbolic names that contain - * tld files that are required by this WAB. - * @param webXmlPath - * @param defaultWebXmlPath TODO: parameter description - * @return The contexthandler created and started - * @throws Exception - */ - public WebAppContext registerWebapplication(Bundle bundle, String webappFolderPath, String contextPath, String extraClasspath, - String overrideBundleInstallLocation, String requireTldBundle, String webXmlPath, String defaultWebXmlPath, - WebAppContext webAppContext) - throws Exception - { - File bundleInstall = overrideBundleInstallLocation == null ? BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(bundle) : new File(overrideBundleInstallLocation); - File webapp = null; - URL baseWebappInstallURL = null; - - if (webappFolderPath != null && webappFolderPath.length() != 0 && !webappFolderPath.equals(".")) - { - if (webappFolderPath.startsWith("/") || webappFolderPath.startsWith("file:")) - { - webapp = new File(webappFolderPath); - } - else if (bundleInstall != null && bundleInstall.isDirectory()) - { - webapp = new File(bundleInstall, webappFolderPath); - } - else if (bundleInstall != null) - { - Enumeration urls = BUNDLE_FILE_LOCATOR_HELPER.findEntries(bundle, webappFolderPath); - if (urls != null && urls.hasMoreElements()) - { - baseWebappInstallURL = urls.nextElement(); - } - } - } - else - { - webapp = bundleInstall; - } - if (baseWebappInstallURL == null && (webapp == null || !webapp.exists())) - { - throw new IllegalArgumentException("Unable to locate " + webappFolderPath - + " inside " - + (bundleInstall != null ? bundleInstall.getAbsolutePath() : "unlocated bundle '" + bundle.getSymbolicName()+ "'")); - } - if (baseWebappInstallURL == null && webapp != null) - { - baseWebappInstallURL = webapp.toURI().toURL(); - } - return registerWebapplication(bundle, webappFolderPath, baseWebappInstallURL, contextPath, extraClasspath, bundleInstall, requireTldBundle, webXmlPath, - defaultWebXmlPath, webAppContext); - } - - /* - * (non-Javadoc) - * - * @see - * org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper# - * registerWebapplication(org.osgi.framework.Bundle, java.lang.String, - * java.io.File, java.lang.String, java.lang.String, java.io.File, - * java.lang.String, java.lang.String) - */ - private WebAppContext registerWebapplication(Bundle contributor, String pathInBundleToWebApp, URL baseWebappInstallURL, String contextPath, - String extraClasspath, File bundleInstall, String requireTldBundle, String webXmlPath, - String defaultWebXmlPath, WebAppContext context) - throws Exception - { - ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); - String[] oldServerClasses = null; - - try - { - - // apply any META-INF/context.xml file that is found to configure - // the webapp first - applyMetaInfContextXml(contributor, context); - - // make sure we provide access to all the jetty bundles by going - // through this bundle. - OSGiWebappClassLoader composite = createWebappClassLoader(contributor); - // configure with access to all jetty classes and also all the - // classes - // that the contributor gives access to. - Thread.currentThread().setContextClassLoader(composite); - - // converts bundleentry: protocol - baseWebappInstallURL = DefaultFileLocatorHelper.getLocalURL(baseWebappInstallURL); - - context.setWar(baseWebappInstallURL.toString()); - context.setContextPath(contextPath); - context.setExtraClasspath(extraClasspath); - - if (webXmlPath != null && webXmlPath.length() != 0) - { - File webXml = null; - if (webXmlPath.startsWith("/") || webXmlPath.startsWith("file:/")) - { - webXml = new File(webXmlPath); - } - else - { - webXml = new File(bundleInstall, webXmlPath); - } - if (webXml.exists()) - { - context.setDescriptor(webXml.getAbsolutePath()); - } - } - - if (defaultWebXmlPath == null || defaultWebXmlPath.length() == 0) - { - // use the one defined by the OSGiAppProvider. - defaultWebXmlPath = _wrapper.getOSGiAppProvider().getDefaultsDescriptor(); - } - if (defaultWebXmlPath != null && defaultWebXmlPath.length() != 0) - { - File defaultWebXml = null; - if (defaultWebXmlPath.startsWith("/") || defaultWebXmlPath.startsWith("file:/")) - { - defaultWebXml = new File(defaultWebXmlPath); - } - else - { - defaultWebXml = new File(bundleInstall, defaultWebXmlPath); - } - if (defaultWebXml.exists()) - { - context.setDefaultsDescriptor(defaultWebXml.getAbsolutePath()); - } - } - - // other parameters that might be defines on the OSGiAppProvider: - context.setParentLoaderPriority(_wrapper.getOSGiAppProvider().isParentLoaderPriority()); - - configureWebappClassLoader(contributor, context, composite, requireTldBundle); - configureWebAppContext(context, contributor, requireTldBundle); - - // @see - // org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext) - // during initialization of the webapp all the jetty packages are - // visible - // through the webapp classloader. - oldServerClasses = context.getServerClasses(); - context.setServerClasses(null); - - _wrapper.getOSGiAppProvider().addContext(contributor, pathInBundleToWebApp, context); - - // support for patch resources. ideally this should be done inside a - // configurator. - List patchResources = (List) context.getAttribute(WebInfConfiguration.RESOURCE_URLS + ".patch"); - if (patchResources != null) - { - LinkedList resourcesPath = new LinkedList(); - // place the patch resources at the beginning of the lookup - // path. - resourcesPath.addAll(patchResources); - // then place the ones from the host web bundle. - Resource hostResources = context.getBaseResource(); - if (hostResources instanceof ResourceCollection) - { - for (Resource re : ((ResourceCollection) hostResources).getResources()) - { - resourcesPath.add(re); - } - } - else - { - resourcesPath.add(hostResources); - } - - ResourceCollection rc = new ResourceCollection(resourcesPath.toArray(new Resource[resourcesPath.size()])); - context.setBaseResource(rc); - } - - return context; - } - finally - { - if (context != null && oldServerClasses != null) - { - context.setServerClasses(oldServerClasses); - } - Thread.currentThread().setContextClassLoader(contextCl); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper# - * unregister(org.eclipse.jetty.server.handler.ContextHandler) - */ - public void unregister(ContextHandler contextHandler) - throws Exception - { - _wrapper.getOSGiAppProvider().removeContext(contextHandler); - } - - /* - * (non-Javadoc) - * - * @see - * org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper# - * registerContext(org.osgi.framework.Bundle, java.lang.String, - * java.lang.String, java.lang.String) - */ - public ContextHandler registerContext(Bundle contributor, String contextFileRelativePath, String extraClasspath, String overrideBundleInstallLocation, - String requireTldBundle, ContextHandler handler) - throws Exception - { - File contextsHome = _wrapper.getOSGiAppProvider().getContextXmlDirAsFile(); - if (contextsHome != null) - { - File prodContextFile = new File(contextsHome, contributor.getSymbolicName() + "/" + contextFileRelativePath); - if (prodContextFile.exists()) { return registerContext(contributor, contextFileRelativePath, prodContextFile, extraClasspath, - overrideBundleInstallLocation, requireTldBundle, handler); } - } - File rootFolder = overrideBundleInstallLocation != null ? Resource.newResource(overrideBundleInstallLocation).getFile() : BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(contributor); - File contextFile = rootFolder != null ? new File(rootFolder, contextFileRelativePath) : null; - if (contextFile != null && contextFile.exists()) - { - return registerContext(contributor, contextFileRelativePath, contextFile, extraClasspath, overrideBundleInstallLocation, requireTldBundle, handler); - } - else - { - if (contextFileRelativePath.startsWith("./")) - { - contextFileRelativePath = contextFileRelativePath.substring(1); - } - if (!contextFileRelativePath.startsWith("/")) - { - contextFileRelativePath = "/" + contextFileRelativePath; - } - - URL contextURL = contributor.getEntry(contextFileRelativePath); - if (contextURL != null) - { - Resource r = Resource.newResource(contextURL); - return registerContext(contributor, contextFileRelativePath, r.getInputStream(), extraClasspath, overrideBundleInstallLocation, - requireTldBundle, handler); - } - throw new IllegalArgumentException("Could not find the context " + "file " - + contextFileRelativePath - + " for the bundle " - + contributor.getSymbolicName() - + (overrideBundleInstallLocation != null ? " using the install location " + overrideBundleInstallLocation : "")); - } - } - - /** - * This type of registration relies on jetty's complete context xml file. - * Context encompasses jndi and all other things. This makes the definition - * of the webapp a lot more self-contained. - * - * @param webapp - * @param contextPath - * @param classInBundle - * @throws Exception - */ - private ContextHandler registerContext(Bundle contributor, String pathInBundle, File contextFile, String extraClasspath, - String overrideBundleInstallLocation, String requireTldBundle, ContextHandler handler) - throws Exception - { - InputStream contextFileInputStream = null; - try - { - contextFileInputStream = new BufferedInputStream(new FileInputStream(contextFile)); - return registerContext(contributor, pathInBundle, contextFileInputStream, extraClasspath, overrideBundleInstallLocation, requireTldBundle, handler); - } - finally - { - IO.close(contextFileInputStream); - } - } - - /** - * @param contributor - * @param contextFileInputStream - * @return The ContextHandler created and registered or null if it did not - * happen. - * @throws Exception - */ - private ContextHandler registerContext(Bundle contributor, String pathInsideBundle, InputStream contextFileInputStream, String extraClasspath, - String overrideBundleInstallLocation, String requireTldBundle, ContextHandler handler) - throws Exception - { - ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); - String[] oldServerClasses = null; - WebAppContext webAppContext = null; - try - { - // make sure we provide access to all the jetty bundles by going - // through this bundle. - OSGiWebappClassLoader composite = createWebappClassLoader(contributor); - // configure with access to all jetty classes and also all the - // classes - // that the contributor gives access to. - Thread.currentThread().setContextClassLoader(composite); - ContextHandler context = createContextHandler(handler, contributor, contextFileInputStream, extraClasspath, overrideBundleInstallLocation, - requireTldBundle); - if (context == null) { return null;// did not happen - } - - // ok now register this webapp. we checked when we started jetty - // that there - // was at least one such handler for webapps. - // the actual registration must happen via the new Deployment API. - // _ctxtHandler.addHandler(context); - - configureWebappClassLoader(contributor, context, composite, requireTldBundle); - if (context instanceof WebAppContext) - { - webAppContext = (WebAppContext) context; - // @see - // org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext) - oldServerClasses = webAppContext.getServerClasses(); - webAppContext.setServerClasses(null); - } - _wrapper.getOSGiAppProvider().addContext(contributor, pathInsideBundle, context); - return context; - } - finally - { - if (webAppContext != null) - { - webAppContext.setServerClasses(oldServerClasses); - } - Thread.currentThread().setContextClassLoader(contextCl); - } - - } - - /** - * Applies the properties of WebAppDeployer as defined in jetty.xml. - * - * @see {WebAppDeployer#scan} around the comment - * // configure it - */ - protected void configureWebAppContext(ContextHandler wah, Bundle contributor, String requireTldBundle) - throws IOException - { - // rfc66 - wah.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT, contributor.getBundleContext()); - - // spring-dm-1.2.1 looks for the BundleContext as a different attribute. - // not a spec... but if we want to support - // org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext - // then we need to do this to: - wah.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), contributor.getBundleContext()); - - // also pass the bundle directly. sometimes a bundle does not have a - // bundlecontext. - // it is still useful to have access to the Bundle from the servlet - // context. - wah.setAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE, contributor); - - // pass the value of the require tld bundle so that the - // TagLibOSGiConfiguration - // can pick it up. - wah.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundle); - - Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles(contributor); - if (fragments != null && fragments.length != 0) - { - // sorted extra resource base found in the fragments. - // the resources are either overriding the resourcebase found in the - // web-bundle - // or appended. - // amongst each resource we sort them according to the alphabetical - // order - // of the name of the internal folder and the symbolic name of the - // fragment. - // this is useful to make sure that the lookup path of those - // resource base defined by fragments is always the same. - // This natural order could be abused to define the order in which - // the base resources are - // looked up. - TreeMap patchResourcesPath = new TreeMap(); - TreeMap appendedResourcesPath = new TreeMap(); - for (Bundle frag : fragments) - { - String fragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_FRAGMENT_FOLDER_PATH); - String patchFragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH); - if (fragFolder != null) - { - URL fragUrl = frag.getEntry(fragFolder); - if (fragUrl == null) { throw new IllegalArgumentException("Unable to locate " + fragFolder - + " inside " - + " the fragment '" - + frag.getSymbolicName() - + "'"); } - fragUrl = DefaultFileLocatorHelper.getLocalURL(fragUrl); - String key = fragFolder.startsWith("/") ? fragFolder.substring(1) : fragFolder; - appendedResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(fragUrl)); - } - if (patchFragFolder != null) - { - URL patchFragUrl = frag.getEntry(patchFragFolder); - if (patchFragUrl == null) { throw new IllegalArgumentException("Unable to locate " + patchFragUrl - + " inside " - + " the fragment '" - + frag.getSymbolicName() - + "'"); } - patchFragUrl = DefaultFileLocatorHelper.getLocalURL(patchFragUrl); - String key = patchFragFolder.startsWith("/") ? patchFragFolder.substring(1) : patchFragFolder; - patchResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(patchFragUrl)); - } - } - if (!appendedResourcesPath.isEmpty()) - { - wah.setAttribute(WebInfConfiguration.RESOURCE_URLS, new ArrayList(appendedResourcesPath.values())); - } - if (!patchResourcesPath.isEmpty()) - { - wah.setAttribute(WebInfConfiguration.RESOURCE_URLS + ".patch", new ArrayList(patchResourcesPath.values())); - } - - if (wah instanceof WebAppContext) - { - // This is the equivalent of what MetaInfConfiguration does. For - // OSGi bundles without the JarScanner - WebAppContext webappCtxt = (WebAppContext) wah; - // take care of the web-fragments, meta-inf resources and tld - // resources: - // similar to what MetaInfConfiguration does. - List frags = (List) wah.getAttribute(FragmentConfiguration.FRAGMENT_RESOURCES); - List resfrags = (List) wah.getAttribute(WebInfConfiguration.RESOURCE_URLS); - List tldfrags = (List) wah.getAttribute(TagLibConfiguration.TLD_RESOURCES); - for (Bundle frag : fragments) - { - URL webFrag = frag.getEntry("/META-INF/web-fragment.xml"); - Enumeration resEnum = frag.findEntries("/META-INF/resources", "*", true); - Enumeration tldEnum = frag.findEntries("/META-INF", "*.tld", false); - if (webFrag != null || (resEnum != null && resEnum.hasMoreElements()) || (tldEnum != null && tldEnum.hasMoreElements())) - { - try - { - File fragFile = BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(frag); - // add it to the webinf jars collection: - // no need to check that it was not there yet: it - // was not there yet for sure. - Resource fragFileAsResource = Resource.newResource(fragFile.toURI()); - webappCtxt.getMetaData().addWebInfJar(fragFileAsResource); - - if (webFrag != null) - { - if (frags == null) - { - frags = new ArrayList(); - wah.setAttribute(FragmentConfiguration.FRAGMENT_RESOURCES, frags); - } - frags.add(fragFileAsResource); - } - if (resEnum != null && resEnum.hasMoreElements()) - { - URL resourcesEntry = frag.getEntry("/META-INF/resources/"); - if (resourcesEntry == null) - { - // probably we found some fragments to a - // bundle. - // those are already contributed. - // so we skip this. - } - else - { - if (resfrags == null) - { - resfrags = new ArrayList(); - wah.setAttribute(WebInfConfiguration.RESOURCE_URLS, resfrags); - } - resfrags.add(Resource.newResource(DefaultFileLocatorHelper.getLocalURL(resourcesEntry))); - } - } - if (tldEnum != null && tldEnum.hasMoreElements()) - { - if (tldfrags == null) - { - tldfrags = new ArrayList(); - wah.setAttribute(TagLibConfiguration.TLD_RESOURCES, tldfrags); - } - while (tldEnum.hasMoreElements()) - { - URL tldUrl = tldEnum.nextElement(); - tldfrags.add(Resource.newResource(DefaultFileLocatorHelper.getLocalURL(tldUrl))); - } - } - } - catch (Exception e) - { - __logger.warn("Unable to locate the bundle " + frag.getBundleId(), e); - } - } - } - } - } - } - - /** - * @See {@link ContextDeployer#scan} - * @param contextFile - * @return - */ - protected ContextHandler createContextHandler(ContextHandler handlerToConfigure, Bundle bundle, File contextFile, String extraClasspath, - String overrideBundleInstallLocation, String requireTldBundle) - { - try - { - return createContextHandler(handlerToConfigure, bundle, new BufferedInputStream(new FileInputStream(contextFile)), extraClasspath, - overrideBundleInstallLocation, requireTldBundle); - } - catch (FileNotFoundException e) - { - __logger.warn(e); - } - return null; - } - - /** - * @See {@link ContextDeployer#scan} - * @param contextFile - * @return - */ - @SuppressWarnings("unchecked") - protected ContextHandler createContextHandler(ContextHandler handlerToConfigure, Bundle bundle, InputStream contextInputStream, String extraClasspath, - String overrideBundleInstallLocation, String requireTldBundle) - { - /* - * Do something identical to what the ContextProvider would have done: - * XmlConfiguration xmlConfiguration=new - * XmlConfiguration(resource.getURL()); HashMap properties = new - * HashMap(); properties.put("Server", _contexts.getServer()); if - * (_configMgr!=null) properties.putAll(_configMgr.getProperties()); - * - * xmlConfiguration.setProperties(properties); ContextHandler - * context=(ContextHandler)xmlConfiguration.configure(); - * context.setAttributes(new AttributesMap(_contextAttributes)); - */ - try - { - XmlConfiguration xmlConfiguration = new XmlConfiguration(contextInputStream); - HashMap properties = new HashMap(); - properties.put("Server", _wrapper.getServer()); - - // insert the bundle's location as a property. - setThisBundleHomeProperty(bundle, properties, overrideBundleInstallLocation); - xmlConfiguration.getProperties().putAll(properties); - - ContextHandler context = null; - if (handlerToConfigure == null) - { - context = (ContextHandler) xmlConfiguration.configure(); - } - else - { - xmlConfiguration.configure(handlerToConfigure); - context = handlerToConfigure; - } - - if (context instanceof WebAppContext) - { - ((WebAppContext) context).setExtraClasspath(extraClasspath); - ((WebAppContext) context).setParentLoaderPriority(_wrapper.getOSGiAppProvider().isParentLoaderPriority()); - if (_wrapper.getOSGiAppProvider().getDefaultsDescriptor() != null && _wrapper.getOSGiAppProvider().getDefaultsDescriptor().length() != 0) - { - ((WebAppContext) context).setDefaultsDescriptor(_wrapper.getOSGiAppProvider().getDefaultsDescriptor()); - } - } - - configureWebAppContext(context, bundle, requireTldBundle); - return context; - } - catch (FileNotFoundException e) - { - return null; - } - catch (SAXException e) - { - __logger.warn(e); - } - catch (IOException e) - { - __logger.warn(e); - } - catch (Throwable e) - { - __logger.warn(e); - } - finally - { - IO.close(contextInputStream); - } - return null; - } - - /** - * Configure a classloader onto the context. If the context is a - * WebAppContext, build a WebAppClassLoader that has access to all the jetty - * classes thanks to the classloader of the JettyBootStrapper bundle and - * also has access to the classloader of the bundle that defines this - * context. - *

- * If the context is not a WebAppContext, same but with a simpler - * URLClassLoader. Note that the URLClassLoader is pretty much fake: it - * delegate all actual classloading to the parent classloaders. - *

- *

- * The URL[] returned by the URLClassLoader create contained specifically - * the jars that some j2ee tools expect and look into. For example the jars - * that contain tld files for jasper's jstl support. - *

- *

- * Also as the jars in the lib folder and the classes in the classes folder - * might already be in the OSGi classloader we filter them out of the - * WebAppClassLoader - *

- * - * @param context - * @param contributor - * @param webapp - * @param contextPath - * @param classInBundle - * @throws Exception - */ - protected void configureWebappClassLoader(Bundle contributor, ContextHandler context, OSGiWebappClassLoader webappClassLoader, String requireTldBundle) - throws Exception - { - if (context instanceof WebAppContext) - { - WebAppContext webappCtxt = (WebAppContext) context; - context.setClassLoader(webappClassLoader); - webappClassLoader.setWebappContext(webappCtxt); - - String pathsToRequiredBundles = getPathsToRequiredBundles(context, contributor, requireTldBundle); - if (pathsToRequiredBundles != null) webappClassLoader.addClassPath(pathsToRequiredBundles); - } - else - { - context.setClassLoader(webappClassLoader); - } - } - - /** - * No matter what the type of webapp, we create a WebappClassLoader. - */ - protected OSGiWebappClassLoader createWebappClassLoader(Bundle contributor) - throws Exception - { - // we use a temporary WebAppContext object. - // if this is a real webapp we will set it on it a bit later: once we - // know. - OSGiWebappClassLoader webappClassLoader = new OSGiWebappClassLoader(_wrapper.getParentClassLoaderForWebapps(), new WebAppContext(), contributor, - BUNDLE_CLASS_LOADER_HELPER); - return webappClassLoader; - } - - protected void applyMetaInfContextXml(Bundle bundle, ContextHandler contextHandler) - throws Exception - { - if (bundle == null) return; - if (contextHandler == null) return; - - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - __logger.info("Context classloader = " + cl); - try - { - Thread.currentThread().setContextClassLoader(_wrapper.getParentClassLoaderForWebapps()); - - // find if there is a META-INF/context.xml file - URL contextXmlUrl = bundle.getEntry("/META-INF/jetty-webapp-context.xml"); - if (contextXmlUrl == null) return; - - // Apply it just as the standard jetty ContextProvider would do - __logger.info("Applying " + contextXmlUrl + " to " + contextHandler); - - XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXmlUrl); - HashMap properties = new HashMap(); - properties.put("Server", _wrapper.getServer()); - xmlConfiguration.getProperties().putAll(properties); - xmlConfiguration.configure(contextHandler); - } - finally - { - Thread.currentThread().setContextClassLoader(cl); - } - } - - /** - * Set the property "this.bundle.install" to point to the location - * of the bundle. Useful when is - * used. - */ - private void setThisBundleHomeProperty(Bundle bundle, HashMap properties, String overrideBundleInstallLocation) - { - try - { - File location = overrideBundleInstallLocation != null ? new File(overrideBundleInstallLocation) : BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(bundle); - properties.put("this.bundle.install", location.getCanonicalPath()); - properties.put("this.bundle.install.url", bundle.getEntry("/").toString()); - } - catch (Throwable t) - { - __logger.warn("Unable to set 'this.bundle.install' " + " for the bundle " + bundle.getSymbolicName(), t); - } - } - - private String getPathsToRequiredBundles(ContextHandler context, Bundle bundle, String requireTldBundle) - throws Exception - { - if (requireTldBundle == null) return null; - - StringBuilder paths = new StringBuilder(); - PackageAdmin packAdmin = getBundleAdmin(); - DefaultFileLocatorHelper fileLocatorHelper = new DefaultFileLocatorHelper(); - - String[] symbNames = requireTldBundle.split(", "); - - for (String symbName : symbNames) - { - Bundle[] bs = packAdmin.getBundles(symbName, null); - if (bs == null || bs.length == 0) { throw new IllegalArgumentException("Unable to locate the bundle '" + symbName - + "' specified in the " - + OSGiWebappConstants.REQUIRE_TLD_BUNDLE - + " of the manifest of " - + (bundle == null ? "unknown" : bundle.getSymbolicName())); } - - File f = fileLocatorHelper.getBundleInstallLocation(bs[0]); - if (paths.length() > 0) paths.append(", "); - __logger.debug("getPathsToRequiredBundles: bundle path=" + bs[0].getLocation() + " uri=" + f.toURI()); - paths.append(f.toURI().toURL().toString()); - } - - return paths.toString(); - } - - private PackageAdmin getBundleAdmin() - { - Bundle bootBundle = ((BundleReference) OSGiWebappConstants.class.getClassLoader()).getBundle(); - ServiceTracker serviceTracker = new ServiceTracker(bootBundle.getBundleContext(), PackageAdmin.class.getName(), null); - serviceTracker.open(); - - return (PackageAdmin) serviceTracker.getService(); - } -} - diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java index db976358ddc..b8adc657054 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java @@ -19,46 +19,70 @@ package org.eclipse.jetty.osgi.boot.internal.webapp; -import java.net.URL; -import java.util.Dictionary; +import java.util.ArrayList; +import java.util.Collection; -import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; -import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.osgi.boot.BundleProvider; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.osgi.framework.Bundle; import org.osgi.framework.BundleEvent; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.BundleTracker; import org.osgi.util.tracker.BundleTrackerCustomizer; +import org.osgi.util.tracker.ServiceTracker; /** - * Support bundles that declare the webapp directly through headers in their - * manifest. - *

- * Those headers will define a new WebApplication: - *

    - *
  • Web-ContextPath
  • - *
  • Jetty-WarFolderPath
  • - *
- *

- *

- * Those headers will define a new app started via a jetty-context or a list of - * them. ',' column is the separator between the various context files. - *

    - *
  • Jetty-ContextFilePath
  • - *
- *

- * And generate a jetty WebAppContext or another ContextHandler then registers - * it as service. Kind of simpler than declarative services and their xml files. - * Also avoid having the contributing bundle depend on jetty's package for - * WebApp. + * WebBundleTrackerCustomizer + * + * + * Support bundles that declare a webpp or context directly through headers in their + * manifest. They will be deployed to the default jetty Server instance. + * + * If you wish to deploy a context or webapp to a different jetty Server instance, + * register your context/webapp as an osgi service, and set the property OSGiServerConstants.MANAGED_JETTY_SERVER_NAME + * with the name of the Server instance you wish to depoy to. * * @author hmalphettes */ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer { private static final Logger LOG = Log.getLogger(WebBundleTrackerCustomizer.class); + + public static Collection JSP_REGISTRATION_HELPERS = new ArrayList(); + public static final String FILTER = "(&(objectclass=" + BundleProvider.class.getName() + ")"+ + "("+OSGiServerConstants.MANAGED_JETTY_SERVER_NAME+"="+OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME+"))"; + private ServiceTracker _serviceTracker; + private BundleTracker _bundleTracker; + + /* ------------------------------------------------------------ */ + /** + * @throws Exception + */ + public WebBundleTrackerCustomizer () + throws Exception + { + Bundle myBundle = FrameworkUtil.getBundle(this.getClass()); + + //track all instances of deployers of webapps/contexts as bundles + _serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null) { + public Object addingService(ServiceReference reference) { + Object object = super.addingService(reference); + LOG.debug("Deployer registered {}", reference); + openBundleTracker(); + return object; + } + }; + _serviceTracker.open(); + + } + + + /* ------------------------------------------------------------ */ /** * A bundle is being added to the BundleTracker. * @@ -83,8 +107,7 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer { if (bundle.getState() == Bundle.ACTIVE) { - boolean isWebBundle = register(bundle); - return isWebBundle ? bundle : null; + register(bundle); } else if (bundle.getState() == Bundle.STOPPING) { @@ -98,6 +121,8 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer return null; } + + /* ------------------------------------------------------------ */ /** * A bundle tracked by the BundleTracker has been modified. * @@ -125,6 +150,8 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer } } + + /* ------------------------------------------------------------ */ /** * A bundle tracked by the BundleTracker has been removed. * @@ -143,129 +170,81 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer unregister(bundle); } + + /* ------------------------------------------------------------ */ /** * @param bundle * @return true if this bundle in indeed a web-bundle. */ private boolean register(Bundle bundle) { - Dictionary dic = bundle.getHeaders(); - String warFolderRelativePath = (String) dic.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); - if (warFolderRelativePath != null) + if (bundle == null) + return false; + + //It might be a bundle that we can deploy to our default jetty server instance + boolean deployed = false; + Object[] deployers = _serviceTracker.getServices(); + if (deployers != null) { - String contextPath = getWebContextPath(bundle, dic, false); - if (contextPath == null || !contextPath.startsWith("/")) + int i=0; + while (!deployed && i dic, boolean webinfWebxmlExists) - { - String rfc66ContextPath = (String) dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); - if (rfc66ContextPath == null) - { - if (!webinfWebxmlExists) { return null; } - // extract from the last token of the bundle's location: - // (really ? - // could consider processing the symbolic name as an alternative - // the location will often reflect the version. - // maybe this is relevant when the file is a war) - String location = bundle.getLocation(); - String toks[] = location.replace('\\', '/').split("/"); - rfc66ContextPath = toks[toks.length - 1]; - // remove .jar, .war etc: - int lastDot = rfc66ContextPath.lastIndexOf('.'); - if (lastDot != -1) - { - rfc66ContextPath = rfc66ContextPath.substring(0, lastDot); - } + public void setAndOpenWebBundleTracker(BundleTracker bundleTracker) { + if(_bundleTracker == null) { + _bundleTracker = bundleTracker; + LOG.debug("Bundle tracker is set"); + openBundleTracker(); } - if (!rfc66ContextPath.startsWith("/")) - { - rfc66ContextPath = "/" + rfc66ContextPath; - } - return rfc66ContextPath; } - private void unregister(Bundle bundle) - { - // nothing to do: when the bundle is stopped, each one of its service - // reference is also stopped and that is what we use to stop the - // corresponding - // webapps registered in that bundle. + private void openBundleTracker() { + if(_bundleTracker != null && _serviceTracker.getServices() != null && + _serviceTracker.getServices().length > 0) { + _bundleTracker.open(); + LOG.debug("Bundle tracker has been opened"); + } } } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java new file mode 100644 index 00000000000..edcfde0f572 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java @@ -0,0 +1,61 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * BundleClassLoaderHelperFactory + * + * Get a class loader helper adapted for the particular osgi environment. + */ +public class BundleClassLoaderHelperFactory +{ + private static final Logger LOG = Log.getLogger(BundleClassLoaderHelperFactory.class); + + private static BundleClassLoaderHelperFactory _instance = new BundleClassLoaderHelperFactory(); + + public static BundleClassLoaderHelperFactory getFactory() + { + return _instance; + } + + private BundleClassLoaderHelperFactory() + { + } + + public BundleClassLoaderHelper getHelper() + { + //use the default + BundleClassLoaderHelper helper = BundleClassLoaderHelper.DEFAULT; + try + { + //if a fragment has not provided their own impl + helper = (BundleClassLoaderHelper) Class.forName(BundleClassLoaderHelper.CLASS_NAME).newInstance(); + } + catch (Throwable t) + { + LOG.ignore(t); + } + + return helper; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java index ebba3dbd6e4..0809b72906b 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java @@ -89,5 +89,31 @@ public interface BundleFileLocatorHelper * @return null or all the entries found for that path. */ public Enumeration findEntries(Bundle bundle, String entryPath); + + /** + * Only useful for equinox: on felix we get the file:// or jar:// url + * already. Other OSGi implementations have not been tested + *

+ * Get a URL to the bundle entry that uses a common protocol (i.e. file: + * jar: or http: etc.). + *

+ * + * @return a URL to the bundle entry that uses a common protocol + */ + public URL getLocalURL(URL url); + + /** + * Only useful for equinox: on felix we get the file:// url already. Other + * OSGi implementations have not been tested + *

+ * Get a URL to the content of the bundle entry that uses the file: + * protocol. The content of the bundle entry may be downloaded or extracted + * to the local file system in order to create a file: URL. + * + * @return a URL to the content of the bundle entry that uses the file: + * protocol + *

+ */ + public URL getFileURL(URL url); } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java new file mode 100644 index 00000000000..13703fa416f --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import java.util.Dictionary; +import java.util.Hashtable; + +import javax.security.auth.login.FailedLoginException; + +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; + +public class EventSender +{ + //OSGi Event Admin events for webapps + public static final String DEPLOYING_EVENT = "org/osgi/service/web/DEPLOYING"; + public static final String DEPLOYED_EVENT = "org/osgi/service/web/DEPLOYED"; + public static final String UNDEPLOYING_EVENT = "org/osgi/service/web/UNDEPLOYING"; + public static final String UNDEPLOYED_EVENT = "org/osgi/service/web/UNDEPLOYED"; + public static final String FAILED_EVENT = "org/osgi/service/web/FAILED"; + + + private static final EventSender __instance = new EventSender(); + private Bundle _myBundle; + private EventAdmin _eventAdmin; + + private EventSender () + { + _myBundle = FrameworkUtil.getBundle(EventSender.class); + ServiceReference ref = _myBundle.getBundleContext().getServiceReference(EventAdmin.class.getName()); + if (ref != null) + _eventAdmin = (EventAdmin)_myBundle.getBundleContext().getService(ref); + } + + + public static EventSender getInstance() + { + return __instance; + } + + public void send (String topic, Bundle wab, String contextPath) + { + if (topic==null || wab==null || contextPath==null) + return; + + send(topic, wab, contextPath, null); + } + + + public void send (String topic, Bundle wab, String contextPath, Exception ex) + { + if (_eventAdmin == null) + return; + + Dictionary props = new Hashtable(); + props.put("bundle.symbolicName", wab.getSymbolicName()); + props.put("bundle.id", wab.getBundleId()); + props.put("bundle", wab); + props.put("bundle.version", wab.getVersion()); + props.put("context.path", contextPath); + props.put("timestamp", System.currentTimeMillis()); + props.put("extender.bundle", _myBundle); + props.put("extender.bundle.symbolicName", _myBundle.getSymbolicName()); + props.put("extender.bundle.id", _myBundle.getBundleId()); + props.put("extender.bundle.version", _myBundle.getVersion()); + + if (FAILED_EVENT.equalsIgnoreCase(topic) && ex != null) + props.put("exception", ex); + + _eventAdmin.sendEvent(new Event(topic, props)); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java new file mode 100644 index 00000000000..8850f5e6397 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java @@ -0,0 +1,221 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; + +/** + * OSGiClassLoader + * + * Class loader that is aware of a bundle. Similar to WebAppClassLoader from Jetty + * and the OSGiWebAppClassLoader, but works without webapps. + */ +public class OSGiClassLoader extends URLClassLoader +{ + private static final Logger LOG = Log.getLogger(OSGiClassLoader.class); + + + private Bundle _bundle; + private ClassLoader _osgiBundleClassLoader; + private boolean _lookInOsgiFirst = true; + private ClassLoader _parent; + + /* ------------------------------------------------------------ */ + public OSGiClassLoader(ClassLoader parent, Bundle bundle) + { + super(new URL[]{}, parent); + _parent = getParent(); + _bundle = bundle; + _osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(_bundle); + } + + + + /* ------------------------------------------------------------ */ + /** + * Get a resource from the classloader + * + * Copied from WebAppClassLoader + */ + public URL getResource(String name) + { + URL url= null; + boolean tried_parent= false; + + + if (_parent!=null && !_lookInOsgiFirst) + { + tried_parent= true; + + if (_parent!=null) + url= _parent.getResource(name); + } + + if (url == null) + { + + url = _osgiBundleClassLoader.getResource(name); + + if (url == null && name.startsWith("/")) + { + if (LOG.isDebugEnabled()) + LOG.debug("HACK leading / off " + name); + + url = _osgiBundleClassLoader.getResource(name.substring(1)); + } + } + + if (url == null && !tried_parent) + { + if (_parent!=null) + url= _parent.getResource(name); + } + + if (url != null) + if (LOG.isDebugEnabled()) + LOG.debug("getResource("+name+")=" + url); + + return url; + } + + /* ------------------------------------------------------------ */ + @Override + public Class loadClass(String name) throws ClassNotFoundException + { + return loadClass(name, false); + } + + /* ------------------------------------------------------------ */ + @Override + protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException + { + Class c = findLoadedClass(name); + ClassNotFoundException ex= null; + boolean tried_parent= false; + + if (c == null && _parent!=null && !_lookInOsgiFirst) + { + tried_parent= true; + try + { + c= _parent.loadClass(name); + if (LOG.isDebugEnabled()) + LOG.debug("loaded " + c); + } + catch (ClassNotFoundException e) + { + ex= e; + } + } + + if (c == null) + { + try + { + c= this.findClass(name); + } + catch (ClassNotFoundException e) + { + ex= e; + } + } + + if (c == null && _parent!=null && !tried_parent) + c = _parent.loadClass(name); + + if (c == null) + throw ex; + + if (resolve) + resolveClass(c); + + if (LOG.isDebugEnabled()) + LOG.debug("loaded " + c+ " from "+c.getClassLoader()); + + return c; + } + + /* ------------------------------------------------------------ */ + @Override + public Enumeration getResources(String name) throws IOException + { + Enumeration osgiUrls = _osgiBundleClassLoader.getResources(name); + Enumeration urls = super.getResources(name); + if (_lookInOsgiFirst) + { + return Collections.enumeration(toList(osgiUrls, urls)); + } + else + { + return Collections.enumeration(toList(urls, osgiUrls)); + } + } + + + /* ------------------------------------------------------------ */ + @Override + protected Class findClass(String name) throws ClassNotFoundException + { + try + { + return _lookInOsgiFirst ? _osgiBundleClassLoader.loadClass(name) : super.findClass(name); + } + catch (ClassNotFoundException cne) + { + try + { + return _lookInOsgiFirst ? super.findClass(name) : _osgiBundleClassLoader.loadClass(name); + } + catch (ClassNotFoundException cne2) + { + throw cne; + } + } + } + + + + + + /* ------------------------------------------------------------ */ + /** + * @param e + * @param e2 + * @return + */ + private List toList(Enumeration e, Enumeration e2) + { + List list = new ArrayList(); + while (e != null && e.hasMoreElements()) + list.add(e.nextElement()); + while (e2 != null && e2.hasMoreElements()) + list.add(e2.nextElement()); + return list; + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java index 951443f0626..813bff42fcb 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java @@ -20,7 +20,8 @@ package org.eclipse.jetty.osgi.boot.utils; import java.net.URL; -import org.eclipse.jetty.osgi.boot.OSGiAppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; + /** * Fix various shortcomings with the way jasper parses the tld files. @@ -55,6 +56,6 @@ public interface WebappRegistrationCustomizer * @return array of URLs * @throws Exception */ - URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper fileLocator) throws Exception; + URL[] getJarsWithTlds(DeploymentManager manager, BundleFileLocatorHelper fileLocator) throws Exception; } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java index b1cfcc4897b..9bb074a5f22 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java @@ -23,6 +23,8 @@ import java.lang.reflect.Method; import java.util.List; import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; import org.osgi.framework.Bundle; /** @@ -31,12 +33,9 @@ import org.osgi.framework.Bundle; */ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper { - - private static boolean identifiedOsgiImpl = false; + private static final Logger LOG = Log.getLogger(BundleClassLoaderHelper.class); - private static Class BundleWiringClass = null; - private static Method BundleWiringClass_getClassLoader_method = null; - private static Method BundleClass_adapt_method = null; + private static boolean identifiedOsgiImpl = false; private static boolean isEquinox = false; @@ -45,40 +44,19 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper private static void init(Bundle bundle) { identifiedOsgiImpl = true; - try { - BundleWiringClass = bundle.getClass().getClassLoader().loadClass("org.osgi.framework.wiring.BundleWiring"); - if (BundleWiringClass != null) - { - BundleWiringClass_getClassLoader_method = BundleWiringClass.getDeclaredMethod("getClassLoader", new Class[] {}); - BundleClass_adapt_method = bundle.getClass().getDeclaredMethod("adapt", new Class[] { Class.class }); - BundleClass_adapt_method.setAccessible(true); - return; - } + isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null; } catch (Throwable t) { - //nevermind: an older version of OSGi where BundleWiring is not availble - //t.printStackTrace(); - } - - if (!bundle.getClass().getName().startsWith("org.apache.felix")) - { - try - { - isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null; - } - catch (Throwable t) - { - isEquinox = false; - } + isEquinox = false; } if (!isEquinox) { try { - isFelix = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl") != null; + isFelix = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl") != null; } catch (Throwable t2) { @@ -95,8 +73,8 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper */ public ClassLoader getBundleClassLoader(Bundle bundle) { - //Older OSGi implementations: String bundleActivator = (String) bundle.getHeaders().get("Bundle-Activator"); + if (bundleActivator == null) { bundleActivator = (String) bundle.getHeaders().get("Jetty-ClassInBundle"); @@ -109,9 +87,7 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper } catch (ClassNotFoundException e) { - // should not happen as we are called if the bundle is started - // anyways. - e.printStackTrace(); + LOG.warn(e); } } // resort to introspection @@ -119,27 +95,16 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper { init(bundle); } - //This works for OSGi 4.2 and more recent. Aka version 1.6 - //It is using ava reflection to execute: - //(BundleClassLoader) bundle.adapt(BundleWiring.class).getClassLoader() - if (BundleClass_adapt_method != null && BundleWiringClass_getClassLoader_method != null) - { - try - { - Object bundleWiring = BundleClass_adapt_method.invoke(bundle, BundleWiringClass); - return (ClassLoader)BundleWiringClass_getClassLoader_method.invoke(bundleWiring, new Object[] {}); - } - catch (Throwable t) - { - t.printStackTrace(); - return null; - } - } if (isEquinox) { return internalGetEquinoxBundleClassLoader(bundle); } - else if (isFelix) { return internalGetFelixBundleClassLoader(bundle); } + else if (isFelix) + { + return internalGetFelixBundleClassLoader(bundle); + } + + LOG.warn("No classloader found for bundle "+bundle.getSymbolicName()); return null; } @@ -169,8 +134,9 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper } catch (Throwable t) { - t.printStackTrace(); + LOG.warn(t); } + LOG.warn("No classloader for equinox platform for bundle "+bundle.getSymbolicName()); return null; } @@ -178,65 +144,152 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper private static Field Felix_ModuleImpl_m_classLoader_field; - private static Field Felix_BundleImpl_m_revisions_field; + private static Method Felix_adapt_method; + private static Method Felix_bundle_wiring_getClassLoader_method; + + private static Class Felix_bundleWiringClazz; + + private static Boolean isFelix403 = null; private static ClassLoader internalGetFelixBundleClassLoader(Bundle bundle) { - // assume felix: - try + //firstly, try to find classes matching a newer version of felix + initFelix403(bundle); + + if (isFelix403.booleanValue()) { - // now get the current module from the bundle. - // and return the private field m_classLoader of ModuleImpl - if (Felix_BundleImpl_m_modules_field == null) - { - Felix_BundleImpl_m_modules_field = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl").getDeclaredField("m_modules"); - Felix_BundleImpl_m_modules_field.setAccessible(true); - } - - // Figure out which version of the modules is exported - Object currentModuleImpl; try { - Object[] moduleArray = (Object[]) Felix_BundleImpl_m_modules_field.get(bundle); - currentModuleImpl = moduleArray[moduleArray.length - 1]; + Object wiring = Felix_adapt_method.invoke(bundle, new Object[] {Felix_bundleWiringClazz}); + ClassLoader cl = (ClassLoader)Felix_bundle_wiring_getClassLoader_method.invoke(wiring); + return cl; } - catch (Throwable t2) + catch (Exception e) + { + LOG.warn(e); + return null; + } + } + + + // Fallback to trying earlier versions of felix. + if (Felix_BundleImpl_m_modules_field == null) + { + try + { + Class bundleImplClazz = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl"); + Felix_BundleImpl_m_modules_field = bundleImplClazz.getDeclaredField("m_modules"); + Felix_BundleImpl_m_modules_field.setAccessible(true); + } + catch (ClassNotFoundException e) + { + LOG.warn(e); + } + catch (NoSuchFieldException e) + { + LOG.warn(e); + } + } + + // Figure out which version of the modules is exported + Object currentModuleImpl; + try + { + Object[] moduleArray = (Object[]) Felix_BundleImpl_m_modules_field.get(bundle); + currentModuleImpl = moduleArray[moduleArray.length - 1]; + } + catch (Throwable t2) + { + try { - @SuppressWarnings("unchecked") List moduleArray = (List) Felix_BundleImpl_m_modules_field.get(bundle); currentModuleImpl = moduleArray.get(moduleArray.size() - 1); } + catch (Exception e) + { + LOG.warn(e); + return null; + } + } - if (Felix_ModuleImpl_m_classLoader_field == null && currentModuleImpl != null) + if (Felix_ModuleImpl_m_classLoader_field == null && currentModuleImpl != null) + { + try { Felix_ModuleImpl_m_classLoader_field = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.ModuleImpl").getDeclaredField("m_classLoader"); Felix_ModuleImpl_m_classLoader_field.setAccessible(true); } - // first make sure that the classloader is ready: - // the m_classLoader field must be initialized by the - // ModuleImpl.getClassLoader() private method. - ClassLoader cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl); - if (cl == null) + catch (ClassNotFoundException e) { - // looks like it was not ready: - // the m_classLoader field must be initialized by the - // ModuleImpl.getClassLoader() private method. - // this call will do that. - bundle.loadClass("java.lang.Object"); - cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl); - return cl; - } - else + LOG.warn(e); + return null; + } + catch (NoSuchFieldException e) { - return cl; + LOG.warn(e); + return null; } } - catch (Throwable t) + // first make sure that the classloader is ready: + // the m_classLoader field must be initialized by the + // ModuleImpl.getClassLoader() private method. + ClassLoader cl = null; + try { - t.printStackTrace(); + cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl); + if (cl != null) + return cl; + } + catch (Exception e) + { + LOG.warn(e); + return null; + } + + // looks like it was not ready: + // the m_classLoader field must be initialized by the + // ModuleImpl.getClassLoader() private method. + // this call will do that. + try + { + bundle.loadClass("java.lang.Object"); + cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl); + return cl; + } + catch (Exception e) + { + LOG.warn(e); + return null; } - return null; } + + private static void initFelix403 (Bundle bundle) + { + //see if the version of Felix is a new one + if (isFelix403 == null) + { + try + { + Class bundleImplClazz = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl"); + Felix_bundleWiringClazz = bundle.getClass().getClassLoader().loadClass("org.osgi.framework.wiring.BundleWiring"); + Felix_adapt_method = bundleImplClazz.getDeclaredMethod("adapt", new Class[] {Class.class}); + Felix_adapt_method.setAccessible(true); + Felix_bundle_wiring_getClassLoader_method = Felix_bundleWiringClazz.getDeclaredMethod("getClassLoader"); + Felix_bundle_wiring_getClassLoader_method.setAccessible(true); + isFelix403 = Boolean.TRUE; + } + catch (ClassNotFoundException e) + { + LOG.warn("Felix 4.x classes not found in environment"); + isFelix403 = Boolean.FALSE; + } + catch (NoSuchMethodException e) + { + LOG.warn("Felix 4.x classes not found in environment"); + isFelix403 = Boolean.FALSE; + } + } + } } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java index 3028aa6c2c4..2d31459de17 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java @@ -31,13 +31,13 @@ import java.util.zip.ZipFile; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.util.URIUtil; -import org.eclipse.jetty.util.resource.FileResource; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.FileResource; import org.osgi.framework.Bundle; /** - * From a bundle to its location on the filesystem. - * Often assumes the bundle is not a jar. + * From a bundle to its location on the filesystem. Assumes the bundle is not a + * jar. * * @author hmalphettes */ @@ -182,16 +182,10 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper File file = new File(location.substring("file:".length())); return file; } - else - { - //Resort to introspection on felix: - return getBundleInstallLocationInFelix(bundle); - } } return null; } - - + /** * Locate a file inside a bundle. * @@ -303,7 +297,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper * * @return a URL to the bundle entry that uses a common protocol */ - public static URL getLocalURL(URL url) + public URL getLocalURL(URL url) { if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol())) { @@ -339,7 +333,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper * protocol *

*/ - public static URL getFileURL(URL url) + public URL getFileURL(URL url) { if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol())) { @@ -362,85 +356,4 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper return url; } - // Felix introspection - private static Method Felix_BundleImpl_getArchive_method; - private static Method Felix_BundleArchive_getCurrentRevision_method; - private static Method Felix_BundleRevision_getRevisionRootDir_method; - - private static boolean felixIntroSpectionDone = false; - - /** - * Introspection of the implementation classes of Felix-3.x and Felix-4.x. - *

- * See org.apache.felix.framework.cache - * In pseudo code: - * - * File revRootDir = BundleImpl.getArchive().getCurrentRevision().getRevisionRootDir(); - * return new File(revRootDir, bundle.jar) if it exists? - * else return revRootDir - *

- * @param bundle - * @return The File or null if we failed to find it. - */ - private static File getBundleInstallLocationInFelix(Bundle bundle) - { - if (Felix_BundleImpl_getArchive_method == null) { - if (felixIntroSpectionDone) - { - return null; - } - felixIntroSpectionDone = true; - try - { - Felix_BundleImpl_getArchive_method = bundle.getClass().getDeclaredMethod("getArchive", new Class[] {}); - Felix_BundleImpl_getArchive_method.setAccessible(true); - Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle); - Class bundleArchiveClass = archive.getClass(); - Felix_BundleArchive_getCurrentRevision_method = bundleArchiveClass.getDeclaredMethod("getCurrentRevision", new Class[] {}); - Felix_BundleArchive_getCurrentRevision_method.setAccessible(true); - Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive); - Class bundleRevisionClass = revision.getClass(); - Felix_BundleRevision_getRevisionRootDir_method = bundleRevisionClass.getMethod("getRevisionRootDir", new Class[] {}); - Felix_BundleRevision_getRevisionRootDir_method.setAccessible(true); - } - catch (Throwable t) - { - //nevermind? - //t.printStackTrace(); - Felix_BundleImpl_getArchive_method = null; - return null; - } - } - if (Felix_BundleImpl_getArchive_method != null) - { - try - { - Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle); - Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive); - File revRootDir = (File)Felix_BundleRevision_getRevisionRootDir_method.invoke(revision); - //System.err.println("Got the archive revision root dir " + revRootDir.getAbsolutePath()); - File bundleJar = new File(revRootDir, "bundle.jar"); - if (bundleJar.exists()) - { - //bundle.jar is hardcoded in org.apache.felix.framework.cache.JarRevision - //when it is not a bundle.jar, then the bundle location starts with 'file:' and we have already - //taken care if that scheme earlier. - return bundleJar; - } - else //sanity check?: if (new File(revRootDir, "META-INF/MANIFEST.MF").exists()) - { - //this is a DirectoryRevision - return revRootDir; - } - } - catch (Throwable t) - { - //best effort: nevermind - //t.printStackTrace(); - } - } - return null; - } -// -- end Felix introspection - } diff --git a/jetty-osgi/jetty-osgi-equinoxtools/pom.xml b/jetty-osgi/jetty-osgi-equinoxtools/pom.xml deleted file mode 100644 index 71e1d0e92c5..00000000000 --- a/jetty-osgi/jetty-osgi-equinoxtools/pom.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - org.eclipse.jetty.osgi - jetty-osgi-project - 7.6.10-SNAPSHOT - ../pom.xml - - 4.0.0 - jetty-osgi-equinoxtools - Jetty :: OSGi :: Example Equinox Tools - Jetty OSGi Example Equinox Tools - - ${project.groupId}.equinoxtools - - - - org.eclipse.jetty - jetty-webapp - - - org.eclipse.jetty - jetty-continuation - - - org.eclipse.jetty - jetty-websocket - - - org.eclipse.jetty - jetty-servlet - - - org.eclipse.osgi - org.eclipse.osgi - - - org.eclipse.osgi - org.eclipse.osgi.services - - - - - - - maven-antrun-plugin - - - process-resources - - - - - - - - - run - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - artifact-jar - - jar - - - - test-jar - - test-jar - - - - - - target/classes/META-INF/MANIFEST.MF - - - - - org.apache.felix - maven-bundle-plugin - true - - - bundle-manifest - process-classes - - manifest - - - - - - org.eclipse.jetty.osgi.equinoxtools - Console - org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator - org.eclipse.jetty.osgi.equinoxtools;x-internal:=true;version="${parsedVersion.osgiVersion}", - org.eclipse.jetty.osgi.equinoxtools.console;x-internal:=true;version="${parsedVersion.osgiVersion}" - - <_nouses>true - - - - - org.codehaus.mojo - findbugs-maven-plugin - - org.eclipse.jetty.osgi.equinoxtools.* - - - - - - diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index d308410fc8f..4b4f36f8322 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -3,7 +3,6 @@ org.eclipse.jetty.osgi jetty-osgi-project 9.0.0-SNAPSHOT - ../pom.xml 4.0.0 jetty-httpservice @@ -96,9 +95,10 @@ org.eclipse.jetty.osgi.httpservice OSGi HttpService contexts/httpservice.xml - org.eclipse.jetty.server.handler;version="9.0.0", -org.eclipse.jetty.util.component;version="9.0.0", -org.eclipse.jetty.server.session;version="9.0.0", + org.eclipse.jetty.server.handler;version="[9.0,10.0)", +org.eclipse.jetty.util.component;version="[9.0,10.0)", +org.eclipse.jetty.server.session;version="[9.0,10.0)", +org.eclipse.jetty.servlet;version="[9.0,10.0)", org.eclipse.equinox.http.servlet, * diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index 24d1b7c9dd5..bebc62aa08c 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -4,7 +4,6 @@ org.eclipse.jetty jetty-project 9.0.0-SNAPSHOT - ../pom.xml org.eclipse.jetty.osgi jetty-osgi-project @@ -23,6 +22,8 @@ jetty-osgi-boot-jsp jetty-osgi-boot-warurl jetty-osgi-httpservice + test-jetty-osgi-webapp + test-jetty-osgi-context test-jetty-osgi @@ -142,28 +143,6 @@ servlet ${equinox-http-servlet-version} - - - org.slf4j slf4j-api diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml new file mode 100644 index 00000000000..109ba98daf0 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -0,0 +1,112 @@ + + + org.eclipse.jetty.osgi + jetty-osgi-project + 9.0.0-SNAPSHOT + + 4.0.0 + test-jetty-osgi-context + Jetty :: OSGi :: Context + Test Jetty OSGi bundle with a ContextHandler + + ${project.groupId}.testcontext + + + + org.eclipse.jetty + jetty-server + ${project.version} + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + + + + + src/main/resources + + + src/main/context + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + artifact-jar + + jar + + + + test-jar + + test-jar + + + + + + target/classes/META-INF/MANIFEST.MF + + + + + org.apache.felix + maven-bundle-plugin + true + + + bundle-manifest + process-classes + + manifest + + + + + + org.eclipse.jetty.osgi.testcontext;singleton:=true + Jetty OSGi Test Context + com.acme.osgi.Activator + J2SE-1.5 + + <_nouses>true + + javax.servlet;version="2.6.0", + javax.servlet.resources;version="2.6.0", + org.osgi.framework, + org.osgi.service.cm;version="1.2.0", + org.osgi.service.packageadmin, + org.osgi.service.startlevel;version="1.0.o", + org.osgi.service.url;version="1.0.0", + org.osgi.util.tracker;version="1.3.0", + org.slf4j;resolution:=optional, + org.slf4j.spi;resolution:=optional, + org.slf4j.helpers;resolution:=optional, + org.xml.sax, + org.xml.sax.helpers, + * + + org.eclipse.jetty.*;version="[9.0,10.0)" + + + + + + + + + diff --git a/jetty-osgi/test-jetty-osgi-context/src/main/context/acme.xml b/jetty-osgi/test-jetty-osgi-context/src/main/context/acme.xml new file mode 100644 index 00000000000..53bbbdb3afb --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-context/src/main/context/acme.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + /static/ + + + + /unset + + + + + + + + + + + index.html + + + max-age=3600,public + + + + + diff --git a/jetty-osgi/test-jetty-osgi-context/src/main/java/com/acme/osgi/Activator.java b/jetty-osgi/test-jetty-osgi-context/src/main/java/com/acme/osgi/Activator.java new file mode 100644 index 00000000000..0b18cd87bef --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-context/src/main/java/com/acme/osgi/Activator.java @@ -0,0 +1,82 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package com.acme.osgi; + +import java.util.Dictionary; +import java.util.Hashtable; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; +import org.osgi.util.tracker.BundleTracker; + +/** + * Bootstrap a ContextHandler + * + * + */ +public class Activator implements BundleActivator +{ + + /** + * + * @param context + */ + public void start(final BundleContext context) throws Exception + { + ContextHandler ch = new ContextHandler(); + ch.addEventListener(new ServletContextListener () { + + @Override + public void contextInitialized(ServletContextEvent sce) + { + System.err.println("Context is initialized"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + System.err.println("CONTEXT IS DESTROYED!"); + } + + }); + Dictionary props = new Hashtable(); + props.put("contextPath","/acme"); + props.put("Jetty-ContextFilePath", "acme.xml"); + context.registerService(ContextHandler.class.getName(),ch,props); + } + + /** + * Stop the activator. + * + * @see + * org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception + { + } +} diff --git a/jetty-osgi/test-jetty-osgi-context/src/main/resources/static/index.html b/jetty-osgi/test-jetty-osgi-context/src/main/resources/static/index.html new file mode 100644 index 00000000000..3189646adce --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-context/src/main/resources/static/index.html @@ -0,0 +1,6 @@ + + +

Test OSGi Context

+

ContextHandler registered as a service successfully deployed.

+ + diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml new file mode 100644 index 00000000000..f5833de55e9 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml @@ -0,0 +1,107 @@ + + + org.eclipse.jetty.osgi + jetty-osgi-project + 9.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + test-jetty-osgi-webapp + Jetty :: OSGi :: WebApp + Test Jetty OSGi Webapp bundle + + ${project.groupId}.webapp + + + + org.eclipse.jetty + jetty-webapp + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + + + + + src/main/resources + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + artifact-jar + + jar + + + + test-jar + + test-jar + + + + + + target/classes/META-INF/MANIFEST.MF + + + + + org.apache.felix + maven-bundle-plugin + true + + + bundle-manifest + process-classes + + manifest + + + + + + org.eclipse.jetty.osgi.testapp;singleton:=true + Jetty OSGi Test WebApp + com.acme.osgi.Activator + J2SE-1.5 + + <_nouses>true + + org.osgi.framework, + org.osgi.service.cm;version="1.2.0", + org.osgi.service.packageadmin, + org.osgi.service.startlevel;version="1.0.o", + org.osgi.service.url;version="1.0.0", + org.osgi.util.tracker;version="1.3.0", + org.slf4j;resolution:=optional, + org.slf4j.spi;resolution:=optional, + org.slf4j.helpers;resolution:=optional, + org.xml.sax, + org.xml.sax.helpers, + * + + org.eclipse.jetty.*;version="[9.0,10.0)" + + + + + + + + + diff --git a/jetty-osgi/test-jetty-osgi-webapp/src/main/java/com/acme/osgi/Activator.java b/jetty-osgi/test-jetty-osgi-webapp/src/main/java/com/acme/osgi/Activator.java new file mode 100644 index 00000000000..a7f9edeb361 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-webapp/src/main/java/com/acme/osgi/Activator.java @@ -0,0 +1,66 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package com.acme.osgi; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; +import org.osgi.util.tracker.BundleTracker; + +/** + * Bootstrap a webapp + * + * + */ +public class Activator implements BundleActivator +{ + + /** + * + * @param context + */ + public void start(BundleContext context) throws Exception + { + WebAppContext webapp = new WebAppContext(); + Dictionary props = new Hashtable(); + props.put("war","."); + props.put("contextPath","/acme"); + //uiProps.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, serverName); + context.registerService(ContextHandler.class.getName(),webapp,props); + } + + /** + * Stop the activator. + * + * @see + * org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception + { + } +} diff --git a/jetty-osgi/test-jetty-osgi-webapp/src/main/resources/index.html b/jetty-osgi/test-jetty-osgi-webapp/src/main/resources/index.html new file mode 100644 index 00000000000..9e62c04bb91 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-webapp/src/main/resources/index.html @@ -0,0 +1,6 @@ + + +

Test OSGi WebApp

+

Webapp registered by bundle as service successfully deployed.

+ + diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 9f68f14a66c..b6cbe5962ad 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -326,6 +326,18 @@ webbundle test
+ + org.eclipse.jetty.osgi + test-jetty-osgi-context + ${project.version} + test + + + org.eclipse.jetty.osgi + test-jetty-osgi-webapp + ${project.version} + test + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-deployer.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-deployer.xml index f3faa68b832..b03a648e3d6 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-deployer.xml +++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-deployer.xml @@ -16,23 +16,6 @@ org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern .*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$ - - - - - - 0 - /contexts - - - - - - - - diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/AbstractTestOSGi.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/AbstractTestOSGi.java deleted file mode 100644 index 0c4dcb86875..00000000000 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/AbstractTestOSGi.java +++ /dev/null @@ -1,195 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.osgi.test; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.junit.Assert; -import org.ops4j.pax.exam.CoreOptions; -import org.ops4j.pax.exam.Option; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpService; - -/** - * Helper methods for pax-exam tests - */ -public class AbstractTestOSGi -{ - - private Map _bundles; - - /** - * Note: this will run many more tests. - * TODO: find a better way to control this and use non-deprecated methods. - * @param options - */ - protected static void addMoreOSGiContainers(List