/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright 2008 - 2009 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.pac.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.HttpConnection;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.handler.ResourceHandler;
import org.mortbay.jetty.plus.jaas.JAASUserRealm;
import org.mortbay.jetty.security.Constraint;
import org.mortbay.jetty.security.ConstraintMapping;
import org.mortbay.jetty.security.SecurityHandler;
import org.mortbay.jetty.security.SslSocketConnector;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;
import org.pentaho.pac.server.common.ConsoleProperties;
import org.pentaho.pac.server.i18n.Messages;
public class JettyServer implements Halter, IJettyServer {
protected Server server;
String delimeter = null;
String consoleHome = null;
String callbackHandler = null;
boolean securityEnabled = false;
private int portNumber;
String roles = null;
String authLoginConfigPath = null;
String realmName = null;
String loginModuleName = null;
String securitEnabledValue = null;
private static final Log logger = LogFactory.getLog(JettyServer.class);
private static int stopPort = 0;
private boolean running = false;
public static JettyServer jettyServer;
public static final int DEFAULT_PORT_NUMBER = 8099;
public static final int DEFAULT_SSL_PORT_NUMBER = 8043;
public static final int DEFAULT_STOP_PORT_NUMBER = 8011;
public static final String DEFAULT_DELIMETER = ","; //$NON-NLS-1$
public static final String DEFAULT_HOSTNAME = "localhost"; //$NON-NLS-1$
public static final String CURRENT_DIR = "."; //$NON-NLS-1$
public static final String JETTY_HOME = "jetty.home"; //$NON-NLS-1$
public static final String AUTH_LOGIN_CONFIG_ENV_VAR = "java.security.auth.login.config"; //$NON-NLS-1$
public static final String DEFAULT_CALLBACK_HANDLER = "org.mortbay.jetty.plus.jaas.callback.DefaultCallbackHandler"; //$NON-NLS-1$
public JettyServer() {
// Get the CONSOLE_HOME Environment variable. This is required as it is needed to cofigure Jetty
consoleHome = System.getProperty("CONSOLE_HOME", CURRENT_DIR);//$NON-NLS-1$
// Set the jetty.home to PEHTAHO_HOME
System.setProperty(JETTY_HOME, consoleHome);
readConfiguration();
server = new Server();
setupServer();
startServer();
stopHandler(this, stopPort);
}
public static JettyServer getInstance() {
return jettyServer;
}
public boolean isRunning() {
return running;
}
public void stop() {
Halter halter = new Halter(this);
// Create the thread supplying it with the runnable object
Thread thread = new Thread(halter);
// Start the thread
thread.start();
}
public void haltNow() {
try {
server.stop();
running = false;
} catch (Exception e) {
logger.error(Messages.getErrorString("JettyServer.ERROR_0001_UNABLE_START_SERVER"), e); //$NON-NLS-1$
}
}
private void startServer() {
Connector connector = null;
// Check whether ssl needs to be enabled or not
String value = ConsoleProperties.getInstance().getProperty(ConsoleProperties.SSLENABLED);
boolean sslEnable = (value != null && value.length() > 0) ? Boolean.parseBoolean(value) : false;
SslParameters sslParameters = new SslParameters(ConsoleProperties.getInstance());
if(sslEnable) {
connector = setupSslConnector(sslParameters);
connector.setPort(sslParameters.getSslPort());
} else {
connector = new SocketConnector();
connector.setPort(portNumber);
}
String hostIP;
String hostName;
try {
hostIP = InetAddress.getLocalHost().getHostAddress();
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
hostIP = DEFAULT_HOSTNAME;
hostName = DEFAULT_HOSTNAME;
}
if(connector instanceof SocketConnector) {
((SocketConnector) connector).setResolveNames(true);
}
server.setConnectors(new Connector[] { connector });
server.setStopAtShutdown(true);
logger.info(Messages.getString("JettyServer.CONSOLE_STARTING")); //$NON-NLS-1$
try {
server.start();
logger.info(Messages.getString("JettyServer.CONSOLE_STARTED", ((sslEnable) ? "https" : "http")//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ "://" + hostName + ":" + connector.getPort(), ((sslEnable) ? "https" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
: "http") + "://" + hostIP + ":" + connector.getPort())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
} catch (BindException e) {
haltNow();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
logger.error(Messages.getErrorString("JettyServer.ERROR_0001_UNABLE_START_SERVER"), e); //$NON-NLS-1$
}
}
public String getResourceBaseName() {
return "www/org.pentaho.pac.PentahoAdminConsole"; //$NON-NLS-1$
}
public String[] getWelcomeFiles() {
return new String[] { "PentahoAdminConsole.html" };//$NON-NLS-1$
}
protected Context createServletContext() {
ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
Context servletContext = new Context(contextHandlers, "/", Context.SESSIONS); //$NON-NLS-1$
servletContext.setResourceBase( getResourceBaseName() );
servletContext.setWelcomeFiles( getWelcomeFiles() );
return servletContext;
}
public void configureResourceHandlers( Context servletContext, SecurityHandler securityHandler ) {
ResourceHandler resources = new ResourceHandler();
resources.setResourceBase( getResourceBaseName() );
resources.setWelcomeFiles( getWelcomeFiles() );
if (securityHandler != null) {
server.setHandlers(new Handler[] { securityHandler, resources, servletContext });
} else {
server.setHandlers(new Handler[] { resources, servletContext });
}
}
public SecurityHandler configureSecurityHandler() {
SecurityHandler securityHandler = null;
// This is required for the jetty security to locate the security configuration file
System.setProperty(AUTH_LOGIN_CONFIG_ENV_VAR, authLoginConfigPath);
// configure security if necessary
if (securityEnabled) {
Constraint constraint = new Constraint();
constraint.setName(Constraint.__BASIC_AUTH);
// Creating the roles list
StringTokenizer token = new StringTokenizer(roles, delimeter);
String[] rolesList = new String[token.countTokens()];
int i =0;
while(token.hasMoreTokens()) {
rolesList[i++] = token.nextToken();
}
constraint.setRoles(rolesList);
constraint.setAuthenticate(true);
ConstraintMapping constraintMapping = new ConstraintMapping();
constraintMapping.setConstraint(constraint);
constraintMapping.setPathSpec("/*"); //$NON-NLS-1$
JAASUserRealm realm = new JAASUserRealm(realmName);
realm.setLoginModuleName(loginModuleName);
realm.setCallbackHandlerClass(callbackHandler);
securityHandler = new SecurityHandler();
securityHandler.setUserRealm(realm);
securityHandler.setConstraintMappings(new ConstraintMapping[] { constraintMapping });
}
return securityHandler;
}
public void configureServlets( Context servletContext ) {
// add servlets
ServletHolder defaultServlet = new ServletHolder(new DefaultConsoleServlet("/", this)); //$NON-NLS-1$
servletContext.addServlet(defaultServlet, "/*"); //$NON-NLS-1$
servletContext.addServlet(defaultServlet, "/halt"); //$NON-NLS-1$
ServletHolder welcomeServlet = new ServletHolder(new BrowserLocaleServlet());
servletContext.addServlet(welcomeServlet, "/browserlocalesvc"); //$NON-NLS-1$
ServletHolder pacsvc = new ServletHolder(new org.pentaho.pac.server.PacServiceImpl());
servletContext.addServlet(pacsvc, "/pacsvc"); //$NON-NLS-1$
ServletHolder schedulersvc = new ServletHolder(new org.pentaho.pac.server.SchedulerServiceImpl());
servletContext.addServlet(schedulersvc, "/schedulersvc"); //$NON-NLS-1$
ServletHolder subscriptionsvc = new ServletHolder(new org.pentaho.pac.server.SubscriptionServiceImpl());
servletContext.addServlet(subscriptionsvc, "/subscriptionsvc"); //$NON-NLS-1$
ServletHolder solutionrepositorysvc = new ServletHolder(new org.pentaho.pac.server.SolutionRepositoryServiceImpl());
servletContext.addServlet(solutionrepositorysvc, "/solutionrepositorysvc"); //$NON-NLS-1$
ServletHolder jdbcdriverdiscoveryservice = new ServletHolder(new org.pentaho.pac.server.common.JdbcDriverDiscoveryServiceImpl());
servletContext.addServlet(jdbcdriverdiscoveryservice, "/jdbcdriverdiscoverysvc"); //$NON-NLS-1$
ServletHolder hibernateconfigurationservice = new ServletHolder(new org.pentaho.pac.server.common.HibernateConfigurationServiceImpl());
servletContext.addServlet(hibernateconfigurationservice, "/hibernateconfigurationsvc"); //$NON-NLS-1$
}
public void configureEventListeners( Context servletContext ) {
// no-op for now
}
protected final void setupServer() {
server = new Server();
SecurityHandler securityHandler = configureSecurityHandler();
Context servletContext = createServletContext();
configureServlets( servletContext );
configureEventListeners( servletContext );
configureResourceHandlers( servletContext, securityHandler );
}
public int getPortNumber() {
return portNumber;
}
public void setPortNumber(int portNumber) {
this.portNumber = portNumber;
}
public static void main(String[] args) {
jettyServer = new JettyServer();
}
// TODO sbarkdull, can this be deleted?
public static class HomeHandler extends AbstractHandler {
public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
throws IOException, ServletException {
Request base_request = (request instanceof Request) ? (Request) request : HttpConnection.getCurrentConnection()
.getRequest();
base_request.setHandled(true);
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/html"); //$NON-NLS-1$
response.getWriter().println("<h1>Hello OneContext</h1>"); //$NON-NLS-1$
}
}
private class Halter implements Runnable {
// This method is called when the thread runs
private JettyServer jettyServer;
public Halter(JettyServer jettyServer) {
this.jettyServer = jettyServer;
}
public void run() {
logger.info(Messages.getString("JettyServer.WAITING_TO_HALT")); //$NON-NLS-1$
try {
Thread.sleep(3000);
} catch (Exception e) {
// ignore this
}
logger.info(Messages.getString("JettyServer.HALTING")); //$NON-NLS-1$
jettyServer.haltNow();
}
}
public void readConfiguration() {
String port = ConsoleProperties.getInstance().getProperty(ConsoleProperties.CONSOLE_PORT_NUMBER);
String stopPortNumber = ConsoleProperties.getInstance().getProperty(ConsoleProperties.STOP_PORT);
if (port != null && port.length() > 0) {
this.portNumber = Integer.parseInt(port);
} else {
this.portNumber = DEFAULT_PORT_NUMBER;
}
if (stopPortNumber != null && stopPortNumber.length() > 0) {
stopPort = Integer.parseInt(stopPortNumber);
} else {
stopPort = DEFAULT_STOP_PORT_NUMBER;
}
roles = ConsoleProperties.getInstance().getProperty(ConsoleProperties.CONSOLE_SECURITY_ROLES_ALLOWED);
String delimeterValue = ConsoleProperties.getInstance().getProperty(ConsoleProperties.CONSOLE_SECURITY_ROLE_DELIMITER);
if (delimeterValue != null && delimeterValue.length() > 0) {
this.delimeter = delimeterValue;
} else {
this.delimeter = DEFAULT_DELIMETER;
}
authLoginConfigPath = ConsoleProperties.getInstance().getProperty(ConsoleProperties.CONSOLE_SECURITY_AUTH_CONFIG_PATH);
realmName = ConsoleProperties.getInstance().getProperty(ConsoleProperties.CONSOLE_SECURITY_REALM_NAME);
loginModuleName = ConsoleProperties.getInstance().getProperty(ConsoleProperties.CONSOLE_SECURITY_LOGIN_MODULE_NAME);
String securitEnabledValue = ConsoleProperties.getInstance().getProperty(ConsoleProperties.CONSOLE_SECURITY_ENABLED);
if(securitEnabledValue != null && securitEnabledValue.length() > 0) {
securityEnabled = Boolean.parseBoolean(securitEnabledValue);
}
String callbackHandlerValue = ConsoleProperties.getInstance().getProperty(ConsoleProperties.CONSOLE_SECURITY_CALLBACK_HANDLER);
if(callbackHandlerValue != null && callbackHandlerValue.length() > 0) {
callbackHandler = callbackHandlerValue;
} else {
callbackHandler = DEFAULT_CALLBACK_HANDLER;
}
}
public void stopHandler(JettyServer jServer, int stopPort) {
ServerSocket server = null;
try {
server = new ServerSocket(stopPort);
} catch (IOException ioe) {
logger.error("IO Error:" + ioe.getLocalizedMessage()); //$NON-NLS-1$
}
try {
Socket s = server.accept();
Thread t = new Thread(new RequestHandler(jServer, s));
t.start();
} catch (Exception e) {
logger.error("IO Error:" + e.getLocalizedMessage()); //$NON-NLS-1$
}
}
private Connector setupSslConnector(SslParameters ssl) {
Connector connector;
String keyStore = ssl.getKeyStore();
if (keyStore == null) {
keyStore = System.getProperty("javax.net.ssl.keyStore", ""); //$NON-NLS-1$ //$NON-NLS-2$
if (keyStore == null) {
throw new IllegalArgumentException(Messages.getErrorString("JettyServer.ERROR_0001_KEY_STORE_MUST_BE_SET")); //$NON-NLS-1$
}
}
String keyStorePassword = ssl.getKeyStorePassword();
if (keyStorePassword == null) {
keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword"); //$NON-NLS-1$
if (keyStorePassword == null) {
throw new IllegalArgumentException(Messages.getErrorString("JettyServer.ERROR_0002_KEY_STORE_PASSWORD_MUST_BE_SET")); //$NON-NLS-1$
}
}
SslSocketConnector sslConnector = new SslSocketConnector();
sslConnector.setConfidentialPort(ssl.getSslPort());
sslConnector.setPassword(ssl.getKeyStorePassword());
sslConnector.setKeyPassword(ssl.getKeyPassword() != null ? ssl.getKeyPassword() : keyStorePassword);
sslConnector.setKeystore(keyStore);
sslConnector.setKeystoreType(ssl.getKeyStoreType());
sslConnector.setNeedClientAuth(ssl.isNeedClientAuth());
sslConnector.setWantClientAuth(ssl.isWantClientAuth());
// important to set this values for selfsigned keys
// otherwise the standard truststore of the jre is used
sslConnector.setTruststore(ssl.getTrustStore());
if (ssl.getTrustStorePassword() != null) {
// check is necessary because if a null password is set
// jetty would ask for a password on the comandline
sslConnector.setTrustPassword(ssl.getTrustStorePassword());
}
sslConnector.setTruststoreType(ssl.getTrustStoreType());
sslConnector.setResolveNames(true);
connector = sslConnector;
return connector;
}
private class RequestHandler implements Runnable {
// This method is called when the thread runs
private JettyServer jettyServer;
private Socket socket;
public RequestHandler(JettyServer jettyServer, Socket socket) {
this.jettyServer = jettyServer;
this.socket = socket;
}
public void run() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
if (inputLine != null && inputLine.length() > 0) {
inputLine = inputLine.trim();
if (inputLine.equalsIgnoreCase(ConsoleProperties.STOP_ARG)) {
logger.info(Messages.getString("JettyServer.WAITING_TO_HALT")); //$NON-NLS-1$
try {
Thread.sleep(3000);
} catch (Exception e) {
// ignore this
}
logger.info(Messages.getString("JettyServer.HALTING")); //$NON-NLS-1$
jettyServer.haltNow();
}
}
}
} catch (IOException ioe) {
logger.error(Messages.getErrorString("JettyServer.ERROR_0002_IO_ERROR",ioe.getLocalizedMessage())); //$NON-NLS-1$
}
}
}
}