/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
package de.cismet.reconnector;
import Sirius.navigator.ui.ComponentRegistry;
import org.openide.util.NbBundle;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import de.cismet.cids.server.ws.rest.SSLInitializationException;
import de.cismet.tools.gui.StaticSwingTools;
/**
* DOCUMENT ME!
*
* @author jruiz
* @version $Revision$, $Date$
*/
public abstract class Reconnector<S extends Object> {
//~ Static fields/initializers ---------------------------------------------
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(Reconnector.class);
//~ Enums ------------------------------------------------------------------
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
private static enum ReconnectorState {
//~ Enum constants -----------------------------------------------------
CONNECTING, CANCELED, COMPLETED, FAILED
}
//~ Instance fields --------------------------------------------------------
private Class serviceClass;
private ConnectorWorker connectorWorker;
private JDialog reconnectorDialog;
private JFrame dialogOwner;
private S service;
private boolean isReconnecting = true;
private final ReconnectorListener dispatcher = new ListenerDispatcher();
private List<ReconnectorListener> listeners = new LinkedList<ReconnectorListener>();
private List<ReconnectorPanel> reconnectorPanels = new LinkedList<ReconnectorPanel>();
// private List<ReconnectorWrapperPanel> wrapperPanels = new LinkedList<ReconnectorWrapperPanel>();
//~ Constructors -----------------------------------------------------------
/**
* Creates a new Reconnector object.
*
* @param serviceClass DOCUMENT ME!
*/
protected Reconnector(final Class serviceClass) {
this.serviceClass = serviceClass;
}
//~ Methods ----------------------------------------------------------------
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws ReconnectorException DOCUMENT ME!
*/
protected abstract S connectService() throws ReconnectorException;
/**
* DOCUMENT ME!
*
* @param throwable DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws Throwable DOCUMENT ME!
*/
protected abstract ReconnectorException getReconnectorException(final Throwable throwable) throws Throwable;
/**
* Abbrechen.
*/
public void doAbort() {
reconnectorDialog.setVisible(false);
isReconnecting = false;
}
/**
* Verbindungsaufbau über Swingworker.
*/
public void doReconnect() {
// Worker schon vorhanden?
if (connectorWorker != null) {
// worker anhalten
connectorWorker.cancel(false);
connectorWorker = null;
}
if (isReconnecting) {
// neuen Worker erzeugen und starten
connectorWorker = new ConnectorWorker();
connectorWorker.execute();
}
}
/**
* Verbindungsaufbau abbrechen.
*/
public void doCancel() {
if ((connectorWorker != null) && !connectorWorker.isDone()) {
connectorWorker.cancel(true);
connectorWorker = null;
if (LOG.isDebugEnabled()) {
LOG.debug("Verbindungsvorgang abgebrochen"); // NOI18N
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("nichts zum Abbrechen"); // NOI18N
}
}
}
/**
* DOCUMENT ME!
*/
public void doIgnore() {
reconnectorDialog.dispose();
}
/**
* DOCUMENT ME!
*
* @param listener DOCUMENT ME!
*/
public void addListener(final ReconnectorListener listener) {
listeners.add(listener);
}
/**
* Service Proxy.
*
* @return DOCUMENT ME!
*/
public S getProxy() {
return (S)Proxy.newProxyInstance(
serviceClass.getClassLoader(),
new Class[] { serviceClass },
new ReconnectorInvocationHandler());
}
/**
* DOCUMENT ME!
*/
void pack() {
reconnectorDialog.pack();
}
/**
* DOCUMENT ME!
*
* @param useDialog DOCUMENT ME!
* @param parentFrame DOCUMENT ME!
*/
public void useDialog(final boolean useDialog, final JFrame parentFrame) {
if (useDialog) {
if (parentFrame != null) {
dialogOwner = parentFrame;
} else {
dialogOwner = null;
}
final String title = NbBundle.getMessage(
Reconnector.class,
"Reconnector.useDialog().reconnectorDialog.title");
reconnectorDialog = new JDialog(dialogOwner, title, true);
reconnectorDialog.setResizable(true);
reconnectorDialog.setMinimumSize(new Dimension(400, 150));
reconnectorDialog.setContentPane(createReconnectorPanel());
reconnectorDialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(final WindowEvent we) {
doAbort();
}
});
reconnectorDialog.setAlwaysOnTop(true);
} else {
// TODO reconnectorPanel des Dialogs aus der Liste entfernen
if (reconnectorDialog != null) {
final ReconnectorPanel panel = (ReconnectorPanel)reconnectorDialog.getContentPane();
reconnectorPanels.remove(panel);
listeners.remove(panel);
}
dialogOwner = null;
reconnectorDialog = null;
}
}
/**
* public JPanel registerFrame(final JPanel panel) { final ReconnectorWrapperPanel reconnectorWrapperPanel = new
* ReconnectorWrapperPanel(panel, createReconnectorPanel()); addListener(reconnectorWrapperPanel);
* //wrapperPanels.add(reconnectorWrapperPanel); return reconnectorWrapperPanel; } Die Nutzung des services ist
* Fehlgeschlagen. Je nach Modus (attended / unattend) wird die Verbindung entweder sofort wieder hergestellt, oder
* es wird eine Fehlermeldung angezeigt und darauf gewartet, dass der User die Verbindung wieder anstößt.
*
* @param exception DOCUMENT ME!
*/
private void serviceFailed(final ReconnectorException exception) {
final Component component = exception.getComponent();
if (isUnattended()) {
doReconnect();
} else {
dispatcher.connectionFailed(new ReconnectorEvent(component));
}
}
/**
* mit GUI oder ohne?
*
* @return DOCUMENT ME!
*/
private boolean isUnattended() {
return (reconnectorDialog == null /* && wrapperPanels.isEmpty()*/);
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private ReconnectorPanel createReconnectorPanel() {
final ReconnectorPanel reconnectorPanel = new ReconnectorPanel(this);
reconnectorPanels.add(reconnectorPanel);
addListener(reconnectorPanel);
return reconnectorPanel;
}
/**
* DOCUMENT ME!
*/
private void updateReconnectorDialogOwner() {
if (dialogOwner == null) {
if (ComponentRegistry.isRegistred()) {
useDialog(false, null);
useDialog(true, ComponentRegistry.getRegistry().getMainWindow());
}
}
}
//~ Inner Classes ----------------------------------------------------------
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
class ReconnectorInvocationHandler implements InvocationHandler {
//~ Methods ------------------------------------------------------------
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
Throwable targetEx = null;
while (isReconnecting) {
try {
// service wieder verbinden
if (service == null) {
service = connectService();
}
// Methodenaufruf durchreichen
return method.invoke(service, args);
// wieder verbinden fehlgeschlagen
} catch (final ReconnectorException ex) {
// Fehler anzeigen und neuverbindung anfragen
serviceFailed(ex);
} catch (final InvocationTargetException ex) {
if (LOG.isDebugEnabled()) {
LOG.debug("Exception while invocation", ex); // NOI18N
}
targetEx = ex.getTargetException();
if (LOG.isDebugEnabled()) {
LOG.debug("Wrapped Exception", targetEx); // NOI18N
}
serviceFailed(getReconnectorException(targetEx));
} catch (final SSLInitializationException sslInitEx) {
if (LOG.isDebugEnabled()) {
LOG.debug("Exception while invocation", sslInitEx); // NOI18N
}
targetEx = sslInitEx.getCause();
if (LOG.isDebugEnabled()) {
LOG.debug("Wrapped Exception", targetEx); // NOI18N
}
serviceFailed(getReconnectorException(targetEx));
}
}
isReconnecting = true;
throw targetEx;
}
}
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
class ConnectorWorker extends SwingWorker<S, Void> {
//~ Methods ------------------------------------------------------------
@Override
protected S doInBackground() throws Exception {
dispatcher.connecting();
// alten service verwerfen
service = null;
// Verbindungs-Prozess starten
try {
return connectService();
} catch (ReconnectorException exception) {
serviceFailed(exception);
throw new ExecutionException(exception);
}
}
@Override
protected void done() {
// abgebrochen ?
if (isCancelled()) {
dispatcher.connectionCanceled();
// abgeschlossen ?
} else {
try {
// neuen service setzen
service = get();
// panels verstecken
dispatcher.connectionCompleted();
// Fehler beim Verbinden?
} catch (final Exception ex) {
// Neuverbindung in Gang setzen
if (ex instanceof ExecutionException) {
} else {
serviceFailed(new ReconnectorException(ex.getClass().getCanonicalName()));
}
}
}
}
}
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
class ListenerDispatcher implements ReconnectorListener {
//~ Methods ------------------------------------------------------------
/**
* DOCUMENT ME!
*
* @param state DOCUMENT ME!
* @param evt DOCUMENT ME!
*/
private void updateAndNotifyAboutState(final ReconnectorState state, final ReconnectorEvent evt) {
if (reconnectorDialog != null) {
final boolean isVisible = reconnectorDialog.isVisible();
if (!isVisible) {
updateReconnectorDialogOwner();
}
switch (state) {
case CONNECTING: {
for (final ReconnectorListener listener : listeners) {
listener.connecting();
}
break;
}
case FAILED: {
for (final ReconnectorListener listener : listeners) {
listener.connectionFailed(evt);
}
break;
}
case COMPLETED: {
for (final ReconnectorListener listener : listeners) {
listener.connectionCompleted();
}
break;
}
case CANCELED: {
for (final ReconnectorListener listener : listeners) {
listener.connectionCanceled();
}
break;
}
}
reconnectorDialog.pack();
if (!isVisible) {
StaticSwingTools.showDialog(reconnectorDialog);
} else if (isVisible
&& ((state == ReconnectorState.COMPLETED) || (state == ReconnectorState.CANCELED))) {
reconnectorDialog.setVisible(false);
}
}
}
@Override
public void connecting() {
this.updateAndNotifyAboutState(ReconnectorState.CONNECTING, null);
}
@Override
public void connectionFailed(final ReconnectorEvent event) {
this.updateAndNotifyAboutState(ReconnectorState.FAILED, event);
}
@Override
public void connectionCompleted() {
this.updateAndNotifyAboutState(ReconnectorState.COMPLETED, null);
}
@Override
public void connectionCanceled() {
this.updateAndNotifyAboutState(ReconnectorState.CANCELED, null);
}
}
}