PCSC4Java  0.2
Library PCSC for Java language.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
D:/Affaire/Perso/SmartCardToolBox/pcsc4java-framework-0.2/src/fr/redbilled/security/pcscforjava/PCSCTerminals.java
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
00003  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
00004  *
00005  * This code is free software; you can redistribute it and/or modify it
00006  * under the terms of the GNU General Public License version 2 only, as
00007  * published by the Free Software Foundation.  Oracle designates this
00008  * particular file as subject to the "Classpath" exception as provided
00009  * by Oracle in the LICENSE file that accompanied this code.
00010  *
00011  * This code is distributed in the hope that it will be useful, but WITHOUT
00012  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00013  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
00014  * version 2 for more details (a copy is included in the LICENSE file that
00015  * accompanied this code).
00016  *
00017  * You should have received a copy of the GNU General Public License version
00018  * 2 along with this work; if not, write to the Free Software Foundation,
00019  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
00020  *
00021  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
00022  * or visit www.oracle.com if you need additional information or have any
00023  * questions.
00024  */
00025 
00026 package fr.redbilled.security.pcscforjava;
00027 
00028 import java.util.*;
00029 import java.lang.ref.*;
00030 
00031 import fr.redbilled.pcscforjava.*;
00032 import static fr.redbilled.pcscforjava.CardTerminals.State.*;
00033 
00034 import static fr.redbilled.security.pcscforjava.PCSC.*;
00035 import static fr.redbilled.security.pcscforjava.PCSCDefines.*;
00036 import java.util.logging.Level;
00037 import java.util.logging.Logger;
00038 
00045 final class PCSCTerminals extends CardTerminals {
00046 
00047     // SCARDCONTEXT, currently shared between all threads/terminals
00048     private static long contextId;
00049 
00050     // terminal state used by waitForCard()
00051     private Map<String,ReaderState> stateMap;
00052 
00053     // Plug and play thread
00054     static private PCSCPnPThread     m_cardTerminalsThread = null;
00055 
00056     static private Thread           m_thread;
00057 
00058     private State                   m_state;
00059 
00060     private List<CardTerminal>      list;
00061 
00062     private Object                  m_currentList;
00063 
00064     PCSCTerminals() {
00065         // empty
00066     }
00067 
00068     static synchronized void initContext() throws PCSCException {
00069         if (contextId == 0) {
00070             contextId = SCardEstablishContext(SCARD_SCOPE_USER);
00071         }
00072     }
00073     
00074     static synchronized void releaseContext() throws PCSCException {
00075         SCardReleaseContext(contextId);
00076         contextId = 0;
00077     }
00078 
00079     private static final Map<String,Reference<TerminalImpl>> terminals
00080         = new HashMap<String,Reference<TerminalImpl>>();
00081 
00082     private static synchronized TerminalImpl implGetTerminal(String name) {
00083         Reference<TerminalImpl> ref = terminals.get(name);
00084         TerminalImpl terminal = (ref != null) ? ref.get() : null;
00085         if (terminal != null) {
00086             terminal.setContextId(contextId);
00087             return terminal;
00088         }
00089         terminal = new TerminalImpl(contextId, name);
00090         terminals.put(name, new WeakReference<TerminalImpl>(terminal));
00091 
00092         return terminal;
00093     }
00094 
00095     public synchronized List<CardTerminal> list(State state) throws CardException {
00096         if (state == null) {
00097             throw new NullPointerException();
00098         }
00099         
00100         try 
00101         {
00102             // Be sure that the context is well established
00103             initContext();
00104         } catch (PCSCException ex) 
00105         {
00106             this.startPnPThread();
00107             this.m_currentList = Collections.emptyList();
00108             return (List<CardTerminal>) this.m_currentList;
00109         }
00110         
00111         if(contextId == 0)
00112         {
00113             this.startPnPThread();
00114             this.m_currentList = Collections.emptyList();
00115             return (List<CardTerminal>) this.m_currentList;
00116         }
00117 
00118         this.m_state = state;
00119 
00120         try {
00121             String[] readerNames = SCardListReaders(contextId);
00122             list = new ArrayList<CardTerminal>(readerNames.length);
00123             if (stateMap == null) {
00124                 // If waitForChange() has never been called, treat event
00125                 // queries as status queries.
00126                 if (state == CARD_INSERTION) {
00127                     state = CARD_PRESENT;
00128                 } else if (state == CARD_REMOVAL) {
00129                     state = CARD_ABSENT;
00130                 }
00131             }
00132 
00133             for (String readerName : readerNames) {
00134                 CardTerminal terminal = implGetTerminal(readerName);
00135                 ReaderState readerState;
00136                 switch (state) {
00137                 case ALL:
00138                     list.add(terminal);
00139                     // Just to be sure that the card terminal is real
00140                     try { terminal.isCardPresent(); }
00141                     catch(Exception ex){ list.remove(terminal); }
00142                     break;
00143                 case CARD_PRESENT:
00144                     if (terminal.isCardPresent()) {
00145                         list.add(terminal);
00146                     }
00147                     break;
00148                 case CARD_ABSENT:
00149                     if (terminal.isCardPresent() == false) {
00150                         list.add(terminal);
00151                     }
00152                     break;
00153                 case CARD_INSERTION:
00154                     readerState = stateMap.get(readerName);
00155                     if ((readerState != null) && readerState.isInsertion()) {
00156                         list.add(terminal);
00157                     }
00158                     break;
00159                 case CARD_REMOVAL:
00160                     readerState = stateMap.get(readerName);
00161                     if ((readerState != null) && readerState.isRemoval()) {
00162                         list.add(terminal);
00163                     }
00164                     break;
00165                 default:
00166                     throw new CardException("Unknown state: " + state);
00167                 }
00168             }
00169             
00170             this.startPnPThread();
00171             this.m_currentList = Collections.unmodifiableList(list);
00172         } catch (PCSCException e) {
00173             if(e.getMessage().contains("SCARD_E_NO_READERS_AVAILABLE"))
00174             {
00175                 this.startPnPThread();
00176                 this.m_currentList = Collections.emptyList();
00177             }
00178             else
00179                 throw new CardException("list() failed", e);
00180         }
00181 
00182         return (List<CardTerminal>) this.m_currentList;
00183     }
00184 
00185     @Override
00186     public boolean isValidContext() {
00187         try
00188         {
00189             SCardIsValidContext(contextId);
00190             return true;
00191         }
00192         catch (PCSCException ex)
00193         {
00194             return false;
00195         }
00196     }
00197 
00198     @Override
00199     public void closeContext() throws CardException {
00200     
00201         if(m_cardTerminalsThread != null)
00202         {
00203             m_cardTerminalsThread.stop();
00204             m_cardTerminalsThread = null;
00205         }
00206         
00207         try
00208         {
00209             SCardReleaseContext(contextId);
00210             contextId = 0;
00211         }
00212         catch(PCSCException ex)
00213         {
00214             throw new CardException(ex.toString());
00215         }
00216     }
00217     
00218     private native boolean SCardIsPlugAndPlaySupported(long lContextId)
00219             throws PCSCException;
00220     
00221     public boolean isPlugAndPlaySupported() throws CardException
00222     {
00223         try
00224         {   
00225             if(contextId != 0)
00226                 return SCardIsPlugAndPlaySupported(contextId);
00227         }
00228         catch(PCSCException ex)
00229         {
00230             throw new CardException(ex.toString());
00231         }
00232         
00233         return false;
00234     }
00235 
00236     private void startPnPThread()
00237     {
00238         if(m_cardTerminalsThread == null)
00239         {
00240             m_cardTerminalsThread = PCSCPnPThread.getInstance(this);
00241             m_thread = m_cardTerminalsThread.start();
00242             m_cardTerminalsThread.addObserver(this);
00243         }
00244         else
00245         {
00246             //m_cardTerminalsThread.addObserver(this);
00247         }
00248     }
00249     
00250     public void updateCardTerminalsListByEvent() throws CardException
00251     {
00252         if (this.m_state == null)
00253             this.m_state = ALL;
00254         
00255         try  {
00256             // Be sure that the context is well established
00257             initContext();
00258         } catch (PCSCException ex) {return;}
00259         
00260         try {
00261             String[] readerNames = SCardListReaders(contextId);
00262 
00263             List<CardTerminal> _tmpList =
00264                     new ArrayList<CardTerminal>(readerNames.length);
00265 
00266             for (String readerName : readerNames) {
00267                 CardTerminal terminal = implGetTerminal(readerName);
00268                 ReaderState readerState;
00269                 switch (this.m_state) {
00270                 case ALL:
00271                     _tmpList.add(terminal);
00272                     break;
00273                 case CARD_PRESENT:
00274                     if (terminal.isCardPresent()) {
00275                         _tmpList.add(terminal);
00276                     }
00277                     break;
00278                 case CARD_ABSENT:
00279                     if (terminal.isCardPresent() == false) {
00280                         _tmpList.add(terminal);
00281                     }
00282                     break;
00283                 case CARD_INSERTION:
00284                     readerState = stateMap.get(readerName);
00285                     if ((readerState != null) && readerState.isInsertion()) {
00286                         _tmpList.add(terminal);
00287                     }
00288                     break;
00289                 case CARD_REMOVAL:
00290                     readerState = stateMap.get(readerName);
00291                     if ((readerState != null) && readerState.isRemoval()) {
00292                         _tmpList.add(terminal);
00293                     }
00294                     break;
00295                 default:
00296                     throw new CardException("Unknown state: " + this.m_state);
00297                 }
00298             }
00299 
00300             this.m_currentList = _tmpList;
00301             
00302         } catch (PCSCException e) {
00303             if(e.getMessage().contains("SCARD_E_NO_READERS_AVAILABLE"))
00304             {
00305                 this.m_currentList = Collections.emptyList();
00306             }
00307             else
00308                 throw new CardException("update() failed", e);
00309         }
00310         
00311         List _tmp = TerminalFactory.getPnPCallbacks();
00312 
00313         if(_tmp != null)
00314         {
00315             for(int _i = 0; _i < _tmp.size(); _i++)
00316                 ((CardTerminalsEvent)_tmp.get(_i)).
00317                         updateCardTerminalsListByEvent((List<CardTerminal>)
00318                         this.m_currentList);
00319         }
00320     }
00321 
00322     private static class ReaderState {
00323         private int current, previous;
00324         ReaderState() {
00325             current = SCARD_STATE_UNAWARE;
00326             previous = SCARD_STATE_UNAWARE;
00327         }
00328         int get() {
00329             return current;
00330         }
00331         void update(int newState) {
00332             previous = current;
00333             current = newState;
00334         }
00335         boolean isInsertion() {
00336             return !present(previous) && present(current);
00337         }
00338         boolean isRemoval() {
00339             return present(previous) && !present(current);
00340         }
00341         static boolean present(int state) {
00342             return (state & SCARD_STATE_PRESENT) != 0;
00343         }
00344     }
00345 
00346     public synchronized boolean waitForChange(long timeout) throws CardException {
00347         if (timeout < 0) {
00348             throw new IllegalArgumentException
00349                 ("Timeout must not be negative: " + timeout);
00350         }
00351         if (stateMap == null) {
00352             // We need to initialize the state database.
00353             // Do that with a recursive call, which will return immediately
00354             // because we pass SCARD_STATE_UNAWARE.
00355             // After that, proceed with the real call.
00356             stateMap = new HashMap<String,ReaderState>();
00357             waitForChange(0);
00358         }
00359         if (timeout == 0) {
00360             timeout = TIMEOUT_INFINITE;
00361         }
00362         try {
00363             String[] readerNames = SCardListReaders(contextId);
00364             int n = readerNames.length;
00365             if (n == 0) {
00366                 throw new IllegalStateException("No terminals available");
00367             }
00368             int[] status = new int[n];
00369             ReaderState[] readerStates = new ReaderState[n];
00370             for (int i = 0; i < readerNames.length; i++) {
00371                 String name = readerNames[i];
00372                 ReaderState state = stateMap.get(name);
00373                 if (state == null) {
00374                     state = new ReaderState();
00375                 }
00376                 readerStates[i] = state;
00377                 status[i] = state.get();
00378             }
00379             status = SCardGetStatusChange(contextId, timeout, status,
00380                     readerNames);
00381             stateMap.clear(); // remove any readers that are no longer available
00382             for (int i = 0; i < n; i++) {
00383                 ReaderState state = readerStates[i];
00384                 if(status != null)
00385                     state.update(status[i]);
00386                 stateMap.put(readerNames[i], state);
00387             }
00388             return true;
00389         } catch (PCSCException e) {
00390             if (e.code == SCARD_E_TIMEOUT) {
00391                 return false;
00392             } else {
00393                 throw new CardException("waitForChange() failed", e);
00394             }
00395         }
00396     }
00397 
00398     static List<CardTerminal> waitForCards(List<? extends CardTerminal> terminals,
00399             long timeout, boolean wantPresent) throws CardException {
00400         // the argument sanity checks are performed in
00401         // javax.smartcardio.TerminalFactory or TerminalImpl
00402 
00403         long thisTimeout;
00404         if (timeout == 0) {
00405             timeout = TIMEOUT_INFINITE;
00406             thisTimeout = TIMEOUT_INFINITE;
00407         } else {
00408             // if timeout is not infinite, do the initial call that retrieves
00409             // the status with a 0 timeout. Otherwise, we might get incorrect
00410             // timeout exceptions (seen on Solaris with PC/SC shim)
00411             thisTimeout = 0;
00412         }
00413 
00414         String[] names = new String[terminals.size()];
00415         int i = 0;
00416         for (CardTerminal terminal : terminals) {
00417             if (terminal instanceof TerminalImpl == false) {
00418                 throw new IllegalArgumentException
00419                     ("Invalid terminal type: " + terminal.getClass().getName());
00420             }
00421             TerminalImpl impl = (TerminalImpl)terminal;
00422             names[i++] = impl.name;
00423         }
00424 
00425         int[] status = new int[names.length];
00426         Arrays.fill(status, SCARD_STATE_UNAWARE);
00427 
00428         try {
00429             while (true) {
00430                 // note that we pass "timeout" on each native PC/SC call
00431                 // that means that if we end up making multiple (more than 2)
00432                 // calls, we might wait too long.
00433                 // for now assume that is unlikely and not a problem.
00434                 status = SCardGetStatusChange(contextId, thisTimeout,
00435                         status, names);
00436                 thisTimeout = timeout;
00437 
00438                 List<CardTerminal> results = null;
00439                 for (i = 0; i < names.length; i++) {
00440                     boolean nowPresent = (status[i] & SCARD_STATE_PRESENT) != 0;
00441                     if (nowPresent == wantPresent) {
00442                         if (results == null) {
00443                             results = new ArrayList<CardTerminal>();
00444                         }
00445                         results.add(implGetTerminal(names[i]));
00446                     }
00447                 }
00448 
00449                 if (results != null) {
00450                     return Collections.unmodifiableList(results);
00451                 }
00452             }
00453         } catch (PCSCException e) {
00454             if (e.code == SCARD_E_TIMEOUT) {
00455                 return Collections.emptyList();
00456             } else {
00457                 throw new CardException("waitForCard() failed", e);
00458             }
00459         }
00460     }
00461 
00462 }