/**************************************************************************** * Copyright (C) 2013 ecsec GmbH. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.ifd.scio.wrapper; import java.lang.reflect.InvocationTargetException; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.List; import javax.smartcardio.CardException; import javax.smartcardio.CardTerminal; import javax.smartcardio.CardTerminals; import org.openecard.common.GenericFactoryException; import org.openecard.common.ifd.TerminalFactory; import org.openecard.common.util.ExceptionUtils; import org.openecard.ifd.scio.IFDException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Self healing CardTerminals implementation. * <p>When loading the IFDTerminalFactory and pcscd is not running, which is the case on OS X when no terminal is * connected, the factory is not working and will never do so. In that case it must be reloaded when a terminal is * connected. Unfortunately this can not be detected. In order to save the using context, in our case the IFD stack, * from taking care of reinitializing the factory, this class enables transparent access to the terminals with periodic * checks to reload the factory.</p> * <p>This behaviour can be best explained with the analogy of Schroedingers well known cat. After creating an instance * of this class, the terminals wrapped by it can be tought of as either dead (stubbed) or alive (working). By using the * functions {@link #list()} and {@link #waitForChange(long)} it can not be differentiated whether the implementation is * dead or alive. Only by "opening the box" through the {@link #isDead()} and {@link #isAlive()} functions, the state of * the implementation can be observed. Similar to as it is the case wih quantum mechanics, the observer influences the * state when observing the status. That means calls to these functions trigger the update of the factory's state.</p> * <p>Obviously PCSC on OS X only works when considering the laws of quantum mechanics,</p> * * @author Tobias Wich <tobias.wich@ecsec.de> */ public class DeadAndAliveTerminals extends CardTerminals { private static final Logger logger = LoggerFactory.getLogger(DeadAndAliveTerminals.class); private static final long WAIT_DELTA = (long) (0.5 * 1000); private boolean error; private long lastTry; private CardTerminals terminals; /** * Creates an instance of the DeadAndAliveTerminals. * * @throws IFDException Thrown in case an unrecoverable error occured while loading the factory. */ public DeadAndAliveTerminals() throws IFDException { reloadTerminals(); } private void reloadTerminals() throws IFDException { lastTry = System.currentTimeMillis(); try { // try to load the "alive" terminals TerminalFactory f = IFDTerminalFactory.getInstance(); terminals = f.terminals(); error = false; } catch (IFDException ex) { // check if it is really a SCARD_E_NO_SERVICE error, when not notify the user as this is a real error NoSuchAlgorithmException destEx = ExceptionUtils.matchPath(ex, NoSuchAlgorithmException.class, InvocationTargetException.class, GenericFactoryException.class); // TODO: i can not access the PCSC code, as the class is part of the abstracted sun classes if (destEx != null && destEx.getCause() != null && destEx.getCause().getClass().getName().endsWith(".PCSCException")) { error = true; } else { // ok this is serious throw ex; } } } /** * Checks whether the implementation is dead, meaning it is stubbed because the real implementation fails to load. * This function tries to reload the implementation when it is errornous. In order to prevent producing too much * load, the function only performs the check again after a short period of time since the last time. * * @return True if the implementation is dead, false otherwise. */ public synchronized boolean isDead() { tryReloadWhenError(); return error; } /** * The inverse of {@link #isAlive()}. * * @return True if the implementation is alive, false otherwise. */ public synchronized boolean isAlive() { return ! isDead(); } private synchronized void tryReloadWhenError() { // try to reload only if implementaion is errornous and we waited long enough long now = System.currentTimeMillis(); if (error && (now - lastTry) > WAIT_DELTA) { try { reloadTerminals(); } catch (IFDException ex) { logger.error("The TerminalFactory has a serious problem.", ex); } } } @Override public synchronized List<CardTerminal> list(State state) throws CardException { if (isDead()) { return Collections.emptyList(); } else { return terminals.list(state); } } @Override public boolean waitForChange(long timeout) throws CardException { if (timeout < 0) { throw new IllegalArgumentException("Timeout is negative."); } else if (timeout == 0) { // fix timeout value, so the handcrufted wait works as expected timeout = Long.MAX_VALUE; } long start = System.currentTimeMillis(); // as long as the terminal is dead we have to take care of the wait for ourselves // wait for a small time frame and check if the terminal is alive again while (isDead() && (System.currentTimeMillis() - start) < timeout) { try { Thread.sleep(WAIT_DELTA); } catch (InterruptedException ex) { // TODO: how can we break execution completely here? } } // either return false or perform the wait with the "alive" terminals synchronized (this) { if (isDead()) { return false; } else { return terminals.waitForChange(timeout); } } } }