Thema dieser Dokumentation / Das FirstSpirit 5 Modul- / Komponenten-Modell / Modul-Implementierung mit den Komponenten-Typen - PUBLIC, SERVICE, LIBRARY / Implementierung der SERVICE Basis-Komponente
Implementierung der SERVICE Basis-Komponente
Interface: de.espirit.firstspirit.opt.vscan:VScanService
Interface: VScanService
Package: de.espirit.firstspirit.opt.vscan
Definition der grundlegenden Service-Eigenschaften, wie z. B. Status-Codes, Laden der Konfiguration.
Class: de.espirit.firstspirit.opt.vscan.VScanServiceImpl
Klasse: VscanServiceImpl
Package: de.espirit.firstspirit.opt.vscan
Konkrekte Implementierung der Service-Komponente. Start-, Stopp-Behandlung, Reagieren auf Ereignisse wie Installation, Aktualisierung, Deinstallation. Als Beispiel ist hier das Kopieren der Konfigurationsdatei fs-vscan.conf bei der Installation des Moduls (conf/modules/FS VScan Service.VScanService) zu nennen.
public class VScanServiceImpl implements VScanService,
de.espirit.firstspirit.module.Service<VScanService>
Listing: de.espirit.firstspirit.opt.vscan.VScanServiceImpl
package de.espirit.firstspirit.opt.vscan;
import de.espirit.common.base.Logging;
import de.espirit.common.io.IoUtil;
import de.espirit.firstspirit.io.FileHandle;
import de.espirit.firstspirit.io.ServerConnection;
import de.espirit.firstspirit.module.ServerEnvironment;
import de.espirit.firstspirit.module.Service;
import de.espirit.firstspirit.module.ServiceProxy;
import de.espirit.firstspirit.module.descriptor.ServiceDescriptor;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class VScanServiceImpl implements VScanService, Service<VScanService> {
private static final Class<?> LOGGER = VScanServiceImpl.class;
public static final String MODULE_CONFIG_FILE = "fs-vscan.conf"; // NON-NLS
public static final String MODULE_LOG_FILE = MODULE_NAME + ".log"; // NON-NLS
private ServerEnvironment _environment;
private volatile boolean _running;
private VScanServiceConfiguration _config;
private FileHandle _configFile;
public VScanServiceImpl() {
_config = new VScanServiceConfiguration();
}
/** Starts the service. */
public void start() {
Logging.logInfo("Starting " + VScanService.MODULE_SERVICE_NAME + "...",
LOGGER); // NON-NLS
appendLog("Starting..."); // NON-NLS
loadConfiguration();
_running = true;
}
/** Stops the service. */
public void stop() {
Logging.logInfo("Shutting down " + VScanService.MODULE_SERVICE_NAME + "...",
LOGGER); // NON-NLS
appendLog("Shutting down..."); // NON-NLS
_running = false;
}
/**
* Returns whether the service is running.
*
* @return <code>true</code> if the service is running.
*/
public boolean isRunning() {
return _running;
}
/**
* Returns the service interface. Only methods of this interface are accessible,
* so <code>Service</code> instances must also implement this interface.
*
* @return service interface class.
*/
public Class<? extends VScanService> getServiceInterface() {
return VScanService.class;
}
/**
* A service proxy is an optional, client-side service implementation. It will
* be instantiated, {@link de.espirit.firstspirit.module.ServiceProxy
* #init(Object, de.espirit.firstspirit.access.Connection) initialized} and
* returned by {@link de.espirit.firstspirit.access.Connection
* #getService(String)}. The proxy class must have a no-arg constructor
* and must implement the {@link de.espirit.firstspirit.module.ServiceProxy}
* interface, but has not to implement the {@link #getServiceInterface()
* service-interface} itself.
*
* @return service proxy class or <code>null</code> if no proxy is provided.
*/
public Class<? extends ServiceProxy> getProxyClass() {
return null;
}
/**
* Initializes this component with the given {@link
* de.espirit.firstspirit.module.descriptor.ComponentDescriptor descriptor}
* and {@link de.espirit.firstspirit.module.ServerEnvironment environment}.
* No other method will be called before the component is initialized!
*
* @param descriptor useful descriptor information for this component.
* @param env useful environment information for this component.
*/
public void init(final ServiceDescriptor descriptor,
final ServerEnvironment env) {
_environment = env;
}
/** Event method: called if Component was
* successfully installed (not updated!)
*/
public void installed() {
copyConfigFile(getConfigFileName()); // NON-NLS
Logging.logDebug(getName() + " installed...", LOGGER); // NON-NLS
appendLog("installed"); // NON-NLS
}
/** Event method: called if Component was in uninstalling procedure */
public void uninstalling() {
Logging.logDebug(getName() + " uinstalling...", LOGGER); // NON-NLS
}
/**
* Event method: called if Component was completely updated
*
* @param oldVersionString old version, before component was updated
*/
public void updated(final String oldVersionString) {
// e.g. merge, diff, bkup the config file or do not overwrite
copyConfigFile(getConfigFileName()); // NON-NLS
Logging.logDebug(getName() + " updated...", LOGGER); // NON-NLS
appendLog("updated"); // NON-NLS
}
/**
* Copy a config file from the package dir to module config dir ->
* conf/modules/$module_name$.$module_service_name$
*
* @param file the module config file name string
*/
private void copyConfigFile(final String file) {
try {
final FileHandle configFile = _environment.getConfDir().obtain(file);
if (configFile.exists()) {
Logging.logDebug("config file " + configFile.getPath() +
" exists, do not overwrite", LOGGER); // NON-NLS
} else {
final InputStream is = getClass().getResourceAsStream(file);
if (is != null) {
try {
configFile.save(is);
} finally {
IoUtil.saveClose(is);
}
}
}
} catch (final IOException e) {
Logging.logError("Error saving file - for " + getName() + " component",
e, LOGGER); // NON-NLS
}
}
private void appendLog(final String message) {
final StringBuilder sb = new StringBuilder();
sb.append(Logging.getDateString());
sb.append('\t');
sb.append(message);
sb.append('\n');
InputStream is = null;
try {
//final FileHandle handle =
_environment.getConfDir().obtain(MODULE_LOG_FILE);
final FileHandle handle =
_environment.getLogDir().obtain(MODULE_LOG_FILE);
is = new ByteArrayInputStream(
sb.toString().getBytes("UTF-8")); // NON-NLS
handle.append(is);
} catch (final UnsupportedEncodingException e) {
throw new IllegalStateException(e); // should not happen
} catch (final IOException e) {
Logging.logError("Couldn't obtain file: " + MODULE_LOG_FILE,
e, LOGGER); // NON-NLS
} finally {
IoUtil.saveClose(is);
}
}
@SuppressWarnings({"UnusedCatchParameter"})
private ScanEngine getScanEngine(@NotNull final String className)
throws ClassNotFoundException {
// replace single call to loadAvEngine by a
// pre loaded engine pool on service start
try {
final Class<?> clazz = getClass().getClassLoader().loadClass(className);
Logging.logInfo("Loading Anti Virus Scanning Engine: " + className +
" ...", LOGGER); // NON-NLS
if (!ScanEngine.class.isAssignableFrom(clazz)) {
throw new IllegalStateException(clazz + " does not implement " +
ScanEngine.class.getName());
}
final ScanEngine result = (ScanEngine) clazz.newInstance();
result.init(_config);
if (result.isThreadSafe()) {
// todo: pooling
}
return result;
} catch (InstantiationException e) {
throw new InstantiationError("Could not instantiate virus scan engine " +
className);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Access denied - class: " + className);
}
}
public void scanFile(@NotNull final File tempFile)
throws ClassNotFoundException, UploadRejectedException {
try {
final ScanEngine scanEngine = getScanEngine(_config.getClassName());
appendLog("scanning " + tempFile + " with " +
scanEngine.getName()); // NON-NLS
scanEngine.scanFile(tempFile);
appendLog("file " + tempFile + " ok"); // NON-NLS
} catch (final UploadRejectedException e) {
appendLog("file " + tempFile + " not ok - " + e.getMessage()); // NON-NLS
throw e;
} catch (final ClassNotFoundException e) {
Logging.logError("Loading of virus scanning engine failed: " +
_config.getClassName(), e, LOGGER); // NON-NLS
throw new ClassNotFoundException(
"Loading of virus scanning engine failed: " +
_config.getClassName(), e);
}
}
// VScanServiceConfiguration
public void loadConfiguration() {
InputStream is = null;
_config.setEnvironment(_environment);
try {
_configFile = _environment.getConfDir().obtain(MODULE_CONFIG_FILE);
is = _configFile.load();
if (_configFile.isFile()) {
_config.getServiceProperties().load(is);
_config.init();
_config.setAvailableEngines(getAvailableEngines());
}
} catch (IOException e) {
Logging.logDebug("Couldn't load config file - " + _configFile.getPath(),
e, LOGGER); // NON-NLS
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
Logging.logDebug("Couldn't close config file - " +
_configFile.getPath(), e, LOGGER); // NON-NLS
}
}
}
public void saveConfiguration() {
OutputStream os = null;
try {
if (_configFile.exists() && _configFile.isFile()) {
IoUtil.copyFile(_configFile.getPath(), _configFile.getPath() + ".1");
}
os = _configFile.getOutputStream();
_config.getServiceProperties().setProperty("fsm.vscan.engine.executable",
_config.getExecutable().toString());
Logging.logDebug("Setting property - fsm.vscan.engine.executable=" +
_config.getExecutable().toString(), LOGGER); // NON-NLS
_config.getServiceProperties().setProperty("fsm.vscan.engine.class",
_config.getClassName());
Logging.logDebug("Setting property - fsm.vscan.engine.class=" +
_config.getClassName(), LOGGER); // NON-NLS
_config.getServiceProperties().setProperty("fsm.vscan.engine.timeout",
String.valueOf(_config.getTimeout()));
Logging.logDebug("Setting property - fsm.vscan.engine.timeout=" +
String.valueOf(_config.getTimeout()), LOGGER); // NON-NLS
_config.getServiceProperties().store(os, "Last modified"); // NON-NLS
} catch (IOException e) {
Logging.logDebug("Couldn't open config file: " + _configFile.getPath(),
e, LOGGER); // NON-NLS
} finally {
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
Logging.logDebug("Closing of config file output stream failed.",
e, LOGGER); // NON-NLS
}
}
}
/** {@inheritDoc} */
@NotNull
public String getName() {
return MODULE_NAME;
}
/**
* Get the module configuration file name
*
* @return the config file name string
*/
@NotNull
private static String getConfigFileName() {
return MODULE_CONFIG_FILE;
}
/** {@inheritDoc} */
public boolean isEnabled() {
return _running;
}
/** {@inheritDoc} */
public VScanServiceConfiguration getServiceConfiguration() {
loadConfiguration();
return _config;
}
public void setServiceConfiguration(final VScanServiceConfiguration config) {
_config = config;// todo: clear pool
saveConfiguration();
}
}
Class: de.espirit.firstspirit.opt.vscan.VScanServiceConfiguration
Klasse: VScanServiceConfiguration
Package: de.espirit.firstspirit.opt.vscan
Modell für die notwendigen Konfigurations-Einstellungen des Services. Getter und Setter zu jeder Konfigurationseigenschaft. Lädt bei Initialisierung die persistenten Einstellungen aus der fs-vscan.conf Datei. Besitzt eine save()-Methode, um die geladenen oder in der Konfigurationsoberfläche geänderten Einstellungen in die Konfigurationsdatei zu schreiben. Jedes Key/Value-Paar in der fs-vscan.conf entspricht hier einer Member-Variable der Klasse und benötigt hierfür jeweils einen Getter und Setter. Die init()-Methode muss ggf. um die weiteren benötigten Konfigurationsoptionen erweitet werden. Laden und Speichern der Konfigurationsoptionen erfolgt über VScanServiceImpl#saveConfiguration() und VScanServiceImpl#loadConfiguration(). Die Konfigurationsklasse implementiert hier das Interface Serializable, das diese vom serverseitigen Service zum FirstSpirit ServerManager übertragen wird. Der Service kontrolliert den Lifecycle der Konfiguration.
public class VScanServiceConfigurationimplements Serializable
Class: de.espirit.firstspirit.opt.vscan.VScanServiceConfigPanel
Klasse: VscanServiceConfigPanel
Package: de.espirit.firstspirit.opt.vscan
Die Service-Konfigurationsoberfläche, zu erreichen den FirstSpirit ServerManager. Alle notwendigen Felder der VscanServiceConfiguration können hier geladen werden – die Methode getGui() initialisiert die Konfigurationsoberfläche, wobei die Methode hasGui() hartcodiert true zurückliefern muss.
Listing: de.espirit.firstspirit.opt.vscan.VScanServiceConfigPanel
package de.espirit.firstspirit.opt.vscan.admin.gui;
import de.espirit.firstspirit.access.ServiceNotFoundException;
import de.espirit.firstspirit.common.gui.CMSDialog;
import de.espirit.firstspirit.io.ServerConnection;
import de.espirit.firstspirit.module.Configuration;
import de.espirit.firstspirit.module.ServerEnvironment;
import de.espirit.firstspirit.opt.vscan.VScanService;
import de.espirit.firstspirit.opt.vscan.VScanServiceConfiguration;
import de.espirit.firstspirit.opt.vscan.resources.ModuleResources;
import de.espirit.firstspirit.server.ManagerNotFoundException;
import info.clearthought.layout.TableLayout;
import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.Frame;
import java.net.URI;
import java.util.List;
import java.util.Set;
public class VScanServiceConfigPanel implements Configuration<ServerEnvironment> {
private VScanServiceConfiguration _config;
private ServerEnvironment _env;
private JPanel _component;
private JTextField _engineExecutableField;
private JTextField _engineTimeoutField;
private JComboBox _enginesClassList;
private List<String> _availableEngines;
public VScanServiceConfigPanel() {
}
/**
* Returns <code>true</code> if this component
* has a gui to show and change it's configuration.
*
* @return <code>true</code> if this component
* has a gui to show and change it's configuration.
*/
public boolean hasGui() {
return true;
}
/**
* Returns the configuration gui.
*
* @param masterFrame basic frame component, for creating new windows
* @return configuration gui or <code>null</code>.
*/
public JComponent getGui(final Frame masterFrame) {
if (_component == null) {
final double border = 5;
final double rowsGap = 5;
final double[][] size = {{border, TableLayout.FILL, border},
{border, TableLayout.PREFERRED, rowsGap, TableLayout.PREFERRED,
rowsGap, TableLayout.PREFERRED, border}};
final TableLayout tbl = new TableLayout(size);
final JPanel panel = new JPanel();
panel.setOpaque(false);
panel.setBorder(BorderFactory
.createTitledBorder(VScanService.MODULE_NAME));
panel.setLayout(tbl);
panel.add(getEnginePanel(), "1, 1, 1, 1");
_component = (JPanel) masterFrame.add(panel);
//_component = panel;
clearGuiValues();
initGuiValues();
}
return _component;
}
private JComponent getEnginePanel() {
final double border = 5;
final double rowsGap = 5;
final double colsGap = 5;
final double[][] size = {{border, TableLayout.PREFERRED, colsGap,
TableLayout.FILL, colsGap, TableLayout.MINIMUM, border},
{border, TableLayout.PREFERRED, rowsGap, TableLayout.PREFERRED,
rowsGap, TableLayout.PREFERRED, border}};
final TableLayout tbl = new TableLayout(size);
final JPanel enginePanel = new JPanel();
enginePanel.setOpaque(false);
enginePanel.setBorder(BorderFactory.createTitledBorder(ModuleResources
.getString("fs-resource.module.vscan.admin.gui." +
"VScanServiceConfigPanel_EngineSetup")));
enginePanel.setLayout(tbl);
enginePanel.add(new JLabel(ModuleResources.getString("fs-resource.module" +
".vscan.admin.gui.VScanServiceConfigPanel_EngineExecutable")),
"1, 1, 1, 1");
//enginePanel.add(new JLabel(ModuleResources.loadIconResource(
"de.espirit.firstspirit.opt.vscan.resources.icons", "core.png")),
"1, 1, 1, 1");// NON-NLS
//enginePanel.add(new JLabel(ModuleResources
.loadIconResource("core.png")), "1, 1, 1, 1");// NON-NLS
_engineExecutableField = new JTextField();
enginePanel.add(_engineExecutableField, "3, 1, 5, 1");
_enginesClassList = new JComboBox(loadEnginesList());
enginePanel.add(new JLabel(ModuleResources.getString("fs-resource.module" +
".vscan.admin.gui.VScanServiceConfigPanel_EngineClassname")),
"1, 3, 1, 3");
enginePanel.add(_enginesClassList, "3, 3, 5, 3");
enginePanel.add(new JLabel(ModuleResources.getString("fs-resource" +
".module.vscan.admin.gui.VScanServiceConfigPanel_EngineTimeout")),
"1, 5, 1, 5");
_engineTimeoutField = new JTextField();
enginePanel.add(_engineTimeoutField, "3, 5, 3, 5");
enginePanel.add(new JLabel("ms"), "5, 5, 5, 5");
return enginePanel;
}
/**
* Loads the current configuration from appropriate
* {@link de.espirit.firstspirit.module.ServerEnvironment#getConfDir()
* conf directory}.
*/
@SuppressWarnings({"UnusedCatchParameter"})
public void load() {
VScanService service = null;
try {
service = (VScanService) _env.getConnection()
.getService(VScanService.MODULE_SERVICE_NAME);
if (!service.isEnabled()) {
showServiceStartDialog();
}
} catch (IllegalStateException e) {
showServiceStartDialog();
} catch (ServiceNotFoundException e) {
showServiceStartDialog();
} catch (ManagerNotFoundException e) {
showServiceStartDialog();
}
if(service == null) {
service = (VScanService) _env.getConnection()
.getService(VScanService.MODULE_SERVICE_NAME);
}
_config = service.getServiceConfiguration();
_config.setEnvironment(_env);
}
/** Stores the current configuration. */
public void store() {
final VScanService service = (VScanService) _env.getConnection()
.getService(VScanService.MODULE_SERVICE_NAME);
_config.setExecutable(URI.create(_engineExecutableField.getText().trim()));
_config.setClassName(_enginesClassList.getSelectedItem().toString());
_config.setTimeout(Long.valueOf(_engineTimeoutField.getText().trim()));
//_config.save();
service.setServiceConfiguration(_config);
}
private void clearGuiValues() {
_engineExecutableField.setText("");
_engineTimeoutField.setText("");
}
private void initGuiValues() {
_engineExecutableField.setText(_config.getExecutable().toString().trim());
_engineTimeoutField.setText(String.valueOf(_config.getTimeout()).trim());
for (final Object engineClasses : _availableEngines) {
if (_config.getClassName().equals(engineClasses.toString())) {
_enginesClassList.setSelectedItem(engineClasses.toString());
}
}
}
private Object[] loadEnginesList() {
_availableEngines = _config.getAvailableEngines();
if (_availableEngines.isEmpty()) {
_availableEngines.add(ModuleResources.getString("fs-resource.module" +
".vscan.admin.gui" +
".VScanServiceConfigPanel_ErrorNoEngineAvailable"));
}
return _availableEngines.toArray();
}
/**
* Returns all parameter names.
*
* @return all parameter names.
*/
public Set<String> getParameterNames() {
return _config.getParameterNames();
}
/**
* Returns a specific parameter or <code>null</code> if it's not available.
*
* @param name parameter name.
* @return parameter or <code>null</code>.
*/
public String getParameter(final String name) {
return _config.getParameter(name);
}
/**
* Initializes this component with the given
* {@link de.espirit.firstspirit.module.ServerEnvironment environment}.
* This method is called before the instance is used.
*
* @param moduleName module name
* @param componentName component name
* @param env useful environment information for this component.
*/
public void init(final String moduleName, final String componentName,
final ServerEnvironment env) {
_env = env;
}
/**
* Returns ComponentEnvironment
*
* @return ComponentEnvironment
*/
public ServerEnvironment getEnvironment() {
return _env;
}
}
Interface: de.espirit.firstspirit.opt.vscan.ScanEngine
Interface: ScanEngine
Package: de.espirit.firstspirit.opt.vscan
Definition der grundlegenden Methoden, die von einer VirusScan-Engine zur Verfügung gestellt werden müssen (siehe auch ClamScanEngine.java und ScanEngine#JavaDoc), damit diese ohne weiteres in die bestehende Infrastruktur eingebunden werden kann.