listeners to be notified: E3.listeners + E1.listeners
+ *
+ *
+ *
+ * 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:
+ *
+ *
+ * @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.osgijetty-osgi-project9.0.0-SNAPSHOT
- ../pom.xml4.0.0jetty-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.osgijetty-osgi-project9.0.0-SNAPSHOT
- ../pom.xml4.0.0jetty-osgi-boot
@@ -29,10 +28,6 @@
org.eclipse.jettyjetty-jmx
-
org.eclipse.osgiorg.eclipse.osgi
@@ -104,8 +99,9 @@
+ org.eclipse.jetty.osgi.boot;singleton:=trueorg.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:
+ *
+ *
+ *
+ *
+ * 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
- *
- * 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