blob: e06b24fded44a75ba9a7022defee6817e61a6c47 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.axis2.testutils;
import java.io.File;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Random;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.WebAppContext;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.transport.http.AxisServlet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
/**
* Support for running an embedded Jetty server
*/
public class JettyServer extends AbstractAxis2Server {
/**
* The alias of the certificate to configure for Jetty's ssl context factory: {@value}
*/
private static final String CERT_ALIAS = "server";
/**
* Webapp resource base directory to use: {@value}
*/
private static final String WEBAPP_DIR = "target" + File.separator + "webapp";
private static final Log log = LogFactory.getLog(JettyServer.class);
private final boolean secure;
private File keyStoreFile;
private SSLContext clientSslContext;
private SslContextFactory serverSslContextFactory;
private Server server;
/**
* Constructor.
*
* @param repositoryPath
* The path to the Axis2 repository to use. Must not be null or empty.
* @param secure
* Whether to enable HTTPS.
*/
public JettyServer(String repositoryPath, boolean secure, AxisServiceFactory... axisServiceFactories) {
super(repositoryPath, axisServiceFactories);
this.secure = secure;
}
private String generatePassword(Random random) {
char[] password = new char[8];
for (int i=0; i<password.length; i++) {
password[i] = (char)('0' + random.nextInt(10));
}
return new String(password);
}
private void writeKeyStore(KeyStore keyStore, File file, String password) throws Exception {
FileOutputStream out = new FileOutputStream(file);
try {
keyStore.store(out, password.toCharArray());
} finally {
out.close();
}
}
private void generateKeys() throws Exception {
SecureRandom random = new SecureRandom();
// Generate key pair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024, random);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// Generate certificate
X500Name dn = new X500Name("cn=localhost,o=Apache");
BigInteger serial = BigInteger.valueOf(random.nextInt());
Date notBefore = new Date();
Date notAfter = new Date(notBefore.getTime() + 3600000L);
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(dn, serial, notBefore, notAfter, dn, subPubKeyInfo);
X509CertificateHolder certHolder = certBuilder.build(new JcaContentSignerBuilder("SHA1WithRSA").build(privateKey));
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
// Build key store
keyStoreFile = File.createTempFile("keystore", "jks", null);
String keyStorePassword = generatePassword(random);
String keyPassword = generatePassword(random);
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
keyStore.setKeyEntry(CERT_ALIAS, privateKey, keyPassword.toCharArray(), new X509Certificate[] { cert });
writeKeyStore(keyStore, keyStoreFile, keyStorePassword);
// Build trust store
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null, null);
trustStore.setCertificateEntry(CERT_ALIAS, cert);
serverSslContextFactory = new SslContextFactory();
serverSslContextFactory.setKeyStorePath(keyStoreFile.getAbsolutePath());
serverSslContextFactory.setKeyStorePassword(keyStorePassword);
serverSslContextFactory.setKeyManagerPassword(keyPassword);
serverSslContextFactory.setCertAlias(CERT_ALIAS);
clientSslContext = SSLContext.getInstance("TLS");
TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init(trustStore);
clientSslContext.init(null, tmfactory.getTrustManagers(), null);
}
@Override
public SSLContext getClientSSLContext() throws Exception {
if (secure) {
if (clientSslContext == null) {
generateKeys();
}
return clientSslContext;
} else {
return null;
}
}
@Override
protected void startServer(final ConfigurationContext configurationContext) throws Throwable {
server = new Server();
if (!secure) {
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
} else {
if (serverSslContextFactory == null) {
generateKeys();
}
ServerConnector sslConnector = new ServerConnector(server, serverSslContextFactory);
server.addConnector(sslConnector);
}
WebAppContext context = new WebAppContext();
File webappDir = new File(WEBAPP_DIR);
if (!webappDir.exists() && !webappDir.mkdirs()) {
log.error("Failed to create Axis2 webapp directory: " + webappDir.getAbsolutePath());
}
context.setResourceBase(webappDir.getAbsolutePath());
context.setContextPath("/axis2");
context.setParentLoaderPriority(true);
context.setThrowUnavailableOnStartupException(true);
@SuppressWarnings("serial")
ServletHolder servlet = new ServletHolder(new AxisServlet() {
@Override
protected ConfigurationContext initConfigContext(ServletConfig config)
throws ServletException {
return configurationContext;
}
});
//load on startup to trigger Axis2 initialization and service deployment
//this is for backward compatibility with the SimpleHttpServer which initializes Axis2 on startup
servlet.setInitOrder(0);
context.addServlet(servlet, "/services/*");
server.setHandler(context);
try {
server.start();
}
catch (SecurityException e) {
if (e.getMessage().equals("class \"javax.servlet.ServletRequestListener\"'s signer information does not match signer information of other classes in the same package")) {
log.error(
"It is likely your test classpath contains multiple different versions of servlet api.\n" +
"If you are running this test in an IDE, please configure it to exclude Rampart's core module servlet api dependency.");
throw e;
}
}
log.info("Server started on port " + getPort());
}
@Override
protected void stopServer() {
if (server != null) {
log.info("Stop called");
try {
server.stop();
} catch (Exception ex) {
log.error("Failed to stop Jetty server", ex);
}
server = null;
}
if (keyStoreFile != null) {
keyStoreFile.delete();
keyStoreFile = null;
}
clientSslContext = null;
serverSslContextFactory = null;
}
@Override
public boolean isSecure() {
return secure;
}
/**
* @return Jetty's http connector port.
* @throws IllegalStateException If Jetty is not running or the http connector cannot be found.
*/
@Override
public int getPort() throws IllegalStateException {
if (server == null) {
throw new IllegalStateException("Jetty server is not initialized");
}
if (!server.isStarted()) {
throw new IllegalStateException("Jetty server is not started");
}
Connector[] connectors = server.getConnectors();
if (connectors.length == 0) {
throw new IllegalStateException("Jetty server is not configured with any connectors");
}
for (Connector connector : connectors) {
if (connector instanceof ServerConnector) {
//must be the http connector
return ((ServerConnector) connector).getLocalPort();
}
}
throw new IllegalStateException("Could not find Jetty http connector");
}
@Override
public String getEndpoint(String serviceName) {
return String.format("%s://localhost:%s/axis2/services/%s", secure ? "https" : "http", getPort(), serviceName);
}
@Override
public EndpointReference getEndpointReference(String serviceName) {
return new EndpointReference(getEndpoint(serviceName));
}
}