/*
* The contents of this file is dual-licensed under 2
* alternative Open Source/Free licenses: LGPL 2.1 or later and
* Apache License 2.0. (starting with JNA version 4.0.0).
*
* You can freely decide which license you want to apply to
* the project.
*
* You may obtain a copy of the LGPL License at:
*
* http://www.gnu.org/licenses/licenses.html
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "LGPL2.1".
*
* You may obtain a copy of the Apache License at:
*
* http://www.apache.org/licenses/
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "AL2.0".
*/
package jnacontrib.win32;
import jnacontrib.jna.*;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinError;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.Winsvc;
import com.sun.jna.platform.win32.Winsvc.SC_HANDLE;
import com.sun.jna.platform.win32.Winsvc.SERVICE_STATUS;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import jnacontrib.jna.Advapi32.SERVICE_STATUS_HANDLE;
import jnacontrib.jna.Advapi32.SERVICE_TABLE_ENTRY;
/**
* Baseclass for a Win32 service.
*/
public abstract class Win32Service {
protected String serviceName;
private ServiceMain serviceMain;
private ServiceControl serviceControl;
private SERVICE_STATUS_HANDLE serviceStatusHandle;
private Object waitObject = new Object();
/**
* Creates a new instance of Win32Service.
*
* @param serviceName internal name of the service
*/
public Win32Service(String serviceName) {
this.serviceName = serviceName;
}
/**
* Install the service.
*
* @param displayName visible name
* @param description description
* @param dependencies array of other services to depend on or null
* @param account service account or null for LocalSystem
* @param password password for service account or null
* @return true on success
*/
public boolean install(String displayName, String description, String[] dependencies, String account, String password) {
// This needs to be adjusted for a production implementation!
//
// Determine the JVM used to invoke this installer and use it as
// runtime for the service
String javaHome = System.getProperty("java.home");
String javaBinary = javaHome + "\\bin\\java.exe";
// Assumption: This is started as:
//
// java -jar <pathToJar>
//
// This is not portable, as it assumes, that this establishes a CL hierachy,
// that starts with one ClassLoader that loads the main jar (this service
// implementation) and its childs are resposible for loading referenced
// jars.
URLClassLoader cl = (URLClassLoader) Win32Service.class.getClassLoader();
URL jarPath = cl.getURLs()[0];
try {
File jar = new File(jarPath.toURI());
return(install(displayName, description, dependencies, account, password,
javaBinary + " -jar \"" + jar.getAbsolutePath() + "\""
));
} catch (URISyntaxException ex) {
return false;
}
}
/**
* Install the service.
*
* @return true on success
* @param displayName visible name
* @param description description
* @param dependencies array of other services to depend on or null
* @param account service account or null for LocalSystem
* @param password password for service account or null
* @param command command line to start the service
* @throws java.lang.Exception
*/
public boolean install(String displayName, String description, String[] dependencies, String account, String password, String command) {
Advapi32 advapi32;
Advapi32.SERVICE_DESCRIPTION desc;
SC_HANDLE service, serviceManager;
boolean success = false;
String dep = "";
if(dependencies != null) {
for(String s : dependencies) {
dep += s + "\0";
}
}
dep += "\0";
desc = new Advapi32.SERVICE_DESCRIPTION();
desc.lpDescription = description;
advapi32 = Advapi32.INSTANCE;
serviceManager = openServiceControlManager(null, Winsvc.SC_MANAGER_ALL_ACCESS);
if(serviceManager != null) {
service = advapi32.CreateService(serviceManager, serviceName, displayName,
Winsvc.SERVICE_ALL_ACCESS, WinNT.SERVICE_WIN32_OWN_PROCESS, WinNT.SERVICE_DEMAND_START,
WinNT.SERVICE_ERROR_NORMAL,
command,
null, null, dep, account, password);
if(service != null) {
success = advapi32.ChangeServiceConfig2(service, Winsvc.SERVICE_CONFIG_DESCRIPTION, desc);
advapi32.CloseServiceHandle(service);
}
advapi32.CloseServiceHandle(serviceManager);
}
return(success);
}
/**
* Uninstall the service.
*
* @throws java.lang.Exception
* @return true on success
*/
public boolean uninstall() {
Advapi32 advapi32;
SC_HANDLE serviceManager, service;
boolean success = false;
advapi32 = Advapi32.INSTANCE;
serviceManager = openServiceControlManager(null, Winsvc.SC_MANAGER_ALL_ACCESS);
if(serviceManager != null) {
service = advapi32.OpenService(serviceManager, serviceName, Winsvc.SERVICE_ALL_ACCESS);
if(service != null) {
success = advapi32.DeleteService(service);
advapi32.CloseServiceHandle(service);
}
advapi32.CloseServiceHandle(serviceManager);
}
return(success);
}
/**
* Ask the ServiceControlManager to start the service.
* @return true on success
*/
public boolean start() {
Advapi32 advapi32;
SC_HANDLE serviceManager, service;
boolean success = false;
advapi32 = Advapi32.INSTANCE;
serviceManager = openServiceControlManager(null, WinNT.GENERIC_EXECUTE);
if(serviceManager != null) {
service = advapi32.OpenService(serviceManager, serviceName, WinNT.GENERIC_EXECUTE);
if(service != null) {
success = advapi32.StartService(service, 0, null);
advapi32.CloseServiceHandle(service);
}
advapi32.CloseServiceHandle(serviceManager);
}
return(success);
}
/**
* Ask the ServiceControlManager to stop the service.
* @return true on success
*/
public boolean stop() throws Exception {
Advapi32 advapi32;
SC_HANDLE serviceManager, service;
SERVICE_STATUS serviceStatus;
boolean success = false;
advapi32 = Advapi32.INSTANCE;
serviceManager = openServiceControlManager(null, WinNT.GENERIC_EXECUTE);
if(serviceManager != null) {
service = advapi32.OpenService(serviceManager, serviceName, WinNT.GENERIC_EXECUTE);
if(service != null) {
serviceStatus = new SERVICE_STATUS();
success = advapi32.ControlService(service, Winsvc.SERVICE_CONTROL_STOP, serviceStatus);
advapi32.CloseServiceHandle(service);
}
advapi32.CloseServiceHandle(serviceManager);
}
return(success);
}
/**
* Initialize the service, connect to the ServiceControlManager.
*/
public void init() {
Advapi32 advapi32;
SERVICE_TABLE_ENTRY entry;
serviceMain = new ServiceMain();
advapi32 = Advapi32.INSTANCE;
entry = new Advapi32.SERVICE_TABLE_ENTRY();
entry.lpServiceName = serviceName;
entry.lpServiceProc = serviceMain;
advapi32.StartServiceCtrlDispatcher((SERVICE_TABLE_ENTRY[]) entry.toArray(2));
}
/**
* Get a handle to the ServiceControlManager.
*
* @param machine name of the machine or null for localhost
* @param access access flags
* @return handle to ServiceControlManager or null when failed
*/
private SC_HANDLE openServiceControlManager(String machine, int access) {
SC_HANDLE handle = null;
Advapi32 advapi32;
advapi32 = Advapi32.INSTANCE;
handle = advapi32.OpenSCManager(machine, null, access);
return(handle);
}
/**
* Report service status to the ServiceControlManager.
*
* @param status status
* @param win32ExitCode exit code
* @param waitHint time to wait
*/
private void reportStatus(int status, int win32ExitCode, int waitHint) {
Advapi32 advapi32;
SERVICE_STATUS serviceStatus;
advapi32 = Advapi32.INSTANCE;
serviceStatus = new SERVICE_STATUS();
serviceStatus.dwServiceType = WinNT.SERVICE_WIN32_OWN_PROCESS;
serviceStatus.dwControlsAccepted = Winsvc.SERVICE_ACCEPT_STOP | Winsvc.SERVICE_ACCEPT_SHUTDOWN;
serviceStatus.dwWin32ExitCode = win32ExitCode;
serviceStatus.dwWaitHint = waitHint;
serviceStatus.dwCurrentState = status;
advapi32.SetServiceStatus(serviceStatusHandle, serviceStatus);
}
/**
* Called when service is starting.
*/
public abstract void onStart();
/*
* Called when service should stop.
*/
public abstract void onStop();
/**
* Implementation of the service main function.
*/
private class ServiceMain implements Advapi32.SERVICE_MAIN_FUNCTION {
/**
* Called when the service is starting.
*
* @param dwArgc number of arguments
* @param lpszArgv pointer to arguments
*/
public void callback(int dwArgc, Pointer lpszArgv) {
Advapi32 advapi32;
advapi32 = Advapi32.INSTANCE;
serviceControl = new ServiceControl();
serviceStatusHandle = advapi32.RegisterServiceCtrlHandlerEx(serviceName, serviceControl, null);
reportStatus(Winsvc.SERVICE_START_PENDING, WinError.NO_ERROR, 3000);
reportStatus(Winsvc.SERVICE_RUNNING, WinError.NO_ERROR, 0);
onStart();
try {
synchronized(waitObject) {
waitObject.wait();
}
} catch (InterruptedException ex) {
}
reportStatus(Winsvc.SERVICE_STOPPED, WinError.NO_ERROR, 0);
// Avoid returning from ServiceMain, which will cause a crash
// See http://support.microsoft.com/kb/201349, which recommends
// having init() wait for this thread.
// Waiting on this thread in init() won't fix the crash, though.
//System.exit(0);
}
}
/**
* Implementation of the service control function.
*/
private class ServiceControl implements Advapi32.HandlerEx {
/**
* Called when the service get a control code.
*
* @param dwControl
* @param dwEventType
* @param lpEventData
* @param lpContext
*/
public int callback(int dwControl, int dwEventType, Pointer lpEventData, Pointer lpContext) {
switch(dwControl) {
case Winsvc.SERVICE_CONTROL_STOP:
case Winsvc.SERVICE_CONTROL_SHUTDOWN:
reportStatus(Winsvc.SERVICE_STOP_PENDING, WinError.NO_ERROR, 5000);
onStop();
synchronized(waitObject) {
waitObject.notifyAll();
}
}
return WinError.NO_ERROR;
}
}
}