//========================================================================
//$Id: IJettyService.java 474 2012-01-23 03:07:14Z janb.webtide $
//Copyright 2008 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//Licensed 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 cn.ccsu.ShareFV;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import cn.ccsu.ShareFV.R;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.ssl.SslContextFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.bio.SocketConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.server.ssl.SslSocketConnector;
import org.eclipse.jetty.util.security.Credential;
import cn.ccsu.deployer.AndroidContextDeployer;
import cn.ccsu.deployer.AndroidWebAppDeployer;
import cn.ccsu.handler.DefaultHandler;
import cn.ccsu.util.AndroidInfo;
import cn.ccsu.util.IJettyToast;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* IJettyService
*
* Android Service which runs the Jetty server, maintaining it in the active
* Notifications so that the user can return to the IJetty Activity to control
* it at any time.
*/
public class IJettyService extends Service {
private static final String TAG = "Jetty";
private static Resources __resources;
private static final String CONTENT_RESOLVER_ATTRIBUTE = "org.mortbay.ijetty.contentResolver";
private static final String ANDROID_CONTEXT_ATTRIBUTE = "org.mortbay.ijetty.context";
public static final int __START_PROGRESS_DIALOG = 0;
public static final int __STARTED = 0;
public static final int __NOT_STARTED = 1;
public static final int __STOPPED = 2;
public static final int __NOT_STOPPED = 3;
public static final int __STARTING = 4;
public static final int __STOPPING = 5;
public static final String[] __configurationClasses = new String[] {
"cn.ccsu.webapp.AndroidWebInfConfiguration",
"org.eclipse.jetty.webapp.WebXmlConfiguration",
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.webapp.TagLibConfiguration" };
private static boolean __isRunning;
private NotificationManager mNM;
private Server server;
private ContextHandlerCollection contexts;
private boolean _useNIO;
private boolean _useSSL;
private int _port;
private int _sslPort;
private String _consolePassword;
private String _keymgrPassword;
private String _keystorePassword;
private String _truststorePassword;
private String _keystoreFile;
private String _truststoreFile;
private SharedPreferences preferences;
private PackageInfo pi;
private android.os.Handler _handler;
private PowerManager.WakeLock wakeLock;
private final IBinder binder = new LocalBinder();
static {
__isRunning = false;
}
/**
* IJettyService always runs in-process with the IJetty activity.
*/
public class LocalBinder extends Binder {
IJettyService getService() {
// Return this instance of LocalService so clients can call public
// methods
return IJettyService.this;
}
}
/**
* JettyStarterThread
*
*
*/
public class JettyStarterThread extends Thread {
android.os.Handler _handler;
public JettyStarterThread(android.os.Handler handler) {
_handler = handler;
}
public void run() {
try {
sendMessage(__STARTING);
startJetty();
sendMessage(__STARTED);
Log.i(TAG, "Jetty started");
} catch (Exception e) {
sendMessage(__NOT_STARTED);
Log.e(TAG, "Error starting jetty", e);
}
}
public void sendMessage(int state) {
Message msg = _handler.obtainMessage();
Bundle b = new Bundle();
b.putInt("state", state);
msg.setData(b);
_handler.sendMessage(msg);
}
}
/**
* JettyStopperThread
*
*
*/
public class JettyStopperThread extends Thread {
android.os.Handler _handler;
public JettyStopperThread(android.os.Handler handler) {
_handler = handler;
}
public void run() {
try {
sendMessage(__STOPPING);
stopJetty();
Log.i(TAG, "Jetty stopped");
sendMessage(__STOPPED);
} catch (Exception e) {
sendMessage(__NOT_STOPPED);
Log.e(TAG, "Error stopping jetty", e);
}
}
public void sendMessage(int state) {
Message msg = _handler.obtainMessage();
Bundle b = new Bundle();
b.putInt("state", state);
msg.setData(b);
_handler.sendMessage(msg);
}
}
/**
* Hack to get around bug in ResourceBundles
*
* @param id
* @return
*/
public static InputStream getStreamToRawResource(int id) {
if (__resources != null)
return __resources.openRawResource(id);
else
return null;
}
public static boolean isRunning() {
return __isRunning;
}
/**
*
*/
public IJettyService() {
super();
_handler = new android.os.Handler() {
public void handleMessage(Message msg) {
switch (msg.getData().getInt("state")) {
case __STARTED: {
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_started);
/*
* mNM = (NotificationManager)
* getSystemService(NOTIFICATION_SERVICE); // The
* PendingIntent to launch IJetty activity if the user
* selects this notification PendingIntent contentIntent =
* PendingIntent.getActivity(IJettyService.this, 0, new
* Intent(IJettyService.this, IJetty.class), 0);
*
* CharSequence text = getText(R.string.manage_jetty);
*
* Notification notification = new
* Notification(R.drawable.ijetty_stat, text,
* System.currentTimeMillis());
*
* notification.setLatestEventInfo(IJettyService.this,
* getText(R.string.app_name), text, contentIntent);
*
* mNM.notify(R.string.jetty_started, notification);
*/
Intent startIntent = new Intent(IJetty.__START_ACTION);
startIntent.addCategory("default");
Connector[] connectors = server.getConnectors();
if (connectors != null) {
String[] tmp = new String[connectors.length];
for (int i = 0; i < connectors.length; i++)
tmp[i] = connectors[i].toString();
startIntent.putExtra("connectors", tmp);
}
sendBroadcast(startIntent);
break;
}
case __NOT_STARTED: {
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_not_started);
break;
}
case __STOPPED: {
// Cancel the persistent notification.
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNM.cancel(R.string.jetty_started);
// Tell the user we stopped.
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_stopped);
Intent stopIntent = new Intent(IJetty.__STOP_ACTION);
stopIntent.addCategory("default");
sendBroadcast(stopIntent);
break;
}
case __NOT_STOPPED: {
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_not_stopped);
break;
}
case __STARTING: {
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_starting);
break;
}
case __STOPPING: {
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_stopping);
break;
}
}
}
};
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
/**
* Android Service create
*
* @see android.app.Service#onCreate()
*/
public void onCreate() {
__resources = getResources();
try {
pi = getPackageManager().getPackageInfo(getPackageName(), 0);
} catch (Exception e) {
Log.e(TAG, "Unable to determine running jetty version");
}
}
/**
* Android Service Start
*
* @see android.app.Service#onStart(android.content.Intent, int)
*/
public void onStart(Intent intent, int startId) {
if (server != null) {
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_already_started);
return;
}
try {
preferences = PreferenceManager.getDefaultSharedPreferences(this);
String portDefault = getText(R.string.pref_port_value).toString();
String sslPortDefault = getText(R.string.pref_ssl_port_value)
.toString();
String pwdDefault = getText(R.string.pref_console_pwd_value)
.toString();
String nioEnabledDefault = getText(R.string.pref_nio_value)
.toString();
String sslEnabledDefault = getText(R.string.pref_ssl_value)
.toString();
String portKey = getText(R.string.pref_port_key).toString();
String sslPortKey = getText(R.string.pref_ssl_port_key).toString();
String pwdKey = getText(R.string.pref_console_pwd_key).toString();
String nioKey = getText(R.string.pref_nio_key).toString();
String sslKey = getText(R.string.pref_ssl_key).toString();
_useSSL = preferences.getBoolean(sslKey,
Boolean.valueOf(sslEnabledDefault));
_useNIO = preferences.getBoolean(nioKey,
Boolean.valueOf(nioEnabledDefault));
_port = Integer.parseInt(preferences
.getString(portKey, portDefault));
if (_useSSL) {
_sslPort = Integer.parseInt(preferences.getString(sslPortKey,
sslPortDefault));
String defaultValue = getText(R.string.pref_keystore_pwd_value)
.toString();
String key = getText(R.string.pref_keystore_pwd_key).toString();
_keystorePassword = preferences.getString(key, defaultValue);
defaultValue = getText(R.string.pref_keymgr_pwd_value)
.toString();
key = getText(R.string.pref_keymgr_pwd_key).toString();
_keymgrPassword = preferences.getString(key, defaultValue);
defaultValue = getText(R.string.pref_truststore_pwd_value)
.toString();
key = getText(R.string.pref_truststore_pwd_key).toString();
_truststorePassword = preferences.getString(key, defaultValue);
defaultValue = getText(R.string.pref_keystore_file).toString();
key = getText(R.string.pref_keystore_file_key).toString();
_keystoreFile = preferences.getString(key, defaultValue);
defaultValue = getText(R.string.pref_truststore_file)
.toString();
key = getText(R.string.pref_truststore_file_key).toString();
_truststoreFile = preferences.getString(key, defaultValue);
}
_consolePassword = preferences.getString(pwdKey, pwdDefault);
Log.d("Jetty", "pref port = " + _port);
Log.d("Jetty", "pref use nio = " + _useNIO);
Log.d("Jetty", "pref use ssl = " + _useSSL);
Log.d("Jetty", "pref ssl port = " + _sslPort);
// Get a wake lock to stop the cpu going to sleep
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,
"IJetty");
wakeLock.acquire();
new JettyStarterThread(_handler).start();
super.onStart(intent, startId);
} catch (Exception e) {
Log.e(TAG, "Error starting jetty", e);
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_not_started);
}
}
/**
* Android Service destroy
*
* @see android.app.Service#onDestroy()
*/
public void onDestroy() {
try {
if (wakeLock != null) {
wakeLock.release();
wakeLock = null;
}
if (server != null) {
new JettyStopperThread(_handler).start();
} else {
Log.i(TAG, "Jetty not running");
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_not_running);
}
} catch (Exception e) {
Log.e(TAG, "Error stopping jetty", e);
IJettyToast.showServiceToast(IJettyService.this,
R.string.jetty_not_stopped);
}
}
public void onLowMemory() {
Log.i(TAG, "Low on memory");
super.onLowMemory();
}
/**
* Get a reference to the Jetty Server instance
*
* @return
*/
public Server getServer() {
return server;
}
protected Server newServer() {
return new Server();
}
protected ContextHandlerCollection newContexts() {
return new ContextHandlerCollection();
}
protected void configureConnectors() {
if (server != null) {
if (_useNIO) {
SelectChannelConnector nioConnector = new SelectChannelConnector();
nioConnector.setUseDirectBuffers(false);
nioConnector.setPort(_port);
server.addConnector(nioConnector);
Log.i(TAG,
"Configured " + SelectChannelConnector.class.getName()
+ " on port " + _port);
} else {
SocketConnector bioConnector = new SocketConnector();
bioConnector.setPort(_port);
server.addConnector(bioConnector);
Log.i(TAG, "Configured " + SocketConnector.class.getName()
+ " on port " + _port);
}
if (_useSSL) {
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStore(_keystoreFile);
sslContextFactory.setTrustStore(_truststoreFile);
sslContextFactory.setKeyStorePassword(_keystorePassword);
sslContextFactory.setKeyManagerPassword(_keymgrPassword);
sslContextFactory.setKeyStoreType("bks");
sslContextFactory.setTrustStorePassword(_truststorePassword);
sslContextFactory.setTrustStoreType("bks");
// TODO SslSelectChannelConnector does not work on android 1.6,
// but does work on android 2.2
if (_useNIO) {
SslSelectChannelConnector sslConnector = new SslSelectChannelConnector(
sslContextFactory);
sslConnector.setPort(_sslPort);
server.addConnector(sslConnector);
Log.i(TAG, "Configured "
+ sslConnector.getClass().getName() + " on port "
+ _sslPort);
} else {
SslSocketConnector sslConnector = new SslSocketConnector(
sslContextFactory);
sslConnector.setPort(_sslPort);
server.addConnector(sslConnector);
Log.i(TAG, "Configured "
+ sslConnector.getClass().getName() + " on port "
+ _sslPort);
}
}
}
}
protected void configureHandlers() {
if (server != null) {
HandlerCollection handlers = new HandlerCollection();
contexts = new ContextHandlerCollection();
handlers.setHandlers(new Handler[] { contexts, new DefaultHandler() });
server.setHandler(handlers);
}
}
protected void configureDeployers() throws Exception {
AndroidWebAppDeployer staticDeployer = new AndroidWebAppDeployer();
AndroidContextDeployer contextDeployer = new AndroidContextDeployer();
File jettyDir = IJetty.__JETTY_DIR;
if (jettyDir.exists()) {
// If the webapps dir exists, start the static webapp deployer
if (new File(jettyDir, IJetty.__WEBAPP_DIR).exists()) {
staticDeployer.setWebAppDir(IJetty.__JETTY_DIR + "/"
+ IJetty.__WEBAPP_DIR);
staticDeployer.setDefaultsDescriptor(IJetty.__JETTY_DIR + "/"
+ IJetty.__ETC_DIR + "/webdefault.xml");
staticDeployer.setContexts(contexts);
staticDeployer.setAttribute(CONTENT_RESOLVER_ATTRIBUTE,
getContentResolver());
staticDeployer.setAttribute(ANDROID_CONTEXT_ATTRIBUTE,
(Context) IJettyService.this);
staticDeployer.setConfigurationClasses(__configurationClasses);
staticDeployer.setAllowDuplicates(false);
}
// Use a ContextDeploy so we can hot-deploy webapps and config at
// startup.
if (new File(jettyDir, IJetty.__CONTEXTS_DIR).exists()) {
contextDeployer.setScanInterval(10); // Don't eat the battery
contextDeployer.setConfigurationDir(IJetty.__JETTY_DIR + "/"
+ IJetty.__CONTEXTS_DIR);
contextDeployer.setAttribute(CONTENT_RESOLVER_ATTRIBUTE,
getContentResolver());
contextDeployer.setAttribute(ANDROID_CONTEXT_ATTRIBUTE,
(Context) IJettyService.this);
contextDeployer.setContexts(contexts);
}
if (server != null) {
Log.i(TAG, "Adding context deployer: ");
server.addBean(contextDeployer);
Log.i(TAG, "Adding webapp deployer: ");
server.addBean(staticDeployer);
}
} else {
Log.w(TAG, "Not loading any webapps - none on SD card.");
}
}
public void configureRealm() throws IOException {
File realmProps = new File(IJetty.__JETTY_DIR + "/" + IJetty.__ETC_DIR
+ "/realm.properties");
if (realmProps.exists()) {
HashLoginService realm = new HashLoginService("Console",
IJetty.__JETTY_DIR + "/" + IJetty.__ETC_DIR
+ "/realm.properties");
realm.setRefreshInterval(0);
if (_consolePassword != null)
realm.putUser("admin",
Credential.getCredential(_consolePassword),
new String[] { "admin" }); // set the admin password for
// console webapp
server.addBean(realm);
}
}
protected void startJetty() throws Exception {
// Set jetty.home
System.setProperty("jetty.home", IJetty.__JETTY_DIR.getAbsolutePath());
// ipv6 workaround for froyo
System.setProperty("java.net.preferIPv6Addresses", "false");
server = newServer();
configureConnectors();
configureHandlers();
configureDeployers();
configureRealm();
server.start();
__isRunning = true;
// TODO
// Less than ideal solution to the problem that dalvik doesn't know
// about manifests of jars.
// A as the version field is private to Server, its difficult
// if not impossible to set it any other way. Note this means that
// ContextHandler.SContext.getServerInfo()
// will still return 0.0.
HttpGenerator.setServerVersion("i-jetty " + pi.versionName);
}
protected void stopJetty() throws Exception {
try {
Log.i(TAG, "Jetty stopping");
server.stop();
Log.i(TAG, "Jetty server stopped");
server = null;
__resources = null;
__isRunning = false;
} finally {
Log.i(TAG, "Finally stopped");
}
}
}