Merge branch 'master' into release-9

This commit is contained in:
Jesse McConnell 2013-02-21 10:01:11 -06:00
commit cd3c840553
167 changed files with 8335 additions and 4146 deletions

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LowResourceMonitor;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@ -36,6 +37,7 @@ import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
public class LikeJettyXml
{
@ -53,6 +55,8 @@ public class LikeJettyXml
server.setDumpAfterStart(false);
server.setDumpBeforeStop(false);
server.addBean(new TimerScheduler());
// Setup JMX
MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbContainer);
@ -121,6 +125,12 @@ public class LikeJettyXml
server.setStopAtShutdown(true);
LowResourceMonitor lowResourcesMonitor=new LowResourceMonitor(server);
lowResourcesMonitor.setLowResourcesIdleTimeout(1000);
lowResourcesMonitor.setMaxConnections(2);
lowResourcesMonitor.setPeriod(1200);
server.addBean(lowResourcesMonitor);
server.start();
server.join();
}

View File

@ -80,14 +80,12 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
public void onComplete(Result result)
{
Request request = result.getRequest();
HttpConversation conversation = client.getConversation(request.getConversationID(), false);
List<Response.ResponseListener> listeners = conversation.getExchanges().peekFirst().getResponseListeners();
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
if (result.isFailed())
{
Throwable failure = result.getFailure();
LOG.debug("Authentication challenge failed {}", failure);
notifier.forwardFailureComplete(listeners, request, result.getRequestFailure(), response, result.getResponseFailure());
forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
return;
}
@ -95,7 +93,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
if (wwwAuthenticates.isEmpty())
{
LOG.debug("Authentication challenge without WWW-Authenticate header");
notifier.forwardFailureComplete(listeners, request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
return;
}
@ -114,15 +112,16 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
if (authentication == null)
{
LOG.debug("No authentication available for {}", request);
notifier.forwardSuccessComplete(listeners, request, response);
forwardSuccessComplete(request, response);
return;
}
HttpConversation conversation = client.getConversation(request.getConversationID(), false);
final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
LOG.debug("Authentication result {}", authnResult);
if (authnResult == null)
{
notifier.forwardSuccessComplete(listeners, request, response);
forwardSuccessComplete(request, response);
return;
}
@ -138,6 +137,20 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
}).send(null);
}
private void forwardSuccessComplete(Request request, Response response)
{
HttpConversation conversation = client.getConversation(request.getConversationID(), false);
conversation.updateResponseListeners(null);
notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
}
private void forwardFailureComplete(Request request, Throwable requestFailure, Response response, Throwable responseFailure)
{
HttpConversation conversation = client.getConversation(request.getConversationID(), false);
conversation.updateResponseListeners(null);
notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
}
private List<WWWAuthenticate> parseWWWAuthenticate(Response response)
{
// TODO: these should be ordered by strength

View File

@ -68,25 +68,28 @@ public class ContinueProtocolHandler implements ProtocolHandler
// Mark the 100 Continue response as handled
conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
// Reset the conversation listeners, since we are going to receive another response code
conversation.updateResponseListeners(null);
HttpExchange exchange = conversation.getExchanges().peekLast();
assert exchange.getResponse() == response;
List<Response.ResponseListener> listeners = exchange.getResponseListeners();
switch (response.getStatus())
{
case 100:
{
// All good, continue
exchange.resetResponse(true);
conversation.setResponseListeners(listeners);
exchange.proceed(true);
break;
}
default:
{
// Server either does not support 100 Continue, or it does and wants to refuse the request content
// Server either does not support 100 Continue,
// or it does and wants to refuse the request content,
// or we got some other HTTP status code like a redirect.
List<Response.ResponseListener> listeners = exchange.getResponseListeners();
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
notifier.forwardSuccess(listeners, contentResponse);
conversation.setResponseListeners(listeners);
exchange.proceed(false);
break;
}
@ -99,6 +102,8 @@ public class ContinueProtocolHandler implements ProtocolHandler
HttpConversation conversation = client.getConversation(response.getConversationID(), false);
// Mark the 100 Continue response as handled
conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
// Reset the conversation listeners to allow the conversation to be completed
conversation.updateResponseListeners(null);
HttpExchange exchange = conversation.getExchanges().peekLast();
assert exchange.getResponse() == response;

View File

@ -64,6 +64,7 @@ import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -122,6 +123,7 @@ public class HttpClient extends ContainerLifeCycle
private volatile Executor executor;
private volatile ByteBufferPool byteBufferPool;
private volatile Scheduler scheduler;
private volatile SocketAddressResolver resolver;
private volatile SelectorManager selectorManager;
private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION);
private volatile boolean followRedirects = true;
@ -132,6 +134,7 @@ public class HttpClient extends ContainerLifeCycle
private volatile int maxRedirects = 8;
private volatile SocketAddress bindAddress;
private volatile long connectTimeout = 15000;
private volatile long addressResolutionTimeout = 15000;
private volatile long idleTimeout;
private volatile boolean tcpNoDelay = true;
private volatile boolean dispatchIO = true;
@ -198,6 +201,8 @@ public class HttpClient extends ContainerLifeCycle
scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
addBean(scheduler);
resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
selectorManager = newSelectorManager();
selectorManager.setConnectTimeout(getConnectTimeout());
addBean(selectorManager);
@ -439,10 +444,10 @@ public class HttpClient extends ContainerLifeCycle
*/
public Destination getDestination(String scheme, String host, int port)
{
return provideDestination(scheme, host, port);
return destinationFor(scheme, host, port);
}
protected HttpDestination provideDestination(String scheme, String host, int port)
protected HttpDestination destinationFor(String scheme, String host, int port)
{
port = normalizePort(scheme, port);
@ -480,34 +485,48 @@ public class HttpClient extends ContainerLifeCycle
if (!Arrays.asList("http", "https").contains(scheme))
throw new IllegalArgumentException("Invalid protocol " + scheme);
HttpDestination destination = provideDestination(scheme, request.getHost(), request.getPort());
HttpDestination destination = destinationFor(scheme, request.getHost(), request.getPort());
destination.send(request, listeners);
}
protected void newConnection(HttpDestination destination, Promise<Connection> promise)
protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
{
SocketChannel channel = null;
try
Destination.Address address = destination.getConnectAddress();
resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
{
channel = SocketChannel.open();
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
channel.bind(bindAddress);
configure(channel);
channel.configureBlocking(false);
channel.connect(destination.getConnectAddress());
@Override
public void succeeded(SocketAddress socketAddress)
{
SocketChannel channel = null;
try
{
channel = SocketChannel.open();
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
channel.bind(bindAddress);
configure(channel);
channel.configureBlocking(false);
channel.connect(socketAddress);
Future<Connection> result = new ConnectionCallback(destination, promise);
selectorManager.connect(channel, result);
}
// Must catch all exceptions, since some like
// UnresolvedAddressException are not IOExceptions.
catch (Exception x)
{
if (channel != null)
close(channel);
promise.failed(x);
}
Future<Connection> futureConnection = new ConnectionCallback(destination, promise);
selectorManager.connect(channel, futureConnection);
}
// Must catch all exceptions, since some like
// UnresolvedAddressException are not IOExceptions.
catch (Throwable x)
{
if (channel != null)
close(channel);
promise.failed(x);
}
}
@Override
public void failed(Throwable x)
{
promise.failed(x);
}
});
}
protected void configure(SocketChannel channel) throws SocketException
@ -544,7 +563,7 @@ public class HttpClient extends ContainerLifeCycle
protected void removeConversation(HttpConversation conversation)
{
conversations.remove(conversation.id());
conversations.remove(conversation.getID());
LOG.debug("{} removed", conversation);
}
@ -599,6 +618,22 @@ public class HttpClient extends ContainerLifeCycle
this.connectTimeout = connectTimeout;
}
/**
* @return the timeout, in milliseconds, for the DNS resolution of host addresses
*/
public long getAddressResolutionTimeout()
{
return addressResolutionTimeout;
}
/**
* @param addressResolutionTimeout the timeout, in milliseconds, for the DNS resolution of host addresses
*/
public void setAddressResolutionTimeout(long addressResolutionTimeout)
{
this.addressResolutionTimeout = addressResolutionTimeout;
}
/**
* @return the max time a connection can be idle (that is, without traffic of bytes in either direction)
*/

View File

@ -117,11 +117,15 @@ public class HttpConnection extends AbstractConnection implements Connection
}
if (listener != null)
listeners.add(listener);
send(request, listeners);
HttpConversation conversation = client.getConversation(request.getConversationID(), true);
HttpExchange exchange = new HttpExchange(conversation, getDestination(), request, listeners);
send(exchange);
}
public void send(Request request, List<Response.ResponseListener> listeners)
public void send(HttpExchange exchange)
{
Request request = exchange.getRequest();
normalizeRequest(request);
// Save the old idle timeout to restore it
@ -129,10 +133,8 @@ public class HttpConnection extends AbstractConnection implements Connection
idleTimeout = endPoint.getIdleTimeout();
endPoint.setIdleTimeout(request.getIdleTimeout());
HttpConversation conversation = client.getConversation(request.getConversationID(), true);
HttpExchange exchange = new HttpExchange(conversation, this, request, listeners);
setExchange(exchange);
conversation.getExchanges().offer(exchange);
// Associate the exchange to the connection
associate(exchange);
sender.send(exchange);
}
@ -279,12 +281,21 @@ public class HttpConnection extends AbstractConnection implements Connection
return exchange.get();
}
protected void setExchange(HttpExchange exchange)
protected void associate(HttpExchange exchange)
{
if (!this.exchange.compareAndSet(null, exchange))
throw new UnsupportedOperationException("Pipelined requests not supported");
else
LOG.debug("{} associated to {}", exchange, this);
exchange.setConnection(this);
LOG.debug("{} associated to {}", exchange, this);
}
protected HttpExchange disassociate()
{
HttpExchange exchange = this.exchange.getAndSet(null);
if (exchange != null)
exchange.setConnection(null);
LOG.debug("{} disassociated from {}", exchange, this);
return exchange;
}
@Override
@ -293,7 +304,7 @@ public class HttpConnection extends AbstractConnection implements Connection
HttpExchange exchange = getExchange();
if (exchange != null)
{
exchange.receive();
receive();
}
else
{
@ -310,7 +321,7 @@ public class HttpConnection extends AbstractConnection implements Connection
public void complete(HttpExchange exchange, boolean success)
{
HttpExchange existing = this.exchange.getAndSet(null);
HttpExchange existing = disassociate();
if (existing == exchange)
{
exchange.awaitTermination();
@ -356,13 +367,13 @@ public class HttpConnection extends AbstractConnection implements Connection
}
}
public boolean abort(HttpExchange exchange, Throwable cause)
public boolean abort(Throwable cause)
{
// We want the return value to be that of the response
// because if the response has already successfully
// arrived then we failed to abort the exchange
sender.abort(exchange, cause);
return receiver.abort(exchange, cause);
sender.abort(cause);
return receiver.abort(cause);
}
public void proceed(boolean proceed)

View File

@ -18,23 +18,20 @@
package org.eclipse.jetty.client;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
public class HttpConversation implements Attributes
public class HttpConversation extends AttributesMap
{
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
private final Deque<HttpExchange> exchanges = new ConcurrentLinkedDeque<>();
private final HttpClient client;
private final long id;
private volatile boolean complete;
private volatile List<Response.ResponseListener> listeners;
public HttpConversation(HttpClient client, long id)
@ -43,7 +40,7 @@ public class HttpConversation implements Attributes
this.id = id;
}
public long id()
public long getID()
{
return id;
}
@ -53,55 +50,119 @@ public class HttpConversation implements Attributes
return exchanges;
}
/**
* Returns the list of response listeners that needs to be notified of response events.
* This list changes as the conversation proceeds, as follows:
* <ol>
* <li>
* request R1 send => conversation.updateResponseListeners(null)
* <ul>
* <li>exchanges in conversation: E1</li>
* <li>listeners to be notified: E1.listeners</li>
* </ul>
* </li>
* <li>
* response R1 arrived, 401 => conversation.updateResponseListeners(AuthenticationProtocolHandler.listener)
* <ul>
* <li>exchanges in conversation: E1</li>
* <li>listeners to be notified: AuthenticationProtocolHandler.listener</li>
* </ul>
* </li>
* <li>
* request R2 send => conversation.updateResponseListeners(null)
* <ul>
* <li>exchanges in conversation: E1 + E2</li>
* <li>listeners to be notified: E2.listeners + E1.listeners</li>
* </ul>
* </li>
* <li>
* response R2 arrived, 302 => conversation.updateResponseListeners(RedirectProtocolHandler.listener)
* <ul>
* <li>exchanges in conversation: E1 + E2</li>
* <li>listeners to be notified: E2.listeners + RedirectProtocolHandler.listener</li>
* </ul>
* </li>
* <li>
* request R3 send => conversation.updateResponseListeners(null)
* <ul>
* <li>exchanges in conversation: E1 + E2 + E3</li>
* <li>listeners to be notified: E3.listeners + E1.listeners</li>
* </ul>
* </li>
* <li>
* response R3 arrived, 200 => conversation.updateResponseListeners(null)
* <ul>
* <li>exchanges in conversation: E1 + E2 + E3</li>
* <li>listeners to be notified: E3.listeners + E1.listeners</li>
* </ul>
* </li>
* </ol>
* 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<Response.ResponseListener> getResponseListeners()
{
return listeners;
}
public void setResponseListeners(List<Response.ResponseListener> 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<String> 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

View File

@ -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<RequestContext> requests;
private final Address address;
private final Queue<HttpExchange> exchanges;
private final BlockingQueue<Connection> idleConnections;
private final BlockingQueue<Connection> 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<Connection> newConnection()
public void newConnection(Promise<Connection> promise)
{
FuturePromise<Connection> result = new FuturePromise<>();
newConnection(new ProxyPromise(result));
return result;
createConnection(new ProxyPromise(promise));
}
protected void newConnection(Promise<Connection> promise)
protected void createConnection(Promise<Connection> 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<Response.ResponseListener> 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
* <p>If a request is waiting to be executed, it will be dequeued and executed by the new connection.</p>
*
* @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<Response.ResponseListener> 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<Response.ResponseListener> 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<Response.ResponseListener> 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<String> 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<Response.ResponseListener> listeners;
private RequestContext(Request request, List<Response.ResponseListener> 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)

View File

@ -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<Response.ResponseListener> 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<Response.ResponseListener> listeners)
public HttpExchange(HttpConversation conversation, HttpDestination destination, Request request, List<Response.ResponseListener> 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<Result> 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()

View File

@ -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<ByteBuffer>
private final AtomicReference<State> 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<ByteBuffer>
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<Response.ResponseListener> 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.<Response.ResponseListener>singletonList(handlerListener));
}
else
{
List<Response.ResponseListener> 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<ByteBuffer>
{
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<ByteBuffer>
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<String> contentEncodings = response.getHeaders().getValues(HttpHeader.CONTENT_ENCODING.asString(), ",");
if (contentEncodings != null)
@ -292,7 +269,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
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<ByteBuffer>
HttpResponse response = exchange.getResponse();
List<Response.ResponseListener> 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<ByteBuffer>
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<ByteBuffer>
{
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<ByteBuffer>
fail(new TimeoutException());
}
public boolean abort(HttpExchange exchange, Throwable cause)
public boolean abort(Throwable cause)
{
return fail(cause);
}

View File

@ -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);
}

View File

@ -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> 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<ByteBuffer> 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<Response.ResponseListener> 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.<ByteBuffer>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) ||

View File

@ -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<Response.ResponseListener> listeners = conversation.getExchanges().peekFirst().getResponseListeners();
// TODO: should we replay all events, or just the failure ?
conversation.updateResponseListeners(null);
List<Response.ResponseListener> listeners = conversation.getResponseListeners();
notifier.notifyFailure(listeners, response, failure);
notifier.notifyComplete(listeners, new Result(request, response, failure));
}

View File

@ -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<Request.RequestListener> 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<Request.Listener> 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

View File

@ -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)}.
* <p />
* {@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
{

View File

@ -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}.
* <p />
* {@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)}.
* <p />
* {@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}.
* <p />
* Use {@link FuturePromise} to wait for the connection:
* <pre>
* Destination destination = ...;
* FuturePromise&lt;Connection&gt; futureConnection = new FuturePromise&lt;&gt;();
* destination.newConnection(futureConnection);
* Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
* </pre>
*
* @param promise the promise of a new, unpooled, {@link Connection}
*/
Future<Connection> newConnection();
void newConnection(Promise<Connection> 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;
}
}
}

View File

@ -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)
{

View File

@ -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.
* <p />
* {@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.
* <p />
* 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<ByteBuffer> chunks = new ConcurrentLinkedQueue<>();
private final AtomicReference<Listener> listener = new AtomicReference<>();
private final Iterator<ByteBuffer> 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()

View File

@ -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}.
* <p />
* {@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.
* <p />
* 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.
* <p />
* 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.
* <p />
* Example usage:
* <pre>
* 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()
* {
* &#64Override
* public void onComplete(Result result)
* {
* // Your logic here
* }
* });
*
* // At a later time...
* output.write("some content".getBytes());
* }
* </pre>
*/
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<ByteBuffer> 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();
}
}
}

View File

@ -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
{

View File

@ -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);

View File

@ -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<Connection> 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<Connection> 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);

View File

@ -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)

View File

@ -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));
}
}

View File

@ -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
{

View File

@ -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<Connection> 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<Connection> futureConnection = new FuturePromise<>();
destination.newConnection(futureConnection);
try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
{
Request request = client.newRequest(destination.getHost(), destination.getPort())
.scheme(scheme)

View File

@ -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.<Response.ResponseListener>singletonList(listener));
HttpExchange exchange = new HttpExchange(conversation, destination, request, Collections.<Response.ResponseListener>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);

View File

@ -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());
}
}

View File

@ -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<Connection> 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
{

View File

@ -200,4 +200,5 @@ etc/jetty-requestlog.xml
# etc/jetty-stats.xml
# etc/jetty-debug.xml
# etc/jetty-ipaccess.xml
# etc/jetty-lowresources.xml
#===========================================================

View File

@ -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;
}
/**

View File

@ -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;
}
//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());
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 null;
}
/**
* 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());
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);
}
/**

View File

@ -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;
/**
*
@ -69,69 +73,137 @@ 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)
private Context comp;
private Object testObj = new Object();
public void contextInitialized(ServletContextEvent sce)
{
System.err.println("local unbind "+binding+" from "+ctx.getName());
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 Binding bind(NamingContext ctx, Binding binding)
public void contextDestroyed(ServletContextEvent sce)
{
System.err.println("local bind "+binding+" to "+ctx.getName());
return binding;
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);
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
}
//set the current thread's classloader
currentThread.setContextClassLoader(childLoader1);
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);
}
}
}

View File

@ -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");

View File

@ -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

View File

@ -3,7 +3,6 @@
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>9.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-osgi-boot-jsp</artifactId>

View File

@ -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<String> getTldBundles(OSGiAppProvider provider)
private static Collection<String> 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<URL> urls = new ArrayList<URL>();
// 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<String> tldbundles = getTldBundles(provider);
HashSet<URL> urls = new HashSet<URL>();
String tmp = System.getProperty(SYS_PROP_TLD_BUNDLES); //comma separated exact names
List<String> sysNames = new ArrayList<String>();
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<URL> urls) throws Exception
private void registerTldBundle(BundleFileLocatorHelper locatorHelper, Bundle bundle, Set<URL> urls) throws Exception
{
File jasperLocation = locatorHelper.getBundleInstallLocation(bundle);
if (jasperLocation.isDirectory())

View File

@ -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;
@ -56,6 +56,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto
{
private static final Logger LOG = Log.getLogger(WebappRegistrationCustomizerImpl.class);
/**
* Default name of a class that belongs to the jstl bundle. From that class
* we locate the corresponding bundle and register it as a bundle that
@ -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<URL> urls = new ArrayList<URL>();
@ -216,7 +215,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto
}
catch (Exception e)
{
LOG.warn(e);
e.printStackTrace();
}
}

View File

@ -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.
* <p>
* 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.
* </p>
@ -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);
}
}

View File

@ -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
{

View File

@ -1,126 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>7.6.10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-osgi-boot-logback</artifactId>
<name>Jetty :: OSGi :: Boot Logback</name>
<description>Jetty OSGi Boot Logback bundle</description>
<properties>
<bundle-symbolic-name>${project.groupId}.boot.logback</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
<execution>
<id>test-jar</id>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot.logback;singleton:=true</Bundle-SymbolicName>
<Bundle-Name>Jetty-OSGi-Logback Integration</Bundle-Name>
<Fragment-Host>org.eclipse.jetty.osgi.boot</Fragment-Host>
<Import-Package>
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
</Import-Package>
<Export-Package>
!org.eclipse.jetty.osgi.boot.logback.internal.*,
org.eclipse.jetty.osgi.boot.logback.*;version="${parsedVersion.osgiVersion}"
</Export-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<onlyAnalyze>org.eclipse.jetty.osgi.boot.logback.*</onlyAnalyze>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -16,23 +16,6 @@
<Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
<Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
</Call>
<!-- Providers of OSGi Apps -->
<Call name="addAppProvider">
<Arg>
<New class="org.eclipse.jetty.osgi.boot.OSGiAppProvider">
<!--
<Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-->
<Set name="scanInterval">0</Set>
<Set name="contextXmlDir"><Property name="jetty.home" default="." />/contexts</Set>
<!-- comma separated list of bundle symbolic names that contain custom tag libraries (*.tld files) -->
<!-- if those bundles don't exist or can't be loaded no errors or warning will be issued! -->
<!-- This default value plugs in the tld files of the reference implementation of JSF -->
<Set name="tldBundles"><Property name="org.eclipse.jetty.osgi.tldbundles" default="javax.faces.jsf-impl" /></Set>
</New>
</Arg>
</Call>
</New>
</Arg>
</Call>

View File

@ -1,34 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- =============================================================== -->
<!-- Configure the Jetty Server Nested inside another -->
<!-- Servlet Container. -->
<!-- -->
<!-- Documentation of this file format can be found at: -->
<!-- http://wiki.eclipse.org/Jetty/Reference/jetty.xml_syntax -->
<!-- -->
<!-- =============================================================== -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addConnector">
<Arg>
<New id="NestedConnector" class="org.eclipse.jetty.nested.NestedConnector">
<Set name="statsOn">false</Set>
<Set name="forwarded">true</Set>
<Set name="forwardedHostHeader">x-forwarded_for</Set>
<Set name="forwardedCipherSuiteHeader">sslclientcipher</Set>
<Set name="forwardedSslSessionIdHeader">sslsessionid</Set>
<Call name="addLifeCycleListener">
<Arg>
<New class="org.eclipse.jetty.osgi.nested.NestedConnectorListener" id="NestedConnectorListener">
<Set name="nestedConnector"><Ref refid="NestedConnector"/></Set>
</New>
</Arg>
</Call>
</New>
</Arg>
</Call>
</Configure>

View File

@ -57,6 +57,20 @@
<!-- =========================================================== -->
<!-- jetty-jndi by default -->
<!-- =========================================================== -->
<Call class="org.eclipse.jetty.webapp.Configuration$ClassList" name="setServerDefault">
<Arg><Ref refid="Server" /></Arg>
<Call name="addAfter">
<Arg name="afterClass">org.eclipse.jetty.webapp.FragmentConfiguration</Arg>
<Arg>
<Array type="String">
<Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
<Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
<Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
</Array>
</Arg>
</Call>
</Call>
<Call class="java.lang.System" name="setProperty">
<Arg>java.naming.factory.initial</Arg>
<Arg><Property name="java.naming.factory.initial" default="org.eclipse.jetty.jndi.InitialContextFactory"/></Arg>

View File

@ -3,7 +3,6 @@
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>9.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-osgi-boot</artifactId>
@ -29,10 +28,6 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
</dependency>
<!--dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-nested</artifactId>
</dependency-->
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
@ -104,8 +99,9 @@
</executions>
<configuration>
<instructions>
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot;singleton:=true</Bundle-SymbolicName>
<Bundle-Activator>org.eclipse.jetty.osgi.boot.JettyBootstrapActivator</Bundle-Activator>
<Export-Package>org.eclipse.jetty.osgi.boot;version="${parsedVersion.osgiVersion}",org.eclipse.jetty.osgi.boot.utils,org.eclipse.jetty.osgi.nested;version="${parsedVersion.osgiVersion}"</Export-Package>
<DynamicImport-Package>org.eclipse.jetty.*;version="[9.0,10.0)"</DynamicImport-Package>
<Import-Package>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,
*
</Import-Package>
<DynamicImport-Package>org.eclipse.jetty.*;version="9.0.0"</DynamicImport-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
</plugin>

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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<String,String> properties = new Hashtable<String,String>();
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;
}
}

View File

@ -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<URL> 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);
}
}

View File

@ -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<String, App> _appMap = new HashMap<String, App>();
private Map<Bundle, List<App>> _bundleMap = new HashMap<Bundle, List<App>>();
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<String,String> properties = new Hashtable<String,String>();
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<App> apps = _bundleMap.get(bundle);
if (apps == null)
{
apps = new ArrayList<App>();
_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<App> 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
}
}

View File

@ -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;
}

View File

@ -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<Bundle, App> _bundleMap = new HashMap<Bundle, App>();
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<String,String> properties = new Hashtable<String,String>();
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;
}
}

View File

@ -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;
}
}

View File

@ -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.
* <p>
* This provider must not be called outside of jetty.boot: it should always be
* called via the OSGi service listener.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*/
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<Bundle> _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<String, App> 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;
}
/**
* &lt;autoInstallOSGiBundles&gt;true&lt;/autoInstallOSGiBundles&gt;
*
* @param installingOSGiBundles
*/
public void setAutoInstallOSGiBundles(boolean installingOSGiBundles)
{
_autoInstallOSGiBundles = installingOSGiBundles;
}
/* ------------------------------------------------------------ */
/**
* Set the directory in which to look for context XML files.
* <p>
* 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.
* </p>
* <p>
* Also if this directory contains some osgi bundles, it will install them.
* </p>
*
* @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.
* <p>
* Really a simple trick to get going quickly with development.
* </p>
*/
@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<Bundle>();
}
_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<Bundle> 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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<Resource> frags = (List<Resource>) context.getAttribute(METAINF_FRAGMENTS);
List<Resource> resfrags = (List<Resource>) context.getAttribute(METAINF_RESOURCES);
List<Resource> tldfrags = (List<Resource>) 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<URL> resEnum = frag.findEntries("/META-INF/resources", "*", true);
Enumeration<URL> 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<Resource>();
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<Resource>();
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<Resource>();
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);
}
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -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:
* <ol>
* <li>SystemProperty SYS_PROP_TLD_BUNDLES</li>
* <li>DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN</li>
* </ol>
*
* 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<String> names = new ArrayList<String>();
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<Resource> matchingResources = new HashSet<Resource>();
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<Resource> findJars (WebAppContext context)
throws Exception
{
List<Resource> mergedResources = new ArrayList<Resource>();
//get jars from WEB-INF/lib if there are any
List<Resource> 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<String, Resource> patchResourcesPath = new TreeMap<String, Resource>();
TreeMap<String, Resource> appendedResourcesPath = new TreeMap<String, Resource>();
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<Resource>(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<Resource> getBundleAsResource(Bundle bundle)
throws Exception
{
List<Resource> resources = new ArrayList<Resource>();
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;
}
}

View File

@ -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";
/** jetty context file path */
public static final String JETTY_EXTRA_CLASSPATH = "Jetty-extraClasspath";
/**
* 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";
/** defaultweb.xml file path */
public static final String JETTY_WEB_XML_PATH = "Jetty-WebXmlFilePath";
/**
* 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"};
}

View File

@ -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<ServiceReference, App> _serviceMap = new HashMap<ServiceReference, App>();
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<String, Object>();
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<String,String> properties = new Hashtable<String,String>();
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();
}
}

View File

@ -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;
}

View File

@ -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<ServiceReference, App> _serviceMap = new HashMap<ServiceReference, App>();
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,String>();
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<String,String> properties = new Hashtable<String,String>();
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();
}
}

View File

@ -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;
/**
* 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";
public static final String JETTY_ETC_FILES = OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS;
/**
* 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())
@ -219,6 +193,8 @@ 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,7 +222,7 @@ public class DefaultJettyAtJettyHomeHelper
else
{
//relative file path
Enumeration<URL> enUrls = BundleFileLocatorHelper.DEFAULT.findEntries(configurationBundle, etcFile);
Enumeration<URL> enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, etcFile);
// default for org.eclipse.osgi.boot where we look inside
// jettyhome for the default embedded configuration.
@ -254,21 +230,22 @@ public class DefaultJettyAtJettyHomeHelper
// 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());
}
}
}
@ -276,6 +253,8 @@ public class DefaultJettyAtJettyHomeHelper
return res.toString();
}
/* ------------------------------------------------------------ */
private static void appendToCommaSeparatedList(StringBuilder buffer, String value)
{
if (buffer.length() != 0)
@ -285,6 +264,8 @@ public class DefaultJettyAtJettyHomeHelper
buffer.append(value);
}
/* ------------------------------------------------------------ */
private static void setProperty(Dictionary<String,String> properties, String key, String value)
{
if (value != null)
@ -293,6 +274,8 @@ public class DefaultJettyAtJettyHomeHelper
}
}
/* ------------------------------------------------------------ */
/**
* recursively substitute the ${sysprop} by their actual system property.
* ${sysprop,defaultvalue} will use 'defaultvalue' as the value if no

View File

@ -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;
@ -67,6 +75,8 @@ public class ServerInstanceWrapper
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
@ -85,20 +95,21 @@ public class ServerInstanceWrapper
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.
@ -110,6 +121,8 @@ public class ServerInstanceWrapper
return _commonParentClassLoaderForWebapps;
}
/* ------------------------------------------------------------ */
/**
* @return The deployment manager registered on this server.
*/
@ -118,32 +131,27 @@ 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<File> 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
@ -210,6 +218,8 @@ public class ServerInstanceWrapper
}
}
/* ------------------------------------------------------------ */
/**
* 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<URL> res = new ArrayList<URL>();
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);
@ -249,6 +267,8 @@ public class ServerInstanceWrapper
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<String, Object> id_map = new HashMap<String, Object>();
//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<String, String> properties = new HashMap<String, String>();
Enumeration<Object> en = props.keys();
while (en.hasMoreElements())
{
Object key = en.nextElement();
Object value = props.get(key);
properties.put(String.valueOf(key), String.valueOf(value));
String keyStr = String.valueOf(key);
String valStr = String.valueOf(value);
properties.put(keyStr, valStr);
server.setAttribute(keyStr, valStr);
}
for (URL jettyConfiguration : jettyConfigurations)
@ -274,6 +299,11 @@ public class ServerInstanceWrapper
{
// Execute a Jetty configuration file
Resource r = Resource.newResource(jettyConfiguration);
if (!r.exists())
{
LOG.warn("File does not exist "+r);
continue;
}
is = r.getInputStream();
XmlConfiguration config = new XmlConfiguration(is);
config.getIdMap().putAll(id_map);
@ -316,14 +346,18 @@ public class ServerInstanceWrapper
*
* It is assumed the server has already been configured with the ContextHandlerCollection structure.
*
* The server must have an instance of OSGiAppProvider. If one is not provided, it is created.
*/
private void init()
{
// Get the context handler
_ctxtHandler = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class);
_ctxtCollection = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class);
// get a deployerManager
if (_ctxtCollection == null)
throw new IllegalStateException("ERROR: No ContextHandlerCollection configured in Server");
List<String> providerClassNames = new ArrayList<String>();
// get a deployerManager and some providers
Collection<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class);
if (deployers != null && !deployers.isEmpty())
{
@ -331,30 +365,78 @@ public class ServerInstanceWrapper
for (AppProvider provider : _deploymentManager.getAppProviders())
{
if (provider instanceof OSGiAppProvider)
{
_provider = (OSGiAppProvider) provider;
break;
}
providerClassNames.add(provider.getClass().getName());
}
if (_provider == null)
}
else
{
//add some kind of default
_deploymentManager = new DeploymentManager();
_deploymentManager.setContexts(_ctxtCollection);
_server.addBean(_deploymentManager);
}
_deploymentManager.setUseStandardBindings(false);
List<AppLifeCycle.Binding> deploymentLifeCycleBindings = new ArrayList<AppLifeCycle.Binding>();
deploymentLifeCycleBindings.add(new OSGiDeployer());
deploymentLifeCycleBindings.add(new StandardStarter());
deploymentLifeCycleBindings.add(new StandardStopper());
deploymentLifeCycleBindings.add(new OSGiUndeployer());
_deploymentManager.setLifeCycleBindings(deploymentLifeCycleBindings);
if (!providerClassNames.contains(BundleWebAppProvider.class.getName()))
{
// create it on the fly with reasonable default values.
try
{
// create it on the fly with reasonable default values.
try
{
_provider = new OSGiAppProvider();
_provider.setMonitoredDirResource(Resource.newResource(getDefaultOSGiContextsHome(new File(System.getProperty("jetty.home"))).toURI()));
}
catch (IOException e)
{
LOG.warn(e);
}
_deploymentManager.addAppProvider(_provider);
BundleWebAppProvider webAppProvider = new BundleWebAppProvider(this);
_deploymentManager.addAppProvider(webAppProvider);
}
catch (Exception e)
{
LOG.warn(e);
}
}
if (_ctxtHandler == null || _provider == null) throw new IllegalStateException("ERROR: No ContextHandlerCollection or OSGiAppProvider configured");
if (!providerClassNames.contains(ServiceWebAppProvider.class.getName()))
{
// create it on the fly with reasonable default values.
try
{
ServiceWebAppProvider webAppProvider = new ServiceWebAppProvider(this);
_deploymentManager.addAppProvider(webAppProvider);
}
catch (Exception e)
{
LOG.warn(e);
}
}
if (!providerClassNames.contains(BundleContextProvider.class.getName()))
{
try
{
BundleContextProvider contextProvider = new BundleContextProvider(this);
_deploymentManager.addAppProvider(contextProvider);
}
catch (Exception e)
{
LOG.warn(e);
}
}
if (!providerClassNames.contains(ServiceContextProvider.class.getName()))
{
try
{
ServiceContextProvider contextProvider = new ServiceContextProvider(this);
_deploymentManager.addAppProvider(contextProvider);
}
catch (Exception e)
{
LOG.warn(e);
}
}
}
/**
@ -381,10 +463,6 @@ public class ServerInstanceWrapper
return new File(jettyHome, "/contexts");
}
File getOSGiContextsHome()
{
return _provider.getContextXmlDirAsFile();
}
/**
* @return the urls in this string.
@ -398,7 +476,7 @@ public class ServerInstanceWrapper
String tok = tokenizer.nextToken();
try
{
urls.add(((DefaultFileLocatorHelper) WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER).getLocalURL(new URL(tok)));
urls.add(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(new URL(tok)));
}
catch (Throwable mfe)
{
@ -422,7 +500,7 @@ public class ServerInstanceWrapper
try
{
URL url = new URL(tok);
url = ((DefaultFileLocatorHelper) WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER).getFileURL(url);
url = BundleFileLocatorHelperFactory.getFactory().getHelper().getFileURL(url);
if (url.getProtocol().equals("file"))
{
Resource res = Resource.newResource(url);

View File

@ -0,0 +1,58 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.osgi.boot.internal.webapp;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* BundleFileLocatorHelperFactory
*
* Obtain a helper for locating files based on the bundle.
*/
public class BundleFileLocatorHelperFactory
{
private static final Logger LOG = Log.getLogger(BundleFileLocatorHelperFactory.class);
private static BundleFileLocatorHelperFactory _instance = new BundleFileLocatorHelperFactory();
private BundleFileLocatorHelperFactory() {}
public static BundleFileLocatorHelperFactory getFactory()
{
return _instance;
}
public BundleFileLocatorHelper getHelper()
{
BundleFileLocatorHelper helper = BundleFileLocatorHelper.DEFAULT;
try
{
//see if a fragment has supplied an alternative
helper = (BundleFileLocatorHelper) Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance();
}
catch (Throwable t)
{
LOG.ignore(t);
}
return helper;
}
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.osgi.boot.internal.webapp;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.webapp.WebAppContext;
import org.osgi.framework.Bundle;

View File

@ -20,19 +20,25 @@ package org.eclipse.jetty.osgi.boot.internal.webapp;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.jetty.osgi.boot.BundleWebAppProvider;
import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.osgi.boot.ServiceProvider;
import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper;
import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry;
import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.webapp.WebAppContext;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@ -40,107 +46,83 @@ import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
/**
* When a {@link ContextHandler} service is activated we look into it and if the
* corresponding webapp is actually not configured then we go and register it.
* <p>
* The idea is to always go through this class when we deploy a new webapp on
* jetty.
* </p>
* <p>
* We are exposing each web-application as an OSGi service. This lets us update
* the webapps and stop/start them directly at the OSGi layer. It also give us
* many ways to declare those services: Declarative Services for example. <br/>
* It is a bit different from the way the HttpService works where we would have
* a WebappService and we woud register a webapp onto it. <br/>
* It does not go against RFC-66 nor does it prevent us from supporting the
* WebappContainer semantics.
* </p>
* JettyContextHandlerServiceTracker
*
* When a {@link ContextHandler} is activated as an osgi service we find a jetty deployer
* for it. The ContextHandler could be either a WebAppContext or any other derivative of
* ContextHandler.
*
* ContextHandlers and WebApps can also be deployed into jetty without creating them as
* osgi services. Instead, they can be deployed via manifest headers inside bundles. See
* {@link WebBundleTrackerCustomizer}.
*/
public class JettyContextHandlerServiceTracker implements ServiceListener
{
private static Logger __logger = Log.getLogger(WebBundleDeployerHelper.class.getName());
private static Logger LOG = Log.getLogger(JettyContextHandlerServiceTracker.class);
/** New style: ability to manage multiple jetty instances */
private final IManagedJettyServerRegistry _registry;
public static final String FILTER = "(objectclass=" + ServiceProvider.class.getName() + ")";
/** The context-handler to deactivate indexed by context handler */
private Map<ServiceReference, ContextHandler> _indexByServiceReference = new HashMap<ServiceReference, ContextHandler>();
/**
* The index is the bundle-symbolic-name/path/to/context/file when there is
* such thing
*/
private Map<String, ServiceReference> _indexByContextFile = new HashMap<String, ServiceReference>();
//track all instances of deployers of webapps as bundles
ServiceTracker _serviceTracker;
/** in charge of detecting changes in the osgi contexts home folder. */
private Scanner _scanner;
/* ------------------------------------------------------------ */
/**
* @param registry
*/
public JettyContextHandlerServiceTracker(IManagedJettyServerRegistry registry) throws Exception
public JettyContextHandlerServiceTracker() throws Exception
{
_registry = registry;
//track all instances of deployers of webapps
Bundle myBundle = FrameworkUtil.getBundle(this.getClass());
_serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null);
_serviceTracker.open();
}
public void stop() throws Exception
{
if (_scanner != null)
{
_scanner.stop();
}
// the class that created the server is also in charge of stopping it.
// nothing to stop in the WebappRegistrationHelper
}
/* ------------------------------------------------------------ */
/**
* @param contextHome Parent folder where the context files can override the
* context files defined in the web bundles: equivalent to the
* contexts folder in a traditional jetty installation. when
* null, just do nothing.
* @param managedServerName
* @return
*/
protected void setupContextHomeScanner(File contextHome) throws IOException
public Map<ServiceReference, ServiceProvider> getDeployers(String managedServerName)
{
if (contextHome == null) { return; }
final String osgiContextHomeFolderCanonicalPath = contextHome.getCanonicalPath();
_scanner = new Scanner();
_scanner.setRecursive(true);
_scanner.setReportExistingFilesOnStartup(false);
_scanner.addListener(new Scanner.DiscreteListener()
if (managedServerName == null)
managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME;
Map<ServiceReference, ServiceProvider> candidates = new HashMap<ServiceReference, ServiceProvider>();
ServiceReference[] references = _serviceTracker.getServiceReferences();
if (references != null)
{
public void fileAdded(String filename) throws Exception
for (ServiceReference ref:references)
{
// adding a file does not create a new app,
// it just reloads it with the new custom file.
// well, if the file does not define a context handler,
// then in fact it does remove it.
reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
String name = (String)ref.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
if (managedServerName.equalsIgnoreCase(name))
{
ServiceProvider candidate = (ServiceProvider)_serviceTracker.getService(ref);
if (candidate != null)
candidates.put(ref, candidate);
}
}
public void fileChanged(String filename) throws Exception
{
reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
}
public void fileRemoved(String filename) throws Exception
{
// removing a file does not remove the app:
// it just goes back to the default embedded in the bundle.
// well, if there was no default then it does remove it.
reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
}
});
}
return candidates;
}
/* ------------------------------------------------------------ */
/**
* Receives notification that a service has had a lifecycle change.
*
* @param ev The <code>ServiceEvent</code> object.
*/
/**
* @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
*/
public void serviceChanged(ServiceEvent ev)
{
ServiceReference sr = ev.getServiceReference();
@ -149,16 +131,31 @@ public class JettyContextHandlerServiceTracker implements ServiceListener
case ServiceEvent.MODIFIED:
case ServiceEvent.UNREGISTERING:
{
ContextHandler ctxtHandler = unregisterInIndex(ev.getServiceReference());
if (ctxtHandler != null && !ctxtHandler.isStopped())
BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext();
ContextHandler contextHandler = (ContextHandler) context.getService(sr);
//if this was not a service that another of our deployers may have deployed (in which case they will undeploy it)
String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK);
//Get a jetty deployer targetted to the named server instance, or the default one if not named
//The individual deployer will decide if it can remove the context or not
String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName);
if (candidates != null)
{
try
boolean removed = false;
Iterator<Entry<ServiceReference, ServiceProvider>> itor = candidates.entrySet().iterator();
while (!removed && itor.hasNext())
{
getWebBundleDeployerHelp(sr).unregister(ctxtHandler);
}
catch (Exception e)
{
__logger.warn(e);
Entry<ServiceReference, ServiceProvider> e = itor.next();
try
{
removed = e.getValue().serviceRemoved(sr, contextHandler);
}
catch (Exception x)
{
LOG.warn("Error undeploying service representing jetty context ", x);
}
}
}
}
@ -181,210 +178,34 @@ public class JettyContextHandlerServiceTracker implements ServiceListener
// is configured elsewhere.
return;
}
String contextFilePath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH);
if (contextHandler instanceof WebAppContext && contextFilePath == null)
// it could be a web-application that will in fact be configured
// via a context file.
// that case is identified by the fact that the contextFilePath
// is not null
// in that case we must use the register context methods.
{
WebAppContext webapp = (WebAppContext) contextHandler;
String contextPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH);
if (contextPath == null)
{
contextPath = webapp.getContextPath();
}
String webXmlPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WEB_XML_PATH);
if (webXmlPath == null)
{
webXmlPath = webapp.getDescriptor();
}
String defaultWebXmlPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_DEFAULT_WEB_XML_PATH);
if (defaultWebXmlPath == null)
{
String jettyHome = System.getProperty(DefaultJettyAtJettyHomeHelper.SYS_PROP_JETTY_HOME);
if (jettyHome != null)
{
File etc = new File(jettyHome, "etc");
if (etc.exists() && etc.isDirectory())
{
File webDefault = new File(etc, "webdefault.xml");
if (webDefault.exists())
defaultWebXmlPath = webDefault.getAbsolutePath();
else
defaultWebXmlPath = webapp.getDefaultsDescriptor();
}
else
defaultWebXmlPath = webapp.getDefaultsDescriptor();
}
}
String war = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WAR);
try
{
IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr);
if (deployerHelper == null)
{
String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK);
if (watermark != null && !"".equals(watermark))
return; //one of our deployers just registered the context as an OSGi service, so we can ignore it
}
else
{
WebAppContext handler = deployerHelper.registerWebapplication(contributor,
war,
contextPath,
(String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH),
(String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE),
(String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE),
webXmlPath, defaultWebXmlPath, webapp);
if (handler != null)
{
registerInIndex(handler, sr);
}
}
}
catch (Throwable e)
{
__logger.warn(e);
}
}
else
//Get a jetty deployer targetted to the named server instance, or the default one if not named
String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName);
if (candidates != null)
{
// consider this just an empty skeleton:
if (contextFilePath == null) { throw new IllegalArgumentException("the property contextFilePath is required"); }
try
boolean added = false;
Iterator<Entry<ServiceReference, ServiceProvider>> itor = candidates.entrySet().iterator();
while (!added && itor.hasNext())
{
IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr);
if (deployerHelper == null)
Entry<ServiceReference, ServiceProvider> e = itor.next();
try
{
// more warnings?
added = e.getValue().serviceAdded(sr, contextHandler);
if (added && LOG.isDebugEnabled())
LOG.debug("Provider "+e.getValue()+" deployed "+contextHandler);
}
else
catch (Exception x)
{
if (Boolean.TRUE.toString().equals(sr.getProperty(IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE)))
{
contextHandler = null;
}
ContextHandler handler = deployerHelper.registerContext(contributor,
contextFilePath,
(String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH),
(String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE),
(String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE),
contextHandler);
if (handler != null)
{
registerInIndex(handler, sr);
}
LOG.warn("Error deploying service representing jetty context", x);
}
}
catch (Throwable e)
{
__logger.warn(e);
}
}
break;
}
break;
}
}
private void registerInIndex(ContextHandler handler, ServiceReference sr)
{
_indexByServiceReference.put(sr, handler);
String key = getSymbolicNameAndContextFileKey(sr);
if (key != null)
{
_indexByContextFile.put(key, sr);
}
}
/**
* Returns the ContextHandler to stop.
*
* @param reg
* @return the ContextHandler to stop.
*/
private ContextHandler unregisterInIndex(ServiceReference sr)
{
ContextHandler handler = _indexByServiceReference.remove(sr);
String key = getSymbolicNameAndContextFileKey(sr);
if (key != null)
{
_indexByContextFile.remove(key);
}
if (handler == null)
{
// a warning?
return null;
}
return handler;
}
/**
* @param sr
* @return The key for a context file within the osgi contexts home folder.
*/
private String getSymbolicNameAndContextFileKey(ServiceReference sr)
{
String contextFilePath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH);
if (contextFilePath != null) { return sr.getBundle().getSymbolicName() + "/" + contextFilePath; }
return null;
}
/**
* Called by the scanner when one of the context files is changed.
*
* @param contextFileFully
*/
public void reloadJettyContextHandler(String canonicalNameOfFileChanged, String osgiContextHomeFolderCanonicalPath)
{
String key = getNormalizedRelativePath(canonicalNameOfFileChanged, osgiContextHomeFolderCanonicalPath);
if (key == null) { return; }
ServiceReference sr = _indexByContextFile.get(key);
if (sr == null)
{
// nothing to do?
return;
}
serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, sr));
}
/**
* @param canFilename
* @return
*/
private String getNormalizedRelativePath(String canFilename, String osgiContextHomeFolderCanonicalPath)
{
if (!canFilename.startsWith(osgiContextHomeFolderCanonicalPath))
{
// why are we here: this does not look like a child of the osgi
// contexts home.
// warning?
return null;
}
return canFilename.substring(osgiContextHomeFolderCanonicalPath.length()).replace('\\', '/');
}
/**
* @return The server on which this webapp is meant to be deployed
*/
private ServerInstanceWrapper getServerInstanceWrapper(String managedServerName)
{
if (_registry == null) { return null; }
if (managedServerName == null)
{
managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME;
}
ServerInstanceWrapper wrapper = _registry.getServerInstanceWrapper(managedServerName);
// System.err.println("Returning " + managedServerName + " = " +
// wrapper);
return wrapper;
}
private IWebBundleDeployerHelper getWebBundleDeployerHelp(ServiceReference sr)
{
if (_registry == null) { return null; }
String managedServerName = (String) sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
ServerInstanceWrapper wrapper = getServerInstanceWrapper(managedServerName);
return wrapper != null ? wrapper.getWebBundleDeployerHelp() : null;
}
}

View File

@ -34,6 +34,7 @@ import java.util.jar.JarFile;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper;
import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperFactory;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@ -86,12 +87,12 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
* @param contributor The bundle that defines this web-application.
* @throws IOException
*/
public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor, BundleClassLoaderHelper bundleClassLoaderHelper)
public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor)
throws IOException
{
super(parent, context);
_contributor = contributor;
_osgiBundleClassLoader = bundleClassLoaderHelper.getBundleClassLoader(contributor);
_osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(contributor);
}
/**

View File

@ -1,909 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.osgi.boot.internal.webapp;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
import org.eclipse.jetty.osgi.boot.utils.internal.DefaultBundleClassLoaderHelper;
import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper;
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.TagLibConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleReference;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.util.tracker.ServiceTracker;
import org.xml.sax.SAXException;
/**
* Bridges the jetty deployers with the OSGi lifecycle where applications are
* managed inside OSGi-bundles.
* <p>
* This class should be called as a consequence of the activation of a new
* service that is a ContextHandler.<br/>
* This way the new webapps are exposed as OSGi services.
* </p>
* <p>
* Helper methods to register a bundle that is a web-application or a context.
* </p>
* Limitations:
* <ul>
* <li>support for jarred webapps is somewhat limited.</li>
* </ul>
*/
public class WebBundleDeployerHelper implements IWebBundleDeployerHelper
{
private static Logger __logger = Log.getLogger(WebBundleDeployerHelper.class.getName());
private static boolean INITIALIZED = false;
/**
* By default set to: {@link DefaultBundleClassLoaderHelper}. It supports
* equinox and apache-felix fragment bundles that are specific to an OSGi
* implementation should set a different implementation.
*/
public static BundleClassLoaderHelper BUNDLE_CLASS_LOADER_HELPER = null;
/**
* By default set to: {@link DefaultBundleClassLoaderHelper}. It supports
* equinox and apache-felix fragment bundles that are specific to an OSGi
* implementation should set a different implementation.
*/
public static BundleFileLocatorHelper BUNDLE_FILE_LOCATOR_HELPER = null;
/**
* By default set to: {@link DefaultBundleClassLoaderHelper}. It supports
* equinox and apache-felix fragment bundles that are specific to an OSGi
* implementation should set a different implementation.
* <p>
* Several of those objects can be added here: For example we could have an
* optional fragment that setups a specific implementation of JSF for the
* whole of jetty-osgi.
* </p>
*/
public static Collection<WebappRegistrationCustomizer> JSP_REGISTRATION_HELPERS = new ArrayList<WebappRegistrationCustomizer>();
/**
* this class loader loads the jars inside {$jetty.home}/lib/ext it is meant
* as a migration path and for jars that are not OSGi ready. also gives
* access to the jsp jars.
*/
// private URLClassLoader _libExtClassLoader;
private ServerInstanceWrapper _wrapper;
public WebBundleDeployerHelper(ServerInstanceWrapper wrapper)
{
staticInit();
_wrapper = wrapper;
}
// Inject the customizing classes that might be defined in fragment bundles.
public static synchronized void staticInit()
{
if (!INITIALIZED)
{
INITIALIZED = true;
// setup the custom BundleClassLoaderHelper
try
{
BUNDLE_CLASS_LOADER_HELPER = (BundleClassLoaderHelper) Class.forName(BundleClassLoaderHelper.CLASS_NAME).newInstance();
}
catch (Throwable t)
{
// System.err.println("support for equinox and felix");
BUNDLE_CLASS_LOADER_HELPER = new DefaultBundleClassLoaderHelper();
}
// setup the custom FileLocatorHelper
try
{
BUNDLE_FILE_LOCATOR_HELPER = (BundleFileLocatorHelper) Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance();
}
catch (Throwable t)
{
// System.err.println("no jsp/jasper support");
BUNDLE_FILE_LOCATOR_HELPER = new DefaultFileLocatorHelper();
}
}
}
/**
* Deploy a new web application on the jetty server.
*
* @param bundle The bundle
* @param webappFolderPath The path to the root of the webapp. Must be a
* path relative to bundle; either an absolute path.
* @param contextPath The context path. Must start with "/"
* @param extraClasspath
* @param overrideBundleInstallLocation
* @param requireTldBundle The list of bundles's symbolic names that contain
* tld files that are required by this WAB.
* @param webXmlPath
* @param defaultWebXmlPath TODO: parameter description
* @return The contexthandler created and started
* @throws Exception
*/
public WebAppContext registerWebapplication(Bundle bundle, String webappFolderPath, String contextPath, String extraClasspath,
String overrideBundleInstallLocation, String requireTldBundle, String webXmlPath, String defaultWebXmlPath,
WebAppContext webAppContext)
throws Exception
{
File bundleInstall = overrideBundleInstallLocation == null ? BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(bundle) : new File(overrideBundleInstallLocation);
File webapp = null;
URL baseWebappInstallURL = null;
if (webappFolderPath != null && webappFolderPath.length() != 0 && !webappFolderPath.equals("."))
{
if (webappFolderPath.startsWith("/") || webappFolderPath.startsWith("file:"))
{
webapp = new File(webappFolderPath);
}
else if (bundleInstall != null && bundleInstall.isDirectory())
{
webapp = new File(bundleInstall, webappFolderPath);
}
else if (bundleInstall != null)
{
Enumeration<URL> urls = BUNDLE_FILE_LOCATOR_HELPER.findEntries(bundle, webappFolderPath);
if (urls != null && urls.hasMoreElements())
{
baseWebappInstallURL = urls.nextElement();
}
}
}
else
{
webapp = bundleInstall;
}
if (baseWebappInstallURL == null && (webapp == null || !webapp.exists()))
{
throw new IllegalArgumentException("Unable to locate " + webappFolderPath
+ " inside "
+ (bundleInstall != null ? bundleInstall.getAbsolutePath() : "unlocated bundle '" + bundle.getSymbolicName()+ "'"));
}
if (baseWebappInstallURL == null && webapp != null)
{
baseWebappInstallURL = webapp.toURI().toURL();
}
return registerWebapplication(bundle, webappFolderPath, baseWebappInstallURL, contextPath, extraClasspath, bundleInstall, requireTldBundle, webXmlPath,
defaultWebXmlPath, webAppContext);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper#
* registerWebapplication(org.osgi.framework.Bundle, java.lang.String,
* java.io.File, java.lang.String, java.lang.String, java.io.File,
* java.lang.String, java.lang.String)
*/
private WebAppContext registerWebapplication(Bundle contributor, String pathInBundleToWebApp, URL baseWebappInstallURL, String contextPath,
String extraClasspath, File bundleInstall, String requireTldBundle, String webXmlPath,
String defaultWebXmlPath, WebAppContext context)
throws Exception
{
ClassLoader contextCl = Thread.currentThread().getContextClassLoader();
String[] oldServerClasses = null;
try
{
// apply any META-INF/context.xml file that is found to configure
// the webapp first
applyMetaInfContextXml(contributor, context);
// make sure we provide access to all the jetty bundles by going
// through this bundle.
OSGiWebappClassLoader composite = createWebappClassLoader(contributor);
// configure with access to all jetty classes and also all the
// classes
// that the contributor gives access to.
Thread.currentThread().setContextClassLoader(composite);
// converts bundleentry: protocol
baseWebappInstallURL = DefaultFileLocatorHelper.getLocalURL(baseWebappInstallURL);
context.setWar(baseWebappInstallURL.toString());
context.setContextPath(contextPath);
context.setExtraClasspath(extraClasspath);
if (webXmlPath != null && webXmlPath.length() != 0)
{
File webXml = null;
if (webXmlPath.startsWith("/") || webXmlPath.startsWith("file:/"))
{
webXml = new File(webXmlPath);
}
else
{
webXml = new File(bundleInstall, webXmlPath);
}
if (webXml.exists())
{
context.setDescriptor(webXml.getAbsolutePath());
}
}
if (defaultWebXmlPath == null || defaultWebXmlPath.length() == 0)
{
// use the one defined by the OSGiAppProvider.
defaultWebXmlPath = _wrapper.getOSGiAppProvider().getDefaultsDescriptor();
}
if (defaultWebXmlPath != null && defaultWebXmlPath.length() != 0)
{
File defaultWebXml = null;
if (defaultWebXmlPath.startsWith("/") || defaultWebXmlPath.startsWith("file:/"))
{
defaultWebXml = new File(defaultWebXmlPath);
}
else
{
defaultWebXml = new File(bundleInstall, defaultWebXmlPath);
}
if (defaultWebXml.exists())
{
context.setDefaultsDescriptor(defaultWebXml.getAbsolutePath());
}
}
// other parameters that might be defines on the OSGiAppProvider:
context.setParentLoaderPriority(_wrapper.getOSGiAppProvider().isParentLoaderPriority());
configureWebappClassLoader(contributor, context, composite, requireTldBundle);
configureWebAppContext(context, contributor, requireTldBundle);
// @see
// org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext)
// during initialization of the webapp all the jetty packages are
// visible
// through the webapp classloader.
oldServerClasses = context.getServerClasses();
context.setServerClasses(null);
_wrapper.getOSGiAppProvider().addContext(contributor, pathInBundleToWebApp, context);
// support for patch resources. ideally this should be done inside a
// configurator.
List<Resource> patchResources = (List<Resource>) context.getAttribute(WebInfConfiguration.RESOURCE_URLS + ".patch");
if (patchResources != null)
{
LinkedList<Resource> resourcesPath = new LinkedList<Resource>();
// place the patch resources at the beginning of the lookup
// path.
resourcesPath.addAll(patchResources);
// then place the ones from the host web bundle.
Resource hostResources = context.getBaseResource();
if (hostResources instanceof ResourceCollection)
{
for (Resource re : ((ResourceCollection) hostResources).getResources())
{
resourcesPath.add(re);
}
}
else
{
resourcesPath.add(hostResources);
}
ResourceCollection rc = new ResourceCollection(resourcesPath.toArray(new Resource[resourcesPath.size()]));
context.setBaseResource(rc);
}
return context;
}
finally
{
if (context != null && oldServerClasses != null)
{
context.setServerClasses(oldServerClasses);
}
Thread.currentThread().setContextClassLoader(contextCl);
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper#
* unregister(org.eclipse.jetty.server.handler.ContextHandler)
*/
public void unregister(ContextHandler contextHandler)
throws Exception
{
_wrapper.getOSGiAppProvider().removeContext(contextHandler);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper#
* registerContext(org.osgi.framework.Bundle, java.lang.String,
* java.lang.String, java.lang.String)
*/
public ContextHandler registerContext(Bundle contributor, String contextFileRelativePath, String extraClasspath, String overrideBundleInstallLocation,
String requireTldBundle, ContextHandler handler)
throws Exception
{
File contextsHome = _wrapper.getOSGiAppProvider().getContextXmlDirAsFile();
if (contextsHome != null)
{
File prodContextFile = new File(contextsHome, contributor.getSymbolicName() + "/" + contextFileRelativePath);
if (prodContextFile.exists()) { return registerContext(contributor, contextFileRelativePath, prodContextFile, extraClasspath,
overrideBundleInstallLocation, requireTldBundle, handler); }
}
File rootFolder = overrideBundleInstallLocation != null ? Resource.newResource(overrideBundleInstallLocation).getFile() : BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(contributor);
File contextFile = rootFolder != null ? new File(rootFolder, contextFileRelativePath) : null;
if (contextFile != null && contextFile.exists())
{
return registerContext(contributor, contextFileRelativePath, contextFile, extraClasspath, overrideBundleInstallLocation, requireTldBundle, handler);
}
else
{
if (contextFileRelativePath.startsWith("./"))
{
contextFileRelativePath = contextFileRelativePath.substring(1);
}
if (!contextFileRelativePath.startsWith("/"))
{
contextFileRelativePath = "/" + contextFileRelativePath;
}
URL contextURL = contributor.getEntry(contextFileRelativePath);
if (contextURL != null)
{
Resource r = Resource.newResource(contextURL);
return registerContext(contributor, contextFileRelativePath, r.getInputStream(), extraClasspath, overrideBundleInstallLocation,
requireTldBundle, handler);
}
throw new IllegalArgumentException("Could not find the context " + "file "
+ contextFileRelativePath
+ " for the bundle "
+ contributor.getSymbolicName()
+ (overrideBundleInstallLocation != null ? " using the install location " + overrideBundleInstallLocation : ""));
}
}
/**
* This type of registration relies on jetty's complete context xml file.
* Context encompasses jndi and all other things. This makes the definition
* of the webapp a lot more self-contained.
*
* @param webapp
* @param contextPath
* @param classInBundle
* @throws Exception
*/
private ContextHandler registerContext(Bundle contributor, String pathInBundle, File contextFile, String extraClasspath,
String overrideBundleInstallLocation, String requireTldBundle, ContextHandler handler)
throws Exception
{
InputStream contextFileInputStream = null;
try
{
contextFileInputStream = new BufferedInputStream(new FileInputStream(contextFile));
return registerContext(contributor, pathInBundle, contextFileInputStream, extraClasspath, overrideBundleInstallLocation, requireTldBundle, handler);
}
finally
{
IO.close(contextFileInputStream);
}
}
/**
* @param contributor
* @param contextFileInputStream
* @return The ContextHandler created and registered or null if it did not
* happen.
* @throws Exception
*/
private ContextHandler registerContext(Bundle contributor, String pathInsideBundle, InputStream contextFileInputStream, String extraClasspath,
String overrideBundleInstallLocation, String requireTldBundle, ContextHandler handler)
throws Exception
{
ClassLoader contextCl = Thread.currentThread().getContextClassLoader();
String[] oldServerClasses = null;
WebAppContext webAppContext = null;
try
{
// make sure we provide access to all the jetty bundles by going
// through this bundle.
OSGiWebappClassLoader composite = createWebappClassLoader(contributor);
// configure with access to all jetty classes and also all the
// classes
// that the contributor gives access to.
Thread.currentThread().setContextClassLoader(composite);
ContextHandler context = createContextHandler(handler, contributor, contextFileInputStream, extraClasspath, overrideBundleInstallLocation,
requireTldBundle);
if (context == null) { return null;// did not happen
}
// ok now register this webapp. we checked when we started jetty
// that there
// was at least one such handler for webapps.
// the actual registration must happen via the new Deployment API.
// _ctxtHandler.addHandler(context);
configureWebappClassLoader(contributor, context, composite, requireTldBundle);
if (context instanceof WebAppContext)
{
webAppContext = (WebAppContext) context;
// @see
// org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext)
oldServerClasses = webAppContext.getServerClasses();
webAppContext.setServerClasses(null);
}
_wrapper.getOSGiAppProvider().addContext(contributor, pathInsideBundle, context);
return context;
}
finally
{
if (webAppContext != null)
{
webAppContext.setServerClasses(oldServerClasses);
}
Thread.currentThread().setContextClassLoader(contextCl);
}
}
/**
* Applies the properties of WebAppDeployer as defined in jetty.xml.
*
* @see {WebAppDeployer#scan} around the comment
* <code>// configure it</code>
*/
protected void configureWebAppContext(ContextHandler wah, Bundle contributor, String requireTldBundle)
throws IOException
{
// rfc66
wah.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT, contributor.getBundleContext());
// spring-dm-1.2.1 looks for the BundleContext as a different attribute.
// not a spec... but if we want to support
// org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext
// then we need to do this to:
wah.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), contributor.getBundleContext());
// also pass the bundle directly. sometimes a bundle does not have a
// bundlecontext.
// it is still useful to have access to the Bundle from the servlet
// context.
wah.setAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE, contributor);
// pass the value of the require tld bundle so that the
// TagLibOSGiConfiguration
// can pick it up.
wah.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundle);
Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles(contributor);
if (fragments != null && fragments.length != 0)
{
// sorted extra resource base found in the fragments.
// the resources are either overriding the resourcebase found in the
// web-bundle
// or appended.
// amongst each resource we sort them according to the alphabetical
// order
// of the name of the internal folder and the symbolic name of the
// fragment.
// this is useful to make sure that the lookup path of those
// resource base defined by fragments is always the same.
// This natural order could be abused to define the order in which
// the base resources are
// looked up.
TreeMap<String, Resource> patchResourcesPath = new TreeMap<String, Resource>();
TreeMap<String, Resource> appendedResourcesPath = new TreeMap<String, Resource>();
for (Bundle frag : fragments)
{
String fragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_FRAGMENT_FOLDER_PATH);
String patchFragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH);
if (fragFolder != null)
{
URL fragUrl = frag.getEntry(fragFolder);
if (fragUrl == null) { throw new IllegalArgumentException("Unable to locate " + fragFolder
+ " inside "
+ " the fragment '"
+ frag.getSymbolicName()
+ "'"); }
fragUrl = DefaultFileLocatorHelper.getLocalURL(fragUrl);
String key = fragFolder.startsWith("/") ? fragFolder.substring(1) : fragFolder;
appendedResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(fragUrl));
}
if (patchFragFolder != null)
{
URL patchFragUrl = frag.getEntry(patchFragFolder);
if (patchFragUrl == null) { throw new IllegalArgumentException("Unable to locate " + patchFragUrl
+ " inside "
+ " the fragment '"
+ frag.getSymbolicName()
+ "'"); }
patchFragUrl = DefaultFileLocatorHelper.getLocalURL(patchFragUrl);
String key = patchFragFolder.startsWith("/") ? patchFragFolder.substring(1) : patchFragFolder;
patchResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(patchFragUrl));
}
}
if (!appendedResourcesPath.isEmpty())
{
wah.setAttribute(WebInfConfiguration.RESOURCE_URLS, new ArrayList<Resource>(appendedResourcesPath.values()));
}
if (!patchResourcesPath.isEmpty())
{
wah.setAttribute(WebInfConfiguration.RESOURCE_URLS + ".patch", new ArrayList<Resource>(patchResourcesPath.values()));
}
if (wah instanceof WebAppContext)
{
// This is the equivalent of what MetaInfConfiguration does. For
// OSGi bundles without the JarScanner
WebAppContext webappCtxt = (WebAppContext) wah;
// take care of the web-fragments, meta-inf resources and tld
// resources:
// similar to what MetaInfConfiguration does.
List<Resource> frags = (List<Resource>) wah.getAttribute(FragmentConfiguration.FRAGMENT_RESOURCES);
List<Resource> resfrags = (List<Resource>) wah.getAttribute(WebInfConfiguration.RESOURCE_URLS);
List<Resource> tldfrags = (List<Resource>) wah.getAttribute(TagLibConfiguration.TLD_RESOURCES);
for (Bundle frag : fragments)
{
URL webFrag = frag.getEntry("/META-INF/web-fragment.xml");
Enumeration<URL> resEnum = frag.findEntries("/META-INF/resources", "*", true);
Enumeration<URL> tldEnum = frag.findEntries("/META-INF", "*.tld", false);
if (webFrag != null || (resEnum != null && resEnum.hasMoreElements()) || (tldEnum != null && tldEnum.hasMoreElements()))
{
try
{
File fragFile = BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(frag);
// add it to the webinf jars collection:
// no need to check that it was not there yet: it
// was not there yet for sure.
Resource fragFileAsResource = Resource.newResource(fragFile.toURI());
webappCtxt.getMetaData().addWebInfJar(fragFileAsResource);
if (webFrag != null)
{
if (frags == null)
{
frags = new ArrayList<Resource>();
wah.setAttribute(FragmentConfiguration.FRAGMENT_RESOURCES, frags);
}
frags.add(fragFileAsResource);
}
if (resEnum != null && resEnum.hasMoreElements())
{
URL resourcesEntry = frag.getEntry("/META-INF/resources/");
if (resourcesEntry == null)
{
// probably we found some fragments to a
// bundle.
// those are already contributed.
// so we skip this.
}
else
{
if (resfrags == null)
{
resfrags = new ArrayList<Resource>();
wah.setAttribute(WebInfConfiguration.RESOURCE_URLS, resfrags);
}
resfrags.add(Resource.newResource(DefaultFileLocatorHelper.getLocalURL(resourcesEntry)));
}
}
if (tldEnum != null && tldEnum.hasMoreElements())
{
if (tldfrags == null)
{
tldfrags = new ArrayList<Resource>();
wah.setAttribute(TagLibConfiguration.TLD_RESOURCES, tldfrags);
}
while (tldEnum.hasMoreElements())
{
URL tldUrl = tldEnum.nextElement();
tldfrags.add(Resource.newResource(DefaultFileLocatorHelper.getLocalURL(tldUrl)));
}
}
}
catch (Exception e)
{
__logger.warn("Unable to locate the bundle " + frag.getBundleId(), e);
}
}
}
}
}
}
/**
* @See {@link ContextDeployer#scan}
* @param contextFile
* @return
*/
protected ContextHandler createContextHandler(ContextHandler handlerToConfigure, Bundle bundle, File contextFile, String extraClasspath,
String overrideBundleInstallLocation, String requireTldBundle)
{
try
{
return createContextHandler(handlerToConfigure, bundle, new BufferedInputStream(new FileInputStream(contextFile)), extraClasspath,
overrideBundleInstallLocation, requireTldBundle);
}
catch (FileNotFoundException e)
{
__logger.warn(e);
}
return null;
}
/**
* @See {@link ContextDeployer#scan}
* @param contextFile
* @return
*/
@SuppressWarnings("unchecked")
protected ContextHandler createContextHandler(ContextHandler handlerToConfigure, Bundle bundle, InputStream contextInputStream, String extraClasspath,
String overrideBundleInstallLocation, String requireTldBundle)
{
/*
* Do something identical to what the ContextProvider would have done:
* XmlConfiguration xmlConfiguration=new
* XmlConfiguration(resource.getURL()); HashMap properties = new
* HashMap(); properties.put("Server", _contexts.getServer()); if
* (_configMgr!=null) properties.putAll(_configMgr.getProperties());
*
* xmlConfiguration.setProperties(properties); ContextHandler
* context=(ContextHandler)xmlConfiguration.configure();
* context.setAttributes(new AttributesMap(_contextAttributes));
*/
try
{
XmlConfiguration xmlConfiguration = new XmlConfiguration(contextInputStream);
HashMap properties = new HashMap();
properties.put("Server", _wrapper.getServer());
// insert the bundle's location as a property.
setThisBundleHomeProperty(bundle, properties, overrideBundleInstallLocation);
xmlConfiguration.getProperties().putAll(properties);
ContextHandler context = null;
if (handlerToConfigure == null)
{
context = (ContextHandler) xmlConfiguration.configure();
}
else
{
xmlConfiguration.configure(handlerToConfigure);
context = handlerToConfigure;
}
if (context instanceof WebAppContext)
{
((WebAppContext) context).setExtraClasspath(extraClasspath);
((WebAppContext) context).setParentLoaderPriority(_wrapper.getOSGiAppProvider().isParentLoaderPriority());
if (_wrapper.getOSGiAppProvider().getDefaultsDescriptor() != null && _wrapper.getOSGiAppProvider().getDefaultsDescriptor().length() != 0)
{
((WebAppContext) context).setDefaultsDescriptor(_wrapper.getOSGiAppProvider().getDefaultsDescriptor());
}
}
configureWebAppContext(context, bundle, requireTldBundle);
return context;
}
catch (FileNotFoundException e)
{
return null;
}
catch (SAXException e)
{
__logger.warn(e);
}
catch (IOException e)
{
__logger.warn(e);
}
catch (Throwable e)
{
__logger.warn(e);
}
finally
{
IO.close(contextInputStream);
}
return null;
}
/**
* Configure a classloader onto the context. If the context is a
* WebAppContext, build a WebAppClassLoader that has access to all the jetty
* classes thanks to the classloader of the JettyBootStrapper bundle and
* also has access to the classloader of the bundle that defines this
* context.
* <p>
* If the context is not a WebAppContext, same but with a simpler
* URLClassLoader. Note that the URLClassLoader is pretty much fake: it
* delegate all actual classloading to the parent classloaders.
* </p>
* <p>
* The URL[] returned by the URLClassLoader create contained specifically
* the jars that some j2ee tools expect and look into. For example the jars
* that contain tld files for jasper's jstl support.
* </p>
* <p>
* Also as the jars in the lib folder and the classes in the classes folder
* might already be in the OSGi classloader we filter them out of the
* WebAppClassLoader
* </p>
*
* @param context
* @param contributor
* @param webapp
* @param contextPath
* @param classInBundle
* @throws Exception
*/
protected void configureWebappClassLoader(Bundle contributor, ContextHandler context, OSGiWebappClassLoader webappClassLoader, String requireTldBundle)
throws Exception
{
if (context instanceof WebAppContext)
{
WebAppContext webappCtxt = (WebAppContext) context;
context.setClassLoader(webappClassLoader);
webappClassLoader.setWebappContext(webappCtxt);
String pathsToRequiredBundles = getPathsToRequiredBundles(context, contributor, requireTldBundle);
if (pathsToRequiredBundles != null) webappClassLoader.addClassPath(pathsToRequiredBundles);
}
else
{
context.setClassLoader(webappClassLoader);
}
}
/**
* No matter what the type of webapp, we create a WebappClassLoader.
*/
protected OSGiWebappClassLoader createWebappClassLoader(Bundle contributor)
throws Exception
{
// we use a temporary WebAppContext object.
// if this is a real webapp we will set it on it a bit later: once we
// know.
OSGiWebappClassLoader webappClassLoader = new OSGiWebappClassLoader(_wrapper.getParentClassLoaderForWebapps(), new WebAppContext(), contributor,
BUNDLE_CLASS_LOADER_HELPER);
return webappClassLoader;
}
protected void applyMetaInfContextXml(Bundle bundle, ContextHandler contextHandler)
throws Exception
{
if (bundle == null) return;
if (contextHandler == null) return;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
__logger.info("Context classloader = " + cl);
try
{
Thread.currentThread().setContextClassLoader(_wrapper.getParentClassLoaderForWebapps());
// find if there is a META-INF/context.xml file
URL contextXmlUrl = bundle.getEntry("/META-INF/jetty-webapp-context.xml");
if (contextXmlUrl == null) return;
// Apply it just as the standard jetty ContextProvider would do
__logger.info("Applying " + contextXmlUrl + " to " + contextHandler);
XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXmlUrl);
HashMap properties = new HashMap();
properties.put("Server", _wrapper.getServer());
xmlConfiguration.getProperties().putAll(properties);
xmlConfiguration.configure(contextHandler);
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
}
/**
* Set the property &quot;this.bundle.install&quot; to point to the location
* of the bundle. Useful when <SystemProperty name="this.bundle.home"/> is
* used.
*/
private void setThisBundleHomeProperty(Bundle bundle, HashMap<String, Object> properties, String overrideBundleInstallLocation)
{
try
{
File location = overrideBundleInstallLocation != null ? new File(overrideBundleInstallLocation) : BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(bundle);
properties.put("this.bundle.install", location.getCanonicalPath());
properties.put("this.bundle.install.url", bundle.getEntry("/").toString());
}
catch (Throwable t)
{
__logger.warn("Unable to set 'this.bundle.install' " + " for the bundle " + bundle.getSymbolicName(), t);
}
}
private String getPathsToRequiredBundles(ContextHandler context, Bundle bundle, String requireTldBundle)
throws Exception
{
if (requireTldBundle == null) return null;
StringBuilder paths = new StringBuilder();
PackageAdmin packAdmin = getBundleAdmin();
DefaultFileLocatorHelper fileLocatorHelper = new DefaultFileLocatorHelper();
String[] symbNames = requireTldBundle.split(", ");
for (String symbName : symbNames)
{
Bundle[] bs = packAdmin.getBundles(symbName, null);
if (bs == null || bs.length == 0) { throw new IllegalArgumentException("Unable to locate the bundle '" + symbName
+ "' specified in the "
+ OSGiWebappConstants.REQUIRE_TLD_BUNDLE
+ " of the manifest of "
+ (bundle == null ? "unknown" : bundle.getSymbolicName())); }
File f = fileLocatorHelper.getBundleInstallLocation(bs[0]);
if (paths.length() > 0) paths.append(", ");
__logger.debug("getPathsToRequiredBundles: bundle path=" + bs[0].getLocation() + " uri=" + f.toURI());
paths.append(f.toURI().toURL().toString());
}
return paths.toString();
}
private PackageAdmin getBundleAdmin()
{
Bundle bootBundle = ((BundleReference) OSGiWebappConstants.class.getClassLoader()).getBundle();
ServiceTracker serviceTracker = new ServiceTracker(bootBundle.getBundleContext(), PackageAdmin.class.getName(), null);
serviceTracker.open();
return (PackageAdmin) serviceTracker.getService();
}
}

View File

@ -19,39 +19,32 @@
package org.eclipse.jetty.osgi.boot.internal.webapp;
import java.net.URL;
import java.util.Dictionary;
import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.osgi.boot.BundleProvider;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
import org.osgi.util.tracker.ServiceTracker;
/**
* Support bundles that declare the webapp directly through headers in their
* manifest.
* <p>
* Those headers will define a new WebApplication:
* <ul>
* <li>Web-ContextPath</li>
* <li>Jetty-WarFolderPath</li>
* </ul>
* </p>
* <p>
* 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.
* <ul>
* <li>Jetty-ContextFilePath</li>
* </ul>
* </p>
* 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
*/
@ -59,6 +52,37 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer
{
private static final Logger LOG = Log.getLogger(WebBundleTrackerCustomizer.class);
public static Collection<WebappRegistrationCustomizer> JSP_REGISTRATION_HELPERS = new ArrayList<WebappRegistrationCustomizer>();
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 <code>BundleTracker</code>.
*
@ -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 <code>BundleTracker</code> has been modified.
*
@ -125,6 +150,8 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer
}
}
/* ------------------------------------------------------------ */
/**
* A bundle tracked by the <code>BundleTracker</code> 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<deployers.length)
{
LOG.warn("The manifest header '" + OSGiWebappConstants.JETTY_WAR_FOLDER_PATH
+ ": "
+ warFolderRelativePath
+ "' in the bundle "
+ bundle.getSymbolicName()
+ " is not valid: there is no Web-ContextPath defined in the manifest.");
return false;
}
// create the corresponding service and publish it in the context of
// the contributor bundle.
try
{
JettyBootstrapActivator.registerWebapplication(bundle, warFolderRelativePath, contextPath);
return true;
}
catch (Throwable e)
{
LOG.warn("Starting the web-bundle " + bundle.getSymbolicName() + " threw an exception.", e);
return true;// maybe it did not work maybe it did. safer to track this bundle.
BundleProvider p = (BundleProvider)deployers[i];
try
{
deployed = p.bundleAdded(bundle);
}
catch (Exception x)
{
LOG.warn("Error deploying bundle for jetty context", x);
}
i++;
}
}
else if (dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH) != null)
return deployed;
}
/* ------------------------------------------------------------ */
/**
* @param bundle
*/
private void unregister(Bundle bundle)
{
Object[] deployers = _serviceTracker.getServices();
boolean undeployed = false;
if (deployers != null)
{
String contextFileRelativePath = (String) dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH);
if (contextFileRelativePath == null)
{
// nothing to register here.
return false;
}
// support for multiple webapps in the same bundle:
String[] pathes = contextFileRelativePath.split(",;");
for (String path : pathes)
int i=0;
while (!undeployed && i<deployers.length)
{
try
{
JettyBootstrapActivator.registerContext(bundle, path.trim());
undeployed = ((BundleProvider)deployers[i++]).bundleRemoved(bundle);
}
catch (Throwable e)
catch (Exception x)
{
LOG.warn(e);
LOG.warn("Error undeploying bundle for jetty context", x);
}
}
return true;
}
else
{
// support for OSGi-RFC66; disclaimer, no access to the actual
// (draft) of the spec: just a couple of posts on the
// world-wide-web.
URL rfc66Webxml = bundle.getEntry("/WEB-INF/web.xml");
if (rfc66Webxml == null && dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH) == null)
{
return false;// no webapp in here
}
// this is risky: should we make sure that there is no classes and
// jars directly available
// at the root of the of the bundle: otherwise they are accessible
// through the browser. we should enforce that the whole classpath
// is
// pointing to files and folders inside WEB-INF. We should
// filter-out
// META-INF too
String rfc66ContextPath = getWebContextPath(bundle, dic, rfc66Webxml == null);
try
{
JettyBootstrapActivator.registerWebapplication(bundle, ".", rfc66ContextPath);
return true;
}
catch (Throwable e)
{
LOG.warn(e);
return true;// maybe it did not work maybe it did. safer to track this bundle.
}
}
}
private String getWebContextPath(Bundle bundle, Dictionary<?, ?> 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");
}
}
}

View File

@ -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;
}
}

View File

@ -90,4 +90,30 @@ public interface BundleFileLocatorHelper
*/
public Enumeration<URL> 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
* <p>
* Get a URL to the bundle entry that uses a common protocol (i.e. file:
* jar: or http: etc.).
* </p>
*
* @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
* <p>
* 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
* </p>
*/
public URL getFileURL(URL url);
}

View File

@ -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<String,Object> props = new Hashtable<String,Object>();
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));
}
}

View File

@ -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<URL> getResources(String name) throws IOException
{
Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name);
Enumeration<URL> 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<URL> toList(Enumeration<URL> e, Enumeration<URL> e2)
{
List<URL> list = new ArrayList<URL>();
while (e != null && e.hasMoreElements())
list.add(e.nextElement());
while (e2 != null && e2.hasMoreElements())
list.add(e2.nextElement());
return list;
}
}

View File

@ -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;
}

View File

@ -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,13 +33,10 @@ import org.osgi.framework.Bundle;
*/
public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
{
private static final Logger LOG = Log.getLogger(BundleClassLoaderHelper.class);
private static boolean identifiedOsgiImpl = false;
private static Class BundleWiringClass = null;
private static Method BundleWiringClass_getClassLoader_method = null;
private static Method BundleClass_adapt_method = null;
private static boolean isEquinox = false;
private static boolean isFelix = false;
@ -45,34 +44,13 @@ 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)
{
@ -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
{
// 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);
}
//firstly, try to find classes matching a newer version of felix
initFelix403(bundle);
// Figure out which version of the modules is exported
Object currentModuleImpl;
if (isFelix403.booleanValue())
{
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<Object> moduleArray = (List<Object>) Felix_BundleImpl_m_modules_field.get(bundle);
currentModuleImpl = moduleArray.get(moduleArray.size() - 1);
}
catch (Exception e)
{
LOG.warn(e);
return null;
}
}
if (Felix_ModuleImpl_m_classLoader_field == null && currentModuleImpl != null)
if (Felix_ModuleImpl_m_classLoader_field == null && currentModuleImpl != null)
{
try
{
Felix_ModuleImpl_m_classLoader_field = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.ModuleImpl").getDeclaredField("m_classLoader");
Felix_ModuleImpl_m_classLoader_field.setAccessible(true);
}
// first make sure that the classloader is ready:
// the m_classLoader field must be initialized by the
// ModuleImpl.getClassLoader() private method.
ClassLoader cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl);
if (cl == null)
catch (ClassNotFoundException e)
{
// looks like it was not ready:
// the m_classLoader field must be initialized by the
// ModuleImpl.getClassLoader() private method.
// this call will do that.
bundle.loadClass("java.lang.Object");
cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl);
return cl;
LOG.warn(e);
return null;
}
else
catch (NoSuchFieldException e)
{
return cl;
LOG.warn(e);
return null;
}
}
catch (Throwable t)
// first make sure that the classloader is ready:
// the m_classLoader field must be initialized by the
// ModuleImpl.getClassLoader() private method.
ClassLoader cl = null;
try
{
t.printStackTrace();
cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl);
if (cl != null)
return cl;
}
catch (Exception e)
{
LOG.warn(e);
return null;
}
// looks like it was not ready:
// the m_classLoader field must be initialized by the
// ModuleImpl.getClassLoader() private method.
// this call will do that.
try
{
bundle.loadClass("java.lang.Object");
cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl);
return cl;
}
catch (Exception e)
{
LOG.warn(e);
return null;
}
return null;
}
private static void initFelix403 (Bundle bundle)
{
//see if the version of Felix is a new one
if (isFelix403 == null)
{
try
{
Class bundleImplClazz = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl");
Felix_bundleWiringClazz = bundle.getClass().getClassLoader().loadClass("org.osgi.framework.wiring.BundleWiring");
Felix_adapt_method = bundleImplClazz.getDeclaredMethod("adapt", new Class[] {Class.class});
Felix_adapt_method.setAccessible(true);
Felix_bundle_wiring_getClassLoader_method = Felix_bundleWiringClazz.getDeclaredMethod("getClassLoader");
Felix_bundle_wiring_getClassLoader_method.setAccessible(true);
isFelix403 = Boolean.TRUE;
}
catch (ClassNotFoundException e)
{
LOG.warn("Felix 4.x classes not found in environment");
isFelix403 = Boolean.FALSE;
}
catch (NoSuchMethodException e)
{
LOG.warn("Felix 4.x classes not found in environment");
isFelix403 = Boolean.FALSE;
}
}
}
}

View File

@ -31,13 +31,13 @@ import java.util.zip.ZipFile;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.FileResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.FileResource;
import org.osgi.framework.Bundle;
/**
* From a bundle to its location on the filesystem.
* Often assumes the bundle is not a jar.
* From a bundle to its location on the filesystem. Assumes the bundle is not a
* jar.
*
* @author hmalphettes
*/
@ -182,16 +182,10 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
File file = new File(location.substring("file:".length()));
return file;
}
else
{
//Resort to introspection on felix:
return getBundleInstallLocationInFelix(bundle);
}
}
return null;
}
/**
* Locate a file inside a bundle.
*
@ -303,7 +297,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
*
* @return a URL to the bundle entry that uses a common protocol
*/
public static URL getLocalURL(URL url)
public URL getLocalURL(URL url)
{
if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol()))
{
@ -339,7 +333,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
* protocol
* </p>
*/
public static URL getFileURL(URL url)
public URL getFileURL(URL url)
{
if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol()))
{
@ -362,85 +356,4 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
return url;
}
// Felix introspection
private static Method Felix_BundleImpl_getArchive_method;
private static Method Felix_BundleArchive_getCurrentRevision_method;
private static Method Felix_BundleRevision_getRevisionRootDir_method;
private static boolean felixIntroSpectionDone = false;
/**
* Introspection of the implementation classes of Felix-3.x and Felix-4.x.
* <p>
* See org.apache.felix.framework.cache
* In pseudo code:
* <code>
* File revRootDir = BundleImpl.getArchive().getCurrentRevision().getRevisionRootDir();
* return new File(revRootDir, bundle.jar) if it exists?
* else return revRootDir
* </p>
* @param bundle
* @return The File or null if we failed to find it.
*/
private static File getBundleInstallLocationInFelix(Bundle bundle)
{
if (Felix_BundleImpl_getArchive_method == null) {
if (felixIntroSpectionDone)
{
return null;
}
felixIntroSpectionDone = true;
try
{
Felix_BundleImpl_getArchive_method = bundle.getClass().getDeclaredMethod("getArchive", new Class[] {});
Felix_BundleImpl_getArchive_method.setAccessible(true);
Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle);
Class bundleArchiveClass = archive.getClass();
Felix_BundleArchive_getCurrentRevision_method = bundleArchiveClass.getDeclaredMethod("getCurrentRevision", new Class[] {});
Felix_BundleArchive_getCurrentRevision_method.setAccessible(true);
Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive);
Class bundleRevisionClass = revision.getClass();
Felix_BundleRevision_getRevisionRootDir_method = bundleRevisionClass.getMethod("getRevisionRootDir", new Class[] {});
Felix_BundleRevision_getRevisionRootDir_method.setAccessible(true);
}
catch (Throwable t)
{
//nevermind?
//t.printStackTrace();
Felix_BundleImpl_getArchive_method = null;
return null;
}
}
if (Felix_BundleImpl_getArchive_method != null)
{
try
{
Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle);
Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive);
File revRootDir = (File)Felix_BundleRevision_getRevisionRootDir_method.invoke(revision);
//System.err.println("Got the archive revision root dir " + revRootDir.getAbsolutePath());
File bundleJar = new File(revRootDir, "bundle.jar");
if (bundleJar.exists())
{
//bundle.jar is hardcoded in org.apache.felix.framework.cache.JarRevision
//when it is not a bundle.jar, then the bundle location starts with 'file:' and we have already
//taken care if that scheme earlier.
return bundleJar;
}
else //sanity check?: if (new File(revRootDir, "META-INF/MANIFEST.MF").exists())
{
//this is a DirectoryRevision
return revRootDir;
}
}
catch (Throwable t)
{
//best effort: nevermind
//t.printStackTrace();
}
}
return null;
}
// -- end Felix introspection
}

View File

@ -1,120 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>7.6.10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-osgi-equinoxtools</artifactId>
<name>Jetty :: OSGi :: Example Equinox Tools</name>
<description>Jetty OSGi Example Equinox Tools</description>
<properties>
<bundle-symbolic-name>${project.groupId}.equinoxtools</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>process-resources</phase>
<configuration>
<tasks>
<copy todir="target/classes/equinoxconsole">
<fileset dir="equinoxconsole" />
</copy>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
<execution>
<id>test-jar</id>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-SymbolicName>org.eclipse.jetty.osgi.equinoxtools</Bundle-SymbolicName>
<Bundle-Name>Console</Bundle-Name>
<Bundle-Activator>org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator</Bundle-Activator>
<Export-Package>org.eclipse.jetty.osgi.equinoxtools;x-internal:=true;version="${parsedVersion.osgiVersion}",
org.eclipse.jetty.osgi.equinoxtools.console;x-internal:=true;version="${parsedVersion.osgiVersion}"
</Export-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<onlyAnalyze>org.eclipse.jetty.osgi.equinoxtools.*</onlyAnalyze>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -3,7 +3,6 @@
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>9.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-httpservice</artifactId>
@ -96,9 +95,10 @@
<Bundle-SymbolicName>org.eclipse.jetty.osgi.httpservice</Bundle-SymbolicName>
<Bundle-Name>OSGi HttpService</Bundle-Name>
<Jetty-ContextFilePath>contexts/httpservice.xml</Jetty-ContextFilePath>
<Import-Package>org.eclipse.jetty.server.handler;version="9.0.0",
org.eclipse.jetty.util.component;version="9.0.0",
org.eclipse.jetty.server.session;version="9.0.0",
<Import-Package>org.eclipse.jetty.server.handler;version="[9.0,10.0)",
org.eclipse.jetty.util.component;version="[9.0,10.0)",
org.eclipse.jetty.server.session;version="[9.0,10.0)",
org.eclipse.jetty.servlet;version="[9.0,10.0)",
org.eclipse.equinox.http.servlet,
*
</Import-Package>

View File

@ -4,7 +4,6 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
@ -23,6 +22,8 @@
<module>jetty-osgi-boot-jsp</module>
<module>jetty-osgi-boot-warurl</module>
<module>jetty-osgi-httpservice</module>
<module>test-jetty-osgi-webapp</module>
<module>test-jetty-osgi-context</module>
<module>test-jetty-osgi</module>
</modules>
<build>
@ -142,28 +143,6 @@
<artifactId>servlet</artifactId>
<version>${equinox-http-servlet-version}</version>
</dependency>
<!--dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>servletbridge</artifactId>
<version>${equinox-servletbridge-version}</version>
</dependency-->
<!-- not ready <dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-impl</artifactId>
<version>${jsp-impl-2.2-glassfish-version}</version>
</dependency-->
<!--
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-2.1-glassfish</artifactId>
<version>${jsp-2.1-glassfish-version}</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-api-2.1-glassfish</artifactId>
<version>${jsp-2.1-glassfish-version}</version>
</dependency>
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>

View File

@ -0,0 +1,112 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>9.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-jetty-osgi-context</artifactId>
<name>Jetty :: OSGi :: Context</name>
<description>Test Jetty OSGi bundle with a ContextHandler</description>
<properties>
<bundle-symbolic-name>${project.groupId}.testcontext</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/context</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
<execution>
<id>test-jar</id>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-SymbolicName>org.eclipse.jetty.osgi.testcontext;singleton:=true</Bundle-SymbolicName>
<Bundle-Name>Jetty OSGi Test Context</Bundle-Name>
<Bundle-Activator>com.acme.osgi.Activator</Bundle-Activator>
<Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
<!-- disable the uses directive: jetty will accomodate pretty much any versions
of the packages it uses; no need to reflect some tight dependency determined at
compilation time. -->
<_nouses>true</_nouses>
<Import-Package>
javax.servlet;version="2.6.0",
javax.servlet.resources;version="2.6.0",
org.osgi.framework,
org.osgi.service.cm;version="1.2.0",
org.osgi.service.packageadmin,
org.osgi.service.startlevel;version="1.0.o",
org.osgi.service.url;version="1.0.0",
org.osgi.util.tracker;version="1.3.0",
org.slf4j;resolution:=optional,
org.slf4j.spi;resolution:=optional,
org.slf4j.helpers;resolution:=optional,
org.xml.sax,
org.xml.sax.helpers,
*
</Import-Package>
<DynamicImport-Package>org.eclipse.jetty.*;version="[9.0,10.0)"</DynamicImport-Package>
<!--Require-Bundle/-->
<!-- Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment -->
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<!-- Get root for static content, could be on file system or this bundle -->
<Call id="res" class="org.eclipse.jetty.util.resource.Resource" name="newResource">
<Arg><Property name="bundle.root"/></Arg>
</Call>
<Ref id="res">
<Call id="base" name="addPath">
<Arg>/static/</Arg>
</Call>
</Ref>
<Set name="contextPath">/unset</Set>
<!-- Set up the base resource for static files relative to inside bundle -->
<Set name="baseResource">
<Ref id="base"/>
</Set>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.ResourceHandler">
<Set name="welcomeFiles">
<Array type="String">
<Item>index.html</Item>
</Array>
</Set>
<Set name="cacheControl">max-age=3600,public</Set>
</New>
</Set>
</Configure>

View File

@ -0,0 +1,82 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package com.acme.osgi;
import java.util.Dictionary;
import java.util.Hashtable;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.BundleTracker;
/**
* Bootstrap a ContextHandler
*
*
*/
public class Activator implements BundleActivator
{
/**
*
* @param context
*/
public void start(final BundleContext context) throws Exception
{
ContextHandler ch = new ContextHandler();
ch.addEventListener(new ServletContextListener () {
@Override
public void contextInitialized(ServletContextEvent sce)
{
System.err.println("Context is initialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
System.err.println("CONTEXT IS DESTROYED!");
}
});
Dictionary props = new Hashtable();
props.put("contextPath","/acme");
props.put("Jetty-ContextFilePath", "acme.xml");
context.registerService(ContextHandler.class.getName(),ch,props);
}
/**
* Stop the activator.
*
* @see
* org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception
{
}
}

View File

@ -0,0 +1,6 @@
<html>
<body>
<h1>Test OSGi Context</h1>
<p>ContextHandler registered as a service successfully deployed.</p>
</body>
</html>

View File

@ -0,0 +1,107 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>9.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-jetty-osgi-webapp</artifactId>
<name>Jetty :: OSGi :: WebApp</name>
<description>Test Jetty OSGi Webapp bundle</description>
<properties>
<bundle-symbolic-name>${project.groupId}.webapp</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
<execution>
<id>test-jar</id>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-SymbolicName>org.eclipse.jetty.osgi.testapp;singleton:=true</Bundle-SymbolicName>
<Bundle-Name>Jetty OSGi Test WebApp</Bundle-Name>
<Bundle-Activator>com.acme.osgi.Activator</Bundle-Activator>
<Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
<!-- disable the uses directive: jetty will accomodate pretty much any versions
of the packages it uses; no need to reflect some tight dependency determined at
compilation time. -->
<_nouses>true</_nouses>
<Import-Package>
org.osgi.framework,
org.osgi.service.cm;version="1.2.0",
org.osgi.service.packageadmin,
org.osgi.service.startlevel;version="1.0.o",
org.osgi.service.url;version="1.0.0",
org.osgi.util.tracker;version="1.3.0",
org.slf4j;resolution:=optional,
org.slf4j.spi;resolution:=optional,
org.slf4j.helpers;resolution:=optional,
org.xml.sax,
org.xml.sax.helpers,
*
</Import-Package>
<DynamicImport-Package>org.eclipse.jetty.*;version="[9.0,10.0)"</DynamicImport-Package>
<!--Require-Bundle/-->
<!-- Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment -->
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,66 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package com.acme.osgi;
import java.util.Dictionary;
import java.util.Hashtable;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.webapp.WebAppContext;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.BundleTracker;
/**
* Bootstrap a webapp
*
*
*/
public class Activator implements BundleActivator
{
/**
*
* @param context
*/
public void start(BundleContext context) throws Exception
{
WebAppContext webapp = new WebAppContext();
Dictionary props = new Hashtable();
props.put("war",".");
props.put("contextPath","/acme");
//uiProps.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, serverName);
context.registerService(ContextHandler.class.getName(),webapp,props);
}
/**
* Stop the activator.
*
* @see
* org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception
{
}
}

View File

@ -0,0 +1,6 @@
<html>
<body>
<h1>Test OSGi WebApp</h1>
<p>Webapp registered by bundle as service successfully deployed.</p>
</body>
</html>

View File

@ -326,6 +326,18 @@
<classifier>webbundle</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>test-jetty-osgi-context</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>test-jetty-osgi-webapp</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>

View File

@ -16,23 +16,6 @@
<Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
<Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
</Call>
<!-- Providers of OSGi Apps -->
<Call name="addAppProvider">
<Arg>
<New class="org.eclipse.jetty.osgi.boot.OSGiAppProvider">
<!--
<Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-->
<Set name="scanInterval">0</Set>
<Set name="contextXmlDir"><Property name="jetty.home" default="." />/contexts</Set>
<!-- comma separated list of bundle symbolic names that contain custom tag libraries (*.tld files) -->
<!-- if those bundles don't exist or can't be loaded no errors or warning will be issued! -->
<!-- This default value plugs in the tld files of the reference implementation of JSF -->
<Set name="tldBundles"><Property name="org.eclipse.jetty.osgi.tldbundles" default="javax.faces.jsf-impl" /></Set>
</New>
</Arg>
</Call>
</New>
</Arg>
</Call>

View File

@ -1,195 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.osgi.test;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
/**
* Helper methods for pax-exam tests
*/
public class AbstractTestOSGi
{
private Map<String,Bundle> _bundles;
/**
* Note: this will run many more tests.
* TODO: find a better way to control this and use non-deprecated methods.
* @param options
*/
protected static void addMoreOSGiContainers(List<Option> options)
{
options.add(CoreOptions.equinox().version("3.6.1"));
options.add(CoreOptions.equinox().version("3.7.0"));
options.add(CoreOptions.felix().version("3.2.2"));
options.add(CoreOptions.felix().version("4.0.2"));
}
protected Bundle getBundle(BundleContext bundleContext, String symbolicName)
{
if (_bundles == null)
{
_bundles = new HashMap<String,Bundle>();
for (Bundle b : bundleContext.getBundles())
{
Bundle prevBundle = _bundles.put(b.getSymbolicName(), b);
String err = prevBundle != null ? "2 versions of the bundle " + b.getSymbolicName() +
" " + b.getHeaders().get("Bundle-Version") +
" and " + prevBundle.getHeaders().get("Bundle-Version")
: "";
Assert.assertNull(err, prevBundle);
}
}
return _bundles.get(symbolicName);
}
protected void assertActiveBundle(BundleContext bundleContext, String symbolicName) throws Exception
{
Bundle b = getBundle(bundleContext, symbolicName);
Assert.assertNotNull(b);
Assert.assertEquals(b.getSymbolicName()+" must be active.", Bundle.ACTIVE, b.getState());
}
protected void assertActiveOrResolvedBundle(BundleContext bundleContext, String symbolicName) throws Exception
{
Bundle b = getBundle(bundleContext, symbolicName);
Assert.assertNotNull(b);
if (b.getHeaders().get("Fragment-Host") == null) diagnoseNonActiveOrNonResolvedBundle(b);
Assert.assertTrue(b.getSymbolicName()+" must be active or resolved. It was "+b.getState(),
b.getState() == Bundle.ACTIVE || b.getState() == Bundle.RESOLVED);
}
protected void assertAllBundlesActiveOrResolved(BundleContext bundleContext)
{
for (Bundle b : bundleContext.getBundles())
{
if (b.getState() == Bundle.INSTALLED)
{
diagnoseNonActiveOrNonResolvedBundle(b);
}
Assert.assertTrue("Bundle: " + b + " (state should be " +
"ACTIVE[" + Bundle.ACTIVE + "] or RESOLVED[" + Bundle.RESOLVED + "]" +
", but was [" + b.getState() + "])",
(b.getState() == Bundle.ACTIVE) || (b.getState() == Bundle.RESOLVED));
}
}
protected boolean diagnoseNonActiveOrNonResolvedBundle(Bundle b)
{
if (b.getState() != Bundle.ACTIVE && b.getHeaders().get("Fragment-Host") == null)
{
try
{
System.err.println("Trying to start the bundle "+b.getSymbolicName()+
" that was supposed to be active or resolved.");
b.start();
System.err.println(b.getSymbolicName() + " did start");
return true;
}
catch (Throwable t)
{
System.err.println(b.getSymbolicName() + " failed to start");
t.printStackTrace(System.err);
return false;
}
}
System.err.println(b.getSymbolicName() + " was already started");
return false;
}
protected void debugBundles(BundleContext bundleContext)
{
Map<String,Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
System.err.println("Active " + Bundle.ACTIVE);
System.err.println("RESOLVED " + Bundle.RESOLVED);
System.err.println("INSTALLED " + Bundle.INSTALLED);
for( Bundle b : bundleContext.getBundles() )
{
bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
System.err.println(" " + b.getSymbolicName() + " " + b.getState());
}
}
protected SslContextFactory getSslContextFactory()
{
SslContextFactory sslContextFactory = new SslContextFactory(true);
sslContextFactory.setEndpointIdentificationAlgorithm("");
return sslContextFactory;
}
protected void testHttpServiceGreetings(BundleContext bundleContext, String protocol, int port) throws Exception
{
assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.boot");
assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.httpservice");
assertActiveBundle(bundleContext, "org.eclipse.equinox.http.servlet");
//in the OSGi world this would be bad code and we should use a bundle tracker.
//here we purposely want to make sure that the httpService is actually ready.
ServiceReference sr = bundleContext.getServiceReference(HttpService.class.getName());
Assert.assertNotNull("The httpServiceOSGiBundle is started and should " +
"have deployed a service reference for HttpService" ,sr);
HttpService http = (HttpService)bundleContext.getService(sr);
http.registerServlet("/greetings", new HttpServlet() {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException,
IOException {
resp.getWriter().write("Hello");
}
}, null, null);
//now test the servlet
HttpClient client = protocol.equals("https") ? new HttpClient(getSslContextFactory()) : new HttpClient();
try
{
client.start();
ContentResponse response = client.GET(protocol+"://127.0.0.1:"+port+"/greetings");
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
Assert.assertEquals("Hello", content);
}
finally
{
client.stop();
}
}
}

View File

@ -0,0 +1,163 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.osgi.test;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import junit.framework.Assert;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
/**
* TestJettyOSGiBootContextAsService
*
* Tests deployment of a ContextHandler as an osgi Service.
*
* Tests the ServiceContextProvider.
*
*/
@RunWith(JUnit4TestRunner.class)
public class TestJettyOSGiBootContextAsService
{
private static final boolean LOGGING_ENABLED = false;
private static final boolean REMOTE_DEBUGGING = false;
@Inject
BundleContext bundleContext = null;
@Configuration
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
// a bundle that registers a webapp as a service for the jetty osgi core
// to pick up and deploy
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
// Enable Logging
if (LOGGING_ENABLED)
{
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging
// (logProfile)
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"))));
}
return options.toArray(new Option[options.size()]);
}
public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
{
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
List<Option> options = new ArrayList<Option>();
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(etc + "/jetty.xml;"
+ etc
+ "/"
+ jettySelectorFileName
+ ";"
+ etc
+ "/jetty-deployer.xml;"
+ etc
+ "/jetty-testrealm.xml"));
options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
return options;
}
@Test
public void assertAllBundlesActiveOrResolved()
{
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
/**
*/
@Test
public void testContextHandlerAsOSGiService() throws Exception
{
// now test the context
HttpClient client = new HttpClient();
try
{
client.start();
ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/acme/index.html");
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
Assert.assertTrue(content.indexOf("<h1>Test OSGi Context</h1>") != -1);
}
finally
{
client.stop();
}
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
Assert.assertNotNull(refs);
Assert.assertEquals(1, refs.length);
String[] keys = refs[0].getPropertyKeys();
if (keys != null)
{
for (String k : keys)
System.err.println("service property: " + k + ", " + refs[0].getProperty(k));
}
ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]);
Assert.assertEquals("/acme", ch.getContextPath());
// Stop the bundle with the ContextHandler in it and check the jetty
// Context is destroyed for it.
// TODO: think of a better way to communicate this to the test, other
// than checking stderr output
Bundle testWebBundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.testcontext");
Assert.assertNotNull("Could not find the org.eclipse.jetty.test-jetty-osgi-context.jar bundle", testWebBundle);
Assert.assertTrue("The bundle org.eclipse.jetty.testcontext is not correctly resolved", testWebBundle.getState() == Bundle.ACTIVE);
testWebBundle.stop();
}
}

View File

@ -28,9 +28,12 @@ import javax.inject.Inject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.MavenUtils;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.ops4j.pax.exam.options.MavenUrlReference.VersionResolver;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@ -38,7 +41,8 @@ import org.osgi.framework.BundleContext;
* Default OSGi setup integration test
*/
@RunWith( JUnit4TestRunner.class )
public class TestJettyOSGiBootCore extends AbstractTestOSGi {
public class TestJettyOSGiBootCore
{
public static int DEFAULT_JETTY_HTTP_PORT = 9876;
@ -48,8 +52,10 @@ public class TestJettyOSGiBootCore extends AbstractTestOSGi {
@Configuration
public Option[] config()
{
VersionResolver resolver = MavenUtils.asInProject();
System.err.println(resolver.getVersion("org.eclipse.jetty", "jetty-server"));
ArrayList<Option> options = new ArrayList<Option>();
addMoreOSGiContainers(options);
TestOSGiUtil.addMoreOSGiContainers(options);
options.addAll(provisionCoreJetty());
options.add(CoreOptions.junitBundles());
options.addAll(httpServiceJetty());
@ -103,9 +109,14 @@ public class TestJettyOSGiBootCore extends AbstractTestOSGi {
}
@Test
public void assertAllBundlesActiveOrResolved()
public void assertAllBundlesActiveOrResolved() throws Exception
{
assertAllBundlesActiveOrResolved(bundleContext);
//TestOSGiUtil.debugBundles(bundleContext);
//Bundle bootBundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.boot");
//TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(bootBundle);
Bundle httpservicebundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.httpservice");
TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(httpservicebundle);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
/**
@ -115,7 +126,7 @@ public class TestJettyOSGiBootCore extends AbstractTestOSGi {
@Test
public void testHttpService() throws Exception
{
testHttpServiceGreetings(bundleContext, "http", DEFAULT_JETTY_HTTP_PORT);
TestOSGiUtil.testHttpServiceGreetings(bundleContext, "http", DEFAULT_JETTY_HTTP_PORT);
}

View File

@ -38,10 +38,12 @@ import org.osgi.framework.BundleContext;
/**
* SPDY setup.
*/
@RunWith( JUnit4TestRunner.class )
public class TestJettyOSGiBootSpdy extends AbstractTestOSGi {
@RunWith(JUnit4TestRunner.class)
public class TestJettyOSGiBootSpdy
{
private static final String JETTY_SPDY_PORT = "jetty.spdy.port";
private static final int DEFAULT_JETTY_SPDY_PORT = 9877;
@Inject
@ -52,7 +54,7 @@ public class TestJettyOSGiBootSpdy extends AbstractTestOSGi {
{
ArrayList<Option> options = new ArrayList<Option>();
addMoreOSGiContainers(options);
TestOSGiUtil.addMoreOSGiContainers(options);
options.addAll(TestJettyOSGiBootCore.provisionCoreJetty());
options.addAll(TestJettyOSGiBootWithJsp.configureJettyHomeAndPort("jetty-spdy.xml"));
@ -66,42 +68,28 @@ public class TestJettyOSGiBootSpdy extends AbstractTestOSGi {
{
List<Option> res = new ArrayList<Option>();
res.add(CoreOptions.systemProperty(JETTY_SPDY_PORT).value(String.valueOf(DEFAULT_JETTY_SPDY_PORT)));
//java -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn-version}/npn-boot-${npn-version}.jar
// res.add(CoreOptions.vmOptions("-Xbootclasspath/p:"+System.getenv("HOME")+"/.m2/repository/org/mortbay/jetty/npn/npn-boot/"+npnBootVersion+"/npn-boot-"+npnBootVersion+".jar"));
// java
// -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn-version}/npn-boot-${npn-version}.jar
// res.add(CoreOptions.vmOptions("-Xbootclasspath/p:"+System.getenv("HOME")+"/.m2/repository/org/mortbay/jetty/npn/npn-boot/"+npnBootVersion+"/npn-boot-"+npnBootVersion+".jar"));
String npnBoot = System.getProperty("mortbay-npn-boot");
if (npnBoot == null)
{
throw new IllegalStateException("Please define the path to the npn boot jar as the sys property -Dmortbay-npn-boot");
//are we trying to be too nice? this kinds of work outside of maven maybe
// String npnBootUrl = mavenBundle().groupId( "org.mortbay.jetty.npn" ).artifactId( "npn-boot" ).versionAsInProject().getURL();
// String npnBootVersion = npnBootUrl.split("\\/")[2];
// if (!Character.isDigit(npnBootVersion.charAt(0)))
// {
// throw new IllegalArgumentException(npnBootUrl + " - " + npnBootVersion);
// }
// npnBoot = System.getenv("HOME")+"/.m2/repository/org/mortbay/jetty/npn/npn-boot/"+npnBootVersion+"/npn-boot-"+npnBootVersion+".jar";
}
if (npnBoot == null) { throw new IllegalStateException("Define path to npn boot jar as system property -Dmortbay-npn-boot"); }
File checkNpnBoot = new File(npnBoot);
if (!checkNpnBoot.exists())
{
throw new IllegalStateException("Unable to find the npn boot jar here: " + npnBoot);
}
if (!checkNpnBoot.exists()) { throw new IllegalStateException("Unable to find the npn boot jar here: " + npnBoot); }
res.add(CoreOptions.vmOptions("-Xbootclasspath/p:"+npnBoot));
res.add(CoreOptions.vmOptions("-Xbootclasspath/p:" + npnBoot));
res.add(CoreOptions.bootDelegationPackages("org.eclipse.jetty.npn"));
res.add(mavenBundle().groupId( "org.eclipse.jetty.spdy" ).artifactId( "spdy-core" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.spdy" ).artifactId( "spdy-server" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.spdy" ).artifactId( "spdy-http-server" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.spdy" ).artifactId( "spdy-client" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-core").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-server").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-http-server").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-client").versionAsInProject().noStart());
return res;
}
@Test
public void checkNpnBootOnBootstrapClasspath() throws Exception
{
Class<?> npn = Thread.currentThread().getContextClassLoader()
.loadClass("org.eclipse.jetty.npn.NextProtoNego");
Class<?> npn = Thread.currentThread().getContextClassLoader().loadClass("org.eclipse.jetty.npn.NextProtoNego");
Assert.assertNotNull(npn);
Assert.assertNull(npn.getClassLoader());
}
@ -109,14 +97,13 @@ public class TestJettyOSGiBootSpdy extends AbstractTestOSGi {
@Test
public void assertAllBundlesActiveOrResolved()
{
assertAllBundlesActiveOrResolved(bundleContext);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
@Test
public void testSpdyOnHttpService() throws Exception
{
testHttpServiceGreetings(bundleContext, "https", DEFAULT_JETTY_SPDY_PORT);
TestOSGiUtil.testHttpServiceGreetings(bundleContext, "https", DEFAULT_JETTY_SPDY_PORT);
}
}

View File

@ -0,0 +1,174 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.osgi.test;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import junit.framework.Assert;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
/**
* TestJettyOSGiBootWebAppAsService
*
* Tests deployment of a WebAppContext as an osgi Service.
*
* Tests the ServiceWebAppProvider.
*
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
* top of this.
*/
@RunWith(JUnit4TestRunner.class)
public class TestJettyOSGiBootWebAppAsService
{
private static final boolean LOGGING_ENABLED = false;
private static final boolean REMOTE_DEBUGGING = false;
@Inject
BundleContext bundleContext = null;
@Configuration
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
// Enable Logging
if (LOGGING_ENABLED)
{
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging
// (logProfile)
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"))));
}
options.addAll(jspDependencies());
return options.toArray(new Option[options.size()]);
}
public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
{
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
List<Option> options = new ArrayList<Option>();
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(etc + "/jetty.xml;"
+ etc
+ "/"
+ jettySelectorFileName
+ ";"
+ etc
+ "/jetty-deployer.xml;"
+ etc
+ "/jetty-testrealm.xml"));
options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
return options;
}
public static List<Option> jspDependencies()
{
List<Option> res = new ArrayList<Option>();
/* orbit deps */
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.el").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("com.sun.el").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.jasper.glassfish").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.taglibs.standard.glassfish").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
/* jetty-osgi deps */
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-boot-jsp").versionAsInProject().noStart());
// a bundle that registers a webapp as a service for the jetty osgi core
// to pick up and deploy
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-webapp").versionAsInProject().start());
return res;
}
@Test
public void assertAllBundlesActiveOrResolved()
{
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
/**
*/
@Test
public void testBundle() throws Exception
{
// now test the jsp/dump.jsp
HttpClient client = new HttpClient();
try
{
client.start();
ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/acme/index.html");
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
Assert.assertTrue(content.indexOf("<h1>Test OSGi WebApp</h1>") != -1);
}
finally
{
client.stop();
}
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
Assert.assertNotNull(refs);
Assert.assertEquals(1, refs.length);
WebAppContext wac = (WebAppContext) bundleContext.getService(refs[0]);
Assert.assertEquals("/acme", wac.getContextPath());
}
}

View File

@ -45,13 +45,15 @@ import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.BundleContext;
/**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the httpservice web-bundle.
* Then make sure we can deploy an OSGi service on the top of this.
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
* top of this.
*/
@RunWith( JUnit4TestRunner.class )
public class TestJettyOSGiBootWithJsp extends AbstractTestOSGi
@RunWith(JUnit4TestRunner.class)
public class TestJettyOSGiBootWithJsp
{
private static final boolean LOGGING_ENABLED = true;
private static final boolean REMOTE_DEBUGGING = false;
@Inject
@ -63,37 +65,41 @@ public class TestJettyOSGiBootWithJsp extends AbstractTestOSGi
ArrayList<Option> options = new ArrayList<Option>();
addMoreOSGiContainers(options);
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*",
"org.w3c.*", "javax.xml.*"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
// Enable Logging
if(LOGGING_ENABLED) {
if (LOGGING_ENABLED)
{
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging (logProfile)
systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" )
)));
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging
// (logProfile)
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"))));
}
options.addAll(jspDependencies());
// Remote JDWP Debugging, this won't work with the forked container.
// if(REMOTE_DEBUGGING) {
// options.addAll(Arrays.asList(options(
// // this just adds all what you write here to java vm argumenents of the (new) osgi process.
// PaxRunnerOptions.vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
// )));
// }
// if(REMOTE_DEBUGGING) {
// options.addAll(Arrays.asList(options(
// // this just adds all what you write here to java vm argumenents of
// the (new) osgi process.
// PaxRunnerOptions.vmOption(
// "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
// )));
// }
//bug at the moment: this would make the httpservice catch all
//requests and prevent the webapp at the root context to catch any of them.
//options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
// bug at the moment: this would make the httpservice catch all
// requests and prevent the webapp at the root context to catch any of
// them.
// options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
return options.toArray(new Option[options.size()]);
}
@ -103,31 +109,38 @@ public class TestJettyOSGiBootWithJsp extends AbstractTestOSGi
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
List<Option> options = new ArrayList<Option>();
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS)
.value(etc + "/jetty.xml;" +
etc + "/" + jettySelectorFileName + ";" +
etc + "/jetty-deployer.xml;" +
etc + "/jetty-testrealm.xml"));
String xmlConfigs = etc + "/jetty.xml;"
+ etc
+ "/"
+ jettySelectorFileName
+ ";"
+ etc
+ "/jetty-deployer.xml;"
+ etc
+ "/jetty-testrealm.xml";
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs));
System.err.println(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS+"="+xmlConfigs);
options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
return options;
}
public static List<Option> jspDependencies() {
public static List<Option> jspDependencies()
{
List<Option> res = new ArrayList<Option>();
/* orbit deps */
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp.jstl" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.el" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "com.sun.el" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.jasper.glassfish" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.taglibs.standard.glassfish" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.eclipse.jdt.core" ).versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.el").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("com.sun.el").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.jasper.glassfish").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.taglibs.standard.glassfish").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
/* jetty-osgi deps */
res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot-jsp" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-boot-jsp").versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "test-jetty-webapp" ).classifier("webbundle").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("test-jetty-webapp").classifier("webbundle").versionAsInProject());
return res;
}
@ -135,35 +148,34 @@ public class TestJettyOSGiBootWithJsp extends AbstractTestOSGi
@Test
public void assertAllBundlesActiveOrResolved()
{
assertAllBundlesActiveOrResolved(bundleContext);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
//at the moment can't run httpservice with jsp at the same time.
//that is a regression in jetty-9
// at the moment can't run httpservice with jsp at the same time.
// that is a regression in jetty-9
@Ignore
@Test
public void testHttpService() throws Exception
{
super.testHttpServiceGreetings(bundleContext, "http", TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT);
TestOSGiUtil.testHttpServiceGreetings(bundleContext, "http", TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT);
}
@Test
public void testJspDump() throws Exception
{
//System.err.println("http://127.0.0.1:9876/jsp/dump.jsp sleeping....");
//Thread.currentThread().sleep(5000000);
//now test the jsp/dump.jsp
// System.err.println("http://127.0.0.1:9876/jsp/dump.jsp sleeping....");
// Thread.currentThread().sleep(5000000);
// now test the jsp/dump.jsp
HttpClient client = new HttpClient();
try
{
client.start();
ContentResponse response = client.GET("http://127.0.0.1:"+
TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT+"/jsp/dump.jsp");
ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/jsp/dump.jsp");
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
//System.err.println("content: " + content);
System.err.println("content: " + content);
Assert.assertTrue(content.contains("<tr><th>ServletPath:</th><td>/jsp/dump.jsp</td></tr>"));
}
finally
@ -173,5 +185,4 @@ public class TestJettyOSGiBootWithJsp extends AbstractTestOSGi
}
}

View File

@ -0,0 +1,199 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.osgi.test;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
/**
* Helper methods for pax-exam tests
*/
public class TestOSGiUtil
{
/**
* Note: this will run many more tests. TODO: find a better way to control
* this and use non-deprecated methods.
*
* @param options
*/
protected static void addMoreOSGiContainers(List<Option> options)
{
options.add(CoreOptions.equinox().version("3.6.1"));
options.add(CoreOptions.equinox().version("3.7.0"));
options.add(CoreOptions.felix().version("3.2.2"));
options.add(CoreOptions.felix().version("4.0.2"));
}
protected static Bundle getBundle(BundleContext bundleContext, String symbolicName)
{
Map<String,Bundle> _bundles = new HashMap<String, Bundle>();
for (Bundle b : bundleContext.getBundles())
{
Bundle prevBundle = _bundles.put(b.getSymbolicName(), b);
String err = prevBundle != null ? "2 versions of the bundle " + b.getSymbolicName()
+ " "
+ b.getHeaders().get("Bundle-Version")
+ " and "
+ prevBundle.getHeaders().get("Bundle-Version") : "";
Assert.assertNull(err, prevBundle);
}
return _bundles.get(symbolicName);
}
protected static void assertActiveBundle(BundleContext bundleContext, String symbolicName) throws Exception
{
Bundle b = getBundle(bundleContext, symbolicName);
Assert.assertNotNull(b);
Assert.assertEquals(b.getSymbolicName() + " must be active.", Bundle.ACTIVE, b.getState());
}
protected static void assertActiveOrResolvedBundle(BundleContext bundleContext, String symbolicName) throws Exception
{
Bundle b = getBundle(bundleContext, symbolicName);
Assert.assertNotNull(b);
if (b.getHeaders().get("Fragment-Host") == null) diagnoseNonActiveOrNonResolvedBundle(b);
Assert.assertTrue(b.getSymbolicName() + " must be active or resolved. It was " + b.getState(),
b.getState() == Bundle.ACTIVE || b.getState() == Bundle.RESOLVED);
}
protected static void assertAllBundlesActiveOrResolved(BundleContext bundleContext)
{
for (Bundle b : bundleContext.getBundles())
{
if (b.getState() == Bundle.INSTALLED)
{
diagnoseNonActiveOrNonResolvedBundle(b);
}
Assert.assertTrue("Bundle: " + b
+ " (state should be "
+ "ACTIVE["
+ Bundle.ACTIVE
+ "] or RESOLVED["
+ Bundle.RESOLVED
+ "]"
+ ", but was ["
+ b.getState()
+ "])", (b.getState() == Bundle.ACTIVE) || (b.getState() == Bundle.RESOLVED));
}
}
protected static boolean diagnoseNonActiveOrNonResolvedBundle(Bundle b)
{
if (b.getState() != Bundle.ACTIVE && b.getHeaders().get("Fragment-Host") == null)
{
try
{
System.err.println("Trying to start the bundle " + b.getSymbolicName() + " that was supposed to be active or resolved.");
b.start();
System.err.println(b.getSymbolicName() + " did start");
return true;
}
catch (Throwable t)
{
System.err.println(b.getSymbolicName() + " failed to start");
t.printStackTrace(System.err);
return false;
}
}
System.err.println(b.getSymbolicName() + " was already started");
return false;
}
protected static void debugBundles(BundleContext bundleContext)
{
Map<String, Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
System.err.println("Active " + Bundle.ACTIVE);
System.err.println("RESOLVED " + Bundle.RESOLVED);
System.err.println("INSTALLED " + Bundle.INSTALLED);
for (Bundle b : bundleContext.getBundles())
{
bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
System.err.println(" " + b.getSymbolicName() + " " + b.getState());
}
}
protected static SslContextFactory newSslContextFactory()
{
SslContextFactory sslContextFactory = new SslContextFactory(true);
sslContextFactory.setEndpointIdentificationAlgorithm("");
return sslContextFactory;
}
protected static void testHttpServiceGreetings(BundleContext bundleContext, String protocol, int port) throws Exception
{
assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.boot");
assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.httpservice");
assertActiveBundle(bundleContext, "org.eclipse.equinox.http.servlet");
// in the OSGi world this would be bad code and we should use a bundle
// tracker.
// here we purposely want to make sure that the httpService is actually
// ready.
ServiceReference sr = bundleContext.getServiceReference(HttpService.class.getName());
Assert.assertNotNull("The httpServiceOSGiBundle is started and should " + "have deployed a service reference for HttpService", sr);
HttpService http = (HttpService) bundleContext.getService(sr);
http.registerServlet("/greetings", new HttpServlet()
{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getWriter().write("Hello");
}
}, null, null);
// now test the servlet
HttpClient client = protocol.equals("https") ? new HttpClient(newSslContextFactory()) : new HttpClient();
try
{
client.start();
ContentResponse response = client.GET(protocol + "://127.0.0.1:" + port + "/greetings");
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
Assert.assertEquals("Hello", content);
}
finally
{
client.stop();
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- =============================================================== -->
<!-- Mixin the Low Resources Monitor -->
<!-- =============================================================== -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addBean">
<Arg>
<New class="org.eclipse.jetty.server.LowResourceMonitor">
<Arg name="server"><Ref refid='Server'/></Arg>
<Set name="period">1000</Set>
<Set name="lowResourcesIdleTimeout">200</Set>
<Set name="monitorThreads">true</Set>
<Set name="maxConnections">0</Set>
<Set name="maxMemory">0</Set>
<Set name="maxLowResourcesTime">5000</Set>
</New>
</Arg>
</Call>
</Configure>

View File

@ -72,6 +72,10 @@
<Set name="requestHeaderSize">8192</Set>
<Set name="responseHeaderSize">8192</Set>
<!-- Uncomment to not send the server version as a header
<Set name="sendServerVersion">false</Set>
-->
<!-- Uncomment to enable handling of X-Forwarded- style headers
<Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>

View File

@ -23,10 +23,13 @@ import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@ -35,6 +38,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -141,16 +145,19 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
private final Scheduler _scheduler;
private final ByteBufferPool _byteBufferPool;
private final Thread[] _acceptors;
private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap());
private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
private volatile CountDownLatch _stopping;
private long _idleTimeout = 30000;
private String _defaultProtocol;
private ConnectionFactory _defaultConnectionFactory;
/**
* @param server The server this connector will be added to. Must not be null.
* @param executor An executor for this connector or null to use the servers executor
* @param scheduler A scheduler for this connector or null to a new {@link TimerScheduler} instance.
* @param pool A buffer pool for this connector or null to use a default {@link ByteBufferPool}
* @param scheduler A scheduler for this connector or null to either a {@link Scheduler} set as a server bean or if none set, then a new {@link TimerScheduler} instance.
* @param pool A buffer pool for this connector or null to either a {@link ByteBufferPool} set as a server bean or none set, the new {@link ArrayByteBufferPool} instance.
* @param acceptors the number of acceptor threads to use, or 0 for a default value.
* @param factories The Connection Factories to use.
*/
@ -164,7 +171,11 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
{
_server=server;
_executor=executor!=null?executor:_server.getThreadPool();
if (scheduler==null)
scheduler=_server.getBean(Scheduler.class);
_scheduler=scheduler!=null?scheduler:new TimerScheduler();
if (pool==null)
pool=_server.getBean(ByteBufferPool.class);
_byteBufferPool = pool!=null?pool:new ArrayByteBufferPool();
addBean(_server,false);
@ -468,6 +479,9 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
}
}
// protected void connectionOpened(Connection connection)
// {
// _stats.connectionOpened();
@ -488,6 +502,22 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
// newConnection.onOpen();
// }
@Override
public Collection<EndPoint> getConnectedEndPoints()
{
return _immutableEndPoints;
}
protected void onEndPointOpened(EndPoint endp)
{
_endpoints.add(endp);
}
protected void onEndPointClosed(EndPoint endp)
{
_endpoints.remove(endp);
}
@Override
public Scheduler getScheduler()
{

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Graceful;
@ -85,5 +86,8 @@ public interface Connector extends LifeCycle, Graceful
*/
public Object getTransport();
/**
* @return immutable collection of connected endpoints
*/
public Collection<EndPoint> getConnectedEndPoints();
}

Some files were not shown because too many files have changed in this diff Show More