View Javadoc

1   /*
2    * This file is part of Domingo
3    * an Open Source Java-API to Lotus Notes/Domino
4    * hosted at http://domingo.sourceforge.net
5    *
6    * Copyright (c) 2003-2006 Beck et al. projects GmbH München (http://www.bea.de)
7    *
8    * This library is free software; you can redistribute it and/or
9    * modify it under the terms of the GNU Lesser General Public
10   * License as published by the Free Software Foundation; either
11   * version 2.1 of the License, or (at your option) any later version.
12   *
13   * This library is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   * Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public
19   * License along with this library; if not, write to the Free Software
20   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21   */
22  
23  package de.bea.domingo.connector.impl;
24  
25  import java.io.PrintWriter;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.Set;
30  
31  import javax.resource.NotSupportedException;
32  import javax.resource.ResourceException;
33  import javax.resource.cci.Connection;
34  import javax.resource.spi.ConnectionEvent;
35  import javax.resource.spi.ConnectionEventListener;
36  import javax.resource.spi.ConnectionRequestInfo;
37  import javax.resource.spi.LocalTransaction;
38  import javax.resource.spi.ManagedConnection;
39  import javax.resource.spi.ManagedConnectionMetaData;
40  import javax.resource.spi.security.GenericCredential;
41  import javax.resource.spi.security.PasswordCredential;
42  import javax.security.auth.Subject;
43  import javax.transaction.xa.XAResource;
44  
45  import de.bea.domingo.DNotesException;
46  import de.bea.domingo.DNotesFactory;
47  import de.bea.domingo.DNotesRuntimeException;
48  import de.bea.domingo.DSession;
49  import de.bea.domingo.connector.DomingoConnection;
50  import de.bea.domingo.i18n.ResourceManager;
51  import de.bea.domingo.i18n.Resources;
52  
53  /***
54   * @author <a href=mailto:kurt.riede@bea.de>Kurt Riede</a>
55   */
56  public final class DomingoManagedConnection implements ManagedConnection, ManagedConnectionMetaData {
57  
58      /*** Internationalized resources. */
59      protected static final Resources RESOURCES = ResourceManager.getPackageResources(DomingoManagedConnection.class);
60  
61      /*** Internationalized resources. */
62      protected static final Resources METADATA = ResourceManager.getClassResources(DomingoManagedConnection.class);
63  
64      /*** Reference to a Domingo monitor for logging. */
65      private DomingoLogAdapter monitor;
66  
67      /*** Reference to the domingo factory. */
68      private DNotesFactory factory;
69  
70      /*** Reference to the domingo session. */
71      private DSession session;
72  
73      /*** Set of event listeners. */
74      private Set eventListeners = Collections.synchronizedSet(new HashSet());
75  
76      /*** Set of all available domingo connections. */
77      private Set connections = Collections.synchronizedSet(new HashSet());
78  
79      /*** Flag if the connection is already destroyed or not. */
80      private boolean isDestroyed;
81  
82      private GenericCredential genericCredential;
83      private PasswordCredential passwordCredential;
84      private boolean hasGenericCred;
85      private boolean hasPasswordCred;
86      private ConnectionRequestInfo connectionRequestInfo;
87      private DomingoManagedConnectionFactory managedConnectionFactory;
88      private boolean destroyed;
89  
90      /***
91       * Constructor (using only ConnectionRequestInfo).
92       *
93       * @param theMonitor the domingo monitor to use for monitoring
94       * @param mcf the managed connection factory that creates this connection
95       * @param cri connection request information for authentication
96       * @throws ResourceException if the connection cannot be created
97       */
98      public DomingoManagedConnection(final DomingoLogAdapter theMonitor, final DomingoManagedConnectionFactory mcf,
99              final ConnectionRequestInfo cri) throws ResourceException {
100         monitor = theMonitor;
101         managedConnectionFactory = mcf;
102         connectionRequestInfo = cri;
103         hasGenericCred = false;
104         hasPasswordCred = false;
105         try {
106             session = createPhysicalConnection(connectionRequestInfo);
107         } catch (Exception e) {
108             throw new ResourceException(RESOURCES.getString("cannot.instantiate.managed.connection"), e);
109         }
110     }
111 
112     /***
113      * Constructor (using PasswordCredential).
114      *
115      * @param theMonitor the domingo monitor to use for monitoring
116      * @param mcf the managed connection factory that creates this connection
117      * @param pc the password credentials for authentication
118      * @throws ResourceException if the connection cannot be created
119      */
120     public DomingoManagedConnection(final DomingoLogAdapter theMonitor, final DomingoManagedConnectionFactory mcf,
121             final PasswordCredential pc) throws ResourceException {
122         monitor = theMonitor;
123         managedConnectionFactory = mcf;
124         passwordCredential = pc;
125         connectionRequestInfo = null;
126         hasPasswordCred = true;
127         hasGenericCred = false;
128         try {
129             String username = pc.getUserName();
130             char[] passwd = pc.getPassword();
131             session = createPhysicalConnection(username, passwd);
132         } catch (Exception e) {
133             throw new ResourceException(RESOURCES.getString("cannot.instantiate.managed.connection"), e);
134         }
135     }
136 
137     /***
138      * Constructor (using GenericCredentials).
139      *
140      * @param theMonitor the domingo monitor to use for monitoring
141      * @param mcf the managed connection factory that creates this connection
142      * @param gc generic credentials for authentication
143      * @throws ResourceException if the connection cannot be created
144      */
145     public DomingoManagedConnection(final DomingoLogAdapter theMonitor, final DomingoManagedConnectionFactory mcf,
146             final GenericCredential gc) throws ResourceException {
147         monitor = theMonitor;
148         managedConnectionFactory = mcf;
149         genericCredential = gc;
150         connectionRequestInfo = null;
151         hasGenericCred = true;
152         hasPasswordCred = false;
153         try {
154             session = createPhysicalConnection(gc);
155         } catch (Exception e) {
156             throw new ResourceException(RESOURCES.getString("cannot.instantiate.managed.connection"), e);
157         }
158     }
159 
160     /***
161      * Returns the factorz of a managed connection.
162      *
163      * @return factory
164      */
165     DomingoManagedConnectionFactory getManagedConnectionFactory() {
166         return this.managedConnectionFactory;
167     }
168 
169     /***
170      * Returns a physical connection (using only ConnectionRequestInfo).
171      *
172      * @param cri connection request information for authentication
173      * @return physical connection to the EIS
174      * @throws ResourceException if the connection cannot be created
175      */
176     protected DSession createPhysicalConnection(final ConnectionRequestInfo cri) throws ResourceException {
177         throw new NotSupportedException(RESOURCES.getString("no.authentication"));
178     }
179 
180     /***
181      * Returns a physical connection (using GenericCredential).
182      *
183      * @param gc generic credentials for authentication
184      * @return physical connection to the EIS
185      * @throws ResourceException if the connection cannot be created
186      */
187     protected DSession createPhysicalConnection(final GenericCredential gc) throws ResourceException {
188         throw new NotSupportedException(RESOURCES.getString("no.generic.authentication"));
189     }
190 
191     /***
192      * Returns a physical connection (using username and password).
193      *
194      * @param username username for authentication
195      * @param password password for authentication
196      * @return physical connection to the EIS
197      * @throws ResourceException if the connection cannot be created
198      */
199     protected DSession createPhysicalConnection(final String username, final char[] password) throws ResourceException {
200         if (connectionRequestInfo != null) {
201             // TODO
202             throw new UnsupportedOperationException("createPhysicalConnection(String, char[])");
203         }
204         final String host = ((DomingoManagedConnectionFactory) getManagedConnectionFactory()).getHost();
205         final String port = ((DomingoManagedConnectionFactory) getManagedConnectionFactory()).getPort();
206         return createSession(host, port, username, password);
207     }
208 
209     /***
210      * Returns the domingo session of a managed connection.
211      *
212      * @return domingo session
213      */
214     public DSession getSession() {
215         return session;
216     }
217 
218     /***
219      * Returns the associated monitor.
220      *
221      * @return associated monitor
222      */
223     public DomingoLogAdapter getMonitor() {
224         return monitor;
225     }
226 
227     /***
228      * {@inheritDoc}
229      *
230      * @see javax.resource.spi.ManagedConnection#associateConnection(java.lang.Object)
231      */
232     public void associateConnection(final Object connection) throws ResourceException {
233         ((DomingoConnection) connection).reassociate(this);
234     }
235 
236     /***
237      * Returns the domingo session of the connection.
238      *
239      * @param host name or IP-address of Lotus Domino server
240      * @param port port of server of Lotus Domino server
241      * @param username username for authentication
242      * @param password password for authentication
243      * @return domingo session
244      * @throws ResourceException if the domingo session cannot be established
245      */
246     private DSession createSession(final String host, final String port, final String username, char[] password)
247             throws ResourceException {
248         monitor.debug("createSession(" + host + ", " + port + ", " + username + ", ********)");
249         if (session == null || !session.isValid()) {
250             try {
251                 // TODO maybe the factory should be cerated somewhen earlier
252                 factory = DNotesFactory.getInstance(monitor);
253                 session = factory.getSession(host + ":" + port, username, new String(password));
254             } catch (DNotesRuntimeException e) {
255                 throw new ResourceException(RESOURCES.getString("cannot.create.session.1", e.getCause().getMessage()), e);
256             }
257         }
258         if (session == null) {
259             throw new ResourceException(RESOURCES.getString("cannot.create.session"));
260         }
261         return session;
262     }
263 
264     /***
265      * {@inheritDoc}
266      *
267      * @see javax.resource.spi.ManagedConnection#getLocalTransaction()
268      */
269     public LocalTransaction getLocalTransaction() throws ResourceException {
270         throw new NotSupportedException("");
271     }
272 
273     /***
274      * {@inheritDoc}
275      *
276      * @see javax.resource.spi.ManagedConnection#getXAResource()
277      */
278     public XAResource getXAResource() throws ResourceException {
279         throw new NotSupportedException(RESOURCES.getString("method.not.supported.1", "getXAResource()"));
280     }
281 
282     /***
283      * {@inheritDoc}
284      *
285      * @see javax.resource.spi.ManagedConnection#getMetaData()
286      */
287     public ManagedConnectionMetaData getMetaData() throws ResourceException {
288         return this;
289     }
290 
291     /***
292      * {@inheritDoc}
293      *
294      * @see javax.resource.spi.ManagedConnection#getLogWriter()
295      */
296     public PrintWriter getLogWriter() throws ResourceException {
297         return monitor.getWriter();
298     }
299 
300     /***
301      * {@inheritDoc}
302      *
303      * @see javax.resource.spi.ManagedConnection#setLogWriter(java.io.PrintWriter)
304      */
305     public void setLogWriter(final PrintWriter writer) throws ResourceException {
306         monitor.setWriter(writer);
307     }
308 
309     /***
310      * {@inheritDoc}
311      *
312      * @see java.lang.Object#toString()
313      */
314     public String toString() {
315         return "[DomingoManagedConnection, todo]";
316     }
317 
318     /***
319      * {@inheritDoc}
320      *
321      * @see javax.resource.spi.ManagedConnectionMetaData#getEISProductName()
322      */
323     public String getEISProductName() throws ResourceException {
324         return DomingoMetaData.EIS_PRODUCT_NAME;
325     }
326 
327     /***
328      * {@inheritDoc}
329      *
330      * @see javax.resource.spi.ManagedConnectionMetaData#getEISProductVersion()
331      */
332     public String getEISProductVersion() throws ResourceException {
333         return session.getNotesVersion();
334     }
335 
336     /***
337      * {@inheritDoc}
338      *
339      * @see javax.resource.spi.ManagedConnectionMetaData#getMaxConnections()
340      */
341     public int getMaxConnections() throws ResourceException {
342         return DomingoMetaData.MAX_CONNECTIONS;
343     }
344 
345     /***
346      * {@inheritDoc}
347      *
348      * @return connection request information
349      */
350     public ConnectionRequestInfo getConnectionRequestInfo() {
351         return connectionRequestInfo;
352     }
353 
354     /***
355      * {@inheritDoc}
356      *
357      * @see javax.resource.spi.ManagedConnectionMetaData#getUserName()
358      */
359     public String getUserName() throws ResourceException {
360         return session.getCanonicalUserName();
361     }
362 
363     /***
364      * {@inheritDoc}
365      *
366      * @see javax.resource.spi.ManagedConnection#cleanup()
367      */
368     public void cleanup() throws ResourceException {
369         closeConnections();
370     }
371 
372     /***
373      * Adds a new connection.
374      *
375      * @param connection the new connection.
376      */
377     public void addConnection(final Connection connection) {
378         if (connection == null) {
379             monitor.error(RESOURCES.getString("argument.is.null", "connection"));
380             return;
381         }
382         synchronized (connections) {
383             connections.add(connection);
384         }
385     }
386 
387 
388     /***
389      * Removes a connection from the set of connections associated with this managed connection.
390      *
391      * @param connection The connection to remove.
392      */
393     public void removeConnection(final DomingoConnection connection) {
394         if (connection == null) {
395             monitor.error(RESOURCES.getString("argument.is.null", "connection"));
396             return;
397         }
398         synchronized (connections) {
399             connections.remove(connection);
400         }
401     }
402 
403     /***
404      * {@inheritDoc}
405      *
406      * @see javax.resource.spi.ManagedConnection#destroy()
407      */
408     public void destroy() throws ResourceException {
409         if (isDestroyed) {
410             return;
411         }
412         isDestroyed = true;
413         closeConnections();
414         try {
415             DNotesFactory.dispose(true);
416         } catch (DNotesException e) {
417             throw new ResourceException("Cannot dispose domingo factory ", e); // todo translate
418         }
419     }
420 
421     /***
422      * {@inheritDoc}
423      *
424      * @see javax.resource.spi.ManagedConnection#getConnection(javax.security.auth.Subject,
425      *      javax.resource.spi.ConnectionRequestInfo)
426      */
427     public Object getConnection(final Subject subj, final ConnectionRequestInfo cri) throws ResourceException {
428         checkIfDestroyed();
429         boolean reauth = checkForReauthentication(subj, cri);
430         if (reauth) {
431             throw new ResourceException("reauthentication.not.supported");
432         } else {
433             final Connection connection = new DomingoConnectionImpl(this);
434             addConnection(connection);
435             return connection;
436         }
437     }
438 
439     /***
440      * {@inheritDoc}
441      *
442      * Closes all connections associated with this managed connection.
443      *
444      * @throws ResourceException Failed to close one or more handles.
445      */
446     private void closeConnections() throws ResourceException {
447         final Iterator iterator = connections.iterator();
448         while (iterator.hasNext()) {
449             ((DomingoConnectionImpl) iterator.next()).close();
450         }
451         connections.clear();
452     }
453 
454     /***
455      * {@inheritDoc}
456      *
457      * @see javax.resource.spi.ManagedConnection#addConnectionEventListener(javax.resource.spi.ConnectionEventListener)
458      */
459     public void addConnectionEventListener(final ConnectionEventListener connectionEventListener) {
460         if (connectionEventListener != null) {
461             synchronized (connectionEventListener) {
462                 eventListeners.add(connectionEventListener);
463             }
464         }
465     }
466 
467     /***
468      * {@inheritDoc}
469      *
470      * @see javax.resource.spi.ManagedConnection#removeConnectionEventListener(javax.resource.spi.ConnectionEventListener)
471      */
472     public void removeConnectionEventListener(final ConnectionEventListener connectionEventListener) {
473         if (connectionEventListener != null) {
474             synchronized (connectionEventListener) {
475                 eventListeners.remove(connectionEventListener);
476             }
477         }
478     }
479 
480     /***
481      * {@inheritDoc}
482      *
483      * @see javax.resource.spi.ConnectionEventListener#connectionClosed(javax.resource.spi.ConnectionEvent)
484      */
485     public void connectionClosed(final ConnectionEvent event) {
486         sendEvent(event);
487     }
488 
489     /***
490      * {@inheritDoc}
491      *
492      * @see javax.resource.spi.ConnectionEventListener#localTransactionStarted(javax.resource.spi.ConnectionEvent)
493      */
494     public void localTransactionStarted(final ConnectionEvent event) {
495         sendEvent(event);
496     }
497 
498     /***
499      * {@inheritDoc}
500      *
501      * @see javax.resource.spi.ConnectionEventListener#localTransactionCommitted(javax.resource.spi.ConnectionEvent)
502      */
503     public void localTransactionCommitted(final ConnectionEvent event) {
504         sendEvent(event);
505     }
506 
507     /***
508      * {@inheritDoc}
509      *
510      * @see javax.resource.spi.ConnectionEventListener#localTransactionRolledback(javax.resource.spi.ConnectionEvent)
511      */
512     public void localTransactionRolledback(final ConnectionEvent event) {
513         sendEvent(event);
514     }
515 
516     /***
517      * {@inheritDoc}
518      *
519      * @see javax.resource.spi.ConnectionEventListener#connectionErrorOccurred(javax.resource.spi.ConnectionEvent)
520      */
521     public void connectionErrorOccurred(final ConnectionEvent event) {
522         sendEvent(event);
523     }
524 
525     /***
526      * Sends an event to all available event listeners.
527      *
528      * @param event the event to send
529      */
530     private void sendEvent(final ConnectionEvent event) {
531         final int type = event.getId();
532         ConnectionEventListener[] list = (ConnectionEventListener[]) eventListeners.toArray();
533         for (int i = 0; i < list.length; i++) {
534             switch (type) {
535             case ConnectionEvent.CONNECTION_CLOSED:
536                 list[i].connectionClosed(event);
537                 break;
538             case ConnectionEvent.LOCAL_TRANSACTION_STARTED:
539                 list[i].localTransactionStarted(event);
540                 break;
541             case ConnectionEvent.LOCAL_TRANSACTION_COMMITTED:
542                 list[i].localTransactionCommitted(event);
543                 break;
544             case ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK:
545                 list[i].localTransactionRolledback(event);
546                 break;
547             case ConnectionEvent.CONNECTION_ERROR_OCCURRED:
548                 list[i].connectionErrorOccurred(event);
549                 break;
550             default:
551                 throw new IllegalArgumentException(RESOURCES.getString("illegal.event.type", new Integer(type)));
552             }
553         }
554     }
555 
556     private boolean checkForReauthentication(Subject subj, ConnectionRequestInfo cri) {
557         boolean reauth = false;
558         if (hasGenericCred) {
559             GenericCredential gc = extractGenericCredential(subj);
560             if (gc == null) {
561                 reauth = true;
562             } else if (!gc.equals(genericCredential)) {
563                 reauth = true;
564             }
565         } else if (hasPasswordCred) {
566             PasswordCredential pc = extractPasswordCredential(subj);
567             if (pc == null) {
568                 reauth = true;
569             } else if (!pc.equals(passwordCredential)) {
570                 reauth = true;
571             }
572         } else if (connectionRequestInfo != null && cri != null && !connectionRequestInfo.equals(cri)) {
573             reauth = true;
574         }
575         return reauth;
576     }
577 
578     private GenericCredential extractGenericCredential(final Subject subj) {
579         GenericCredential gc = null;
580         Set theSet = subj.getPrivateCredentials(GenericCredential.class);
581         if (theSet.size() == 0) {
582             theSet = subj.getPublicCredentials(GenericCredential.class);
583         }
584         Iterator iter = theSet.iterator();
585         if (iter.hasNext()) {
586             gc = (GenericCredential) iter.next();
587         }
588         return gc;
589     }
590 
591     private PasswordCredential extractPasswordCredential(final Subject subj) {
592         PasswordCredential pc = null;
593         Set theSet = subj.getPrivateCredentials(PasswordCredential.class);
594         for (Iterator iter = theSet.iterator(); iter.hasNext();) {
595             PasswordCredential temp = (PasswordCredential) iter.next();
596             if (temp.getManagedConnectionFactory().equals(managedConnectionFactory)) {
597                 pc = temp;
598                 break;
599             }
600         }
601         return pc;
602     }
603 
604     private void checkIfDestroyed() throws IllegalStateException {
605         if (destroyed) {
606             throw new IllegalStateException(RESOURCES.getString("connection.already.destroyed"));
607         } else {
608             return;
609         }
610     }
611 }