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-2007 Beck et al. projects GmbH Munich, Germany (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.service;
24  
25  import java.applet.Applet;
26  import java.lang.reflect.Array;
27  import java.lang.reflect.Method;
28  
29  import de.bea.domingo.DNotesFactory;
30  import de.bea.domingo.DNotesMonitor;
31  import de.bea.domingo.DNotesRuntimeException;
32  import de.bea.domingo.DSession;
33  import de.bea.domingo.i18n.ResourceManager;
34  import de.bea.domingo.i18n.Resources;
35  import de.bea.domingo.monitor.NullMonitor;
36  import de.bea.domingo.proxy.DNotesThread;
37  import de.bea.domingo.proxy.NotesProxyException;
38  import de.bea.domingo.proxy.NotesProxyFactory;
39  import de.bea.domingo.threadpool.SimpleThreadPool;
40  import de.bea.domingo.threadpool.ThreadFactory;
41  import de.bea.domingo.threadpool.ThreadPool;
42  import de.bea.domingo.threadpool.ThreadPoolException;
43  
44  /***
45   * Factory for sessions to Notes/Domino.
46   *
47   * <p>Local calls are processed in a thread pool with notes thread registered
48   * with a local client installation. Each call to the getSession() method
49   * returns the same instance.</p> <p>Corba calls are always processed in the
50   * calling thread. Each call to a getSession(...) with arguments returns a new
51   * instance, so it is up to the caller to ensure proper disposal of the
52   * sessions.</p>
53   *
54   * @author <a href=mailto:kriede@users.sourceforge.net>Kurt Riede</a>
55   */
56  public final class NotesServiceFactory extends DNotesFactory {
57  
58      // //////////////////////////////////////////////
59      // constants
60      // //////////////////////////////////////////////
61  
62      /*** Empty array of parameter types for reflection. */
63      private static final Class[] PARAMS_EMPTY = new Class[]{};
64  
65      /*** Array of parameter types for reflection: <code>{ String }</code>. */
66      private static final Class[] PARAMS_STRING = { String.class};
67  
68      /*** Array of parameter types for reflection: <code>{ String, String, String }</code>. */
69      private static final Class[] PARAMS_STRING3 = { String.class, String.class, String.class};
70  
71      /*** Array of parameter types for reflection: <code>{ String, Array, String, String }</code>. */
72      private static final Class[] PARAMS_STRING_ARRAY_STRING_STRING = { String.class, Array.class, String.class, String.class};
73  
74      /*** Array of parameter types for reflection: <code>{ Applet, String, String }</code>. */
75      private static final Class[] PARAMS_APPLET_STRING_STRING = new Class[]{ Applet.class, String.class, String.class};
76  
77      /*** Array of parameter types for reflection: <code>{ Boolean }</code>. */
78      private static final Class[] PARAMS_BOOLEAN = new Class[] { Boolean.TYPE };
79  
80      /*** Array of parameter types for reflection: <code>{ DSession }</code>. */
81      private static final Class[] PARAMS_SESSION = new Class[] { DSession.class };
82  
83      /*** Empty array of arguments for reflection. */
84      private static final Object[] ARGS_EMPTY = new Object[]{};
85  
86      /*** Array of argument values for reflection: <code>{ Boolean.TRUE }</code>. */
87      private static final Object[] ARGS_TRUE = { Boolean.TRUE };
88  
89      /*** Array of argument values for reflection: <code>{  Boolean.FALSE }</code>. */
90      private static final Object[] ARGS_FALSE = { Boolean.FALSE};
91  
92      /*** Default timeout. */
93      public static final long DEFAULT_TIMEOUT = 10000;
94  
95      /*** Default number of threads in thread pool. */
96      public static final int DEFAULT_THREADPOOL_SIZE = 1;
97  
98      /*** Internationalized resources. */
99      private static final Resources RESOURCES = ResourceManager.getPackageResources(NotesServiceFactory.class);
100 
101     // //////////////////////////////////////////////
102     // static attributes
103     // //////////////////////////////////////////////
104 
105     /*** Reference to associated ThreadPool. */
106     private ThreadPool threadPool = null;
107 
108     // //////////////////////////////////////////////
109     // instance attributes
110     // //////////////////////////////////////////////
111 
112     /*** Base Logger instance. */
113     private DNotesMonitor monitor;
114 
115     /*** Associated internal factory. */
116     private NotesProxyFactory factory = null;
117 
118     /*** Associated thread factory. */
119     private NotesThreadFactory threadFactory;
120 
121     // //////////////////////////////////////////////
122     // creation
123     // //////////////////////////////////////////////
124 
125     /***
126      * Default constructor.
127      *
128      * <p>Must be public to allow abstract factory (the base class) to create
129      * an instance of this class.</p>
130      *
131      * @throws DNotesRuntimeException if the factory cannot be created
132      */
133     public NotesServiceFactory() throws DNotesRuntimeException {
134         try {
135             factory = new NotesProxyFactory();
136         } catch (NoClassDefFoundError t) {
137             throwWrappedException(t);
138         }
139         setMonitor(NullMonitor.getInstance());
140     }
141 
142     // //////////////////////////////////////////////
143     // interface DNotesFactory
144     // //////////////////////////////////////////////
145 
146     /***
147      * {@inheritDoc}
148      *
149      * @see de.bea.domingo.DNotesFactory#getSession(java.lang.Object)
150      */
151     public DSession getSession(final Object notesSession) throws DNotesRuntimeException {
152         throw new NotesServiceRuntimeException("Invalid method call");
153     }
154 
155     /***
156      * {@inheritDoc}
157      *
158      * @see de.bea.domingo.DNotesFactory#getSession()
159      */
160     public DSession getSession() throws DNotesRuntimeException {
161         return createSession("getSession", ARGS_EMPTY, PARAMS_EMPTY);
162     }
163 
164     /***
165      * {@inheritDoc}
166      *
167      * @see de.bea.domingo.DNotesFactory#getSession(java.lang.String)
168      */
169     public DSession getSession(final String serverUrl) throws DNotesRuntimeException {
170         final Object[] arguments = { serverUrl };
171         return createSession("getSession", arguments, PARAMS_STRING);
172     }
173 
174     /***
175      * {@inheritDoc}
176      *
177      * @see de.bea.domingo.DNotesFactory#getSession(java.lang.String,
178      *      java.lang.String, java.lang.String)
179      */
180     public DSession getSession(final String host, final String user, final String passwd) throws DNotesRuntimeException {
181         final Object[] arguments = { host, user, passwd };
182         return createSession("getSession", arguments, PARAMS_STRING3);
183     }
184 
185     /***
186      * {@inheritDoc}
187      *
188      * @see de.bea.domingo.DNotesFactory#getSession(java.lang.String, java.lang.String[], java.lang.String, java.lang.String)
189      */
190     public DSession getSession(final String host, final String[] args, final String user, final String passwd) throws DNotesRuntimeException {
191         final Object[] arguments = { host, args, user, passwd };
192         return createSession("getSession", arguments, PARAMS_STRING_ARRAY_STRING_STRING);
193     }
194 
195     /***
196      * {@inheritDoc}
197      *
198      * @see de.bea.domingo.DNotesFactory#getSessionSSL(java.lang.String, java.lang.String, java.lang.String)
199      */
200     public DSession getSessionSSL(final String host, final String user, final String passwd) throws DNotesRuntimeException {
201         final Object[] arguments = { host, user, passwd };
202         return createSession("getSession", arguments, PARAMS_STRING3);
203     }
204 
205     /***
206      * {@inheritDoc}
207      *
208      * @see de.bea.domingo.DNotesFactory#getSession(java.applet.Applet,
209      *      java.lang.String, java.lang.String)
210      */
211     public DSession getSession(final Applet applet, final String user, final String password) throws DNotesRuntimeException {
212         final Object[] args = { applet, user, password };
213         return createSession("getSession", args, PARAMS_APPLET_STRING_STRING);
214     }
215 
216     /***
217      * {@inheritDoc}
218      *
219      * @see de.bea.domingo.DNotesFactory#getSessionWithFullAccess()
220      */
221     public DSession getSessionWithFullAccess() throws DNotesRuntimeException {
222         return createSession("getSessionWithFullAccess", ARGS_EMPTY, PARAMS_EMPTY);
223     }
224 
225     /***
226      * {@inheritDoc}
227      *
228      * @see de.bea.domingo.DNotesFactory#getSessionWithFullAccess(java.lang.String)
229      */
230     public DSession getSessionWithFullAccess(final String password) throws DNotesRuntimeException {
231         final Object[] args = { password};
232         return createSession("getSessionWithFullAccess", args, PARAMS_STRING);
233     }
234 
235     /***
236      * @see de.bea.domingo.DNotesFactory#gc()
237      * @deprecated only use this method for testing
238      */
239     public void gc() {
240         factory.gc();
241     }
242 
243     /***
244      * Creates a new session with the given method and arguments.
245      *
246      * @param methodName name of method to call on the factory
247      * @param args array of arguments
248      * @param types array of types
249      * @return a session proxy
250      */
251     private DSession createSession(final String methodName, final Object[] args, final Class[] types) {
252         initThreadPool();
253         final Object result;
254         try {
255             result = invoke(factory, DNotesFactory.class.getMethod(methodName, types), args);
256         } catch (Throwable t) {
257             if (t instanceof NotesServiceRuntimeException) {
258                 throw (NotesServiceRuntimeException) t;
259             } else {
260                 throw new NotesServiceRuntimeException(t);
261             }
262         }
263         return (DSession) NotesInvocationHandler.getNotesProxy(PARAMS_SESSION, result);
264     }
265 
266     /***
267      * In this multi-threaded implementation the single-threaded dispose method
268      * is invoked via a dynamic proxy into the thread pool.
269      *
270      * @param force indicates if disposal should happen even if still any string
271      *            or soft reference exists. if <code>false</code>, only weak
272      *            references must remain.
273      * @throws DNotesRuntimeException if an error occurs during disposal or if
274      *             not all objects can be disposed
275      *
276      * @see de.bea.domingo.DNotesFactory#dispose()
277      * @see de.bea.domingo.DNotesFactory#disposeInternal(boolean)
278      * @deprecated use {@link #disposeInstance(boolean)} instead
279      */
280     public void disposeInternal(final boolean force) throws DNotesRuntimeException {
281     }
282 
283     /***
284      * {@inheritDoc}
285      * @see de.bea.domingo.DNotesFactory#disposeInstance(boolean)
286      */
287     public void disposeInstance(final boolean force) throws DNotesRuntimeException {
288         try {
289             final Object[] args = force ? ARGS_TRUE : ARGS_FALSE;
290             invoke(factory, DNotesFactory.class.getMethod("disposeInstance", PARAMS_BOOLEAN), args);
291         } catch (NoSuchMethodException e) {
292             throw new NotesServiceRuntimeException(e.getClass().getName() + ": " + "dispose", e);
293         } catch (Throwable e) {
294             throw new NotesServiceRuntimeException(e.getMessage(), e);
295         }
296         threadPool.stop();
297         factory = null;
298     }
299 
300     /***
301      * {@inheritDoc}
302      * @see de.bea.domingo.DNotesFactory#disposeInstance()
303      */
304     public void disposeInstance() throws DNotesRuntimeException {
305         disposeInstance(false);
306     }
307 
308     /***
309      * Initializes a notes thread pool to process Notes local calls.
310      */
311     private void initThreadPool() {
312         if (threadPool != null) {
313             return;
314         }
315         final int threadPoolSize = getIntProperty("de.bea.domingo.threadpool.size", DEFAULT_THREADPOOL_SIZE);
316         threadFactory = new NotesThreadFactory();
317         try {
318             threadPool = new SimpleThreadPool(getMonitor(), threadFactory, threadPoolSize);
319         } catch (ThreadPoolException e) {
320             throwWrappedException(e);
321         }
322         final Throwable t = threadFactory.getFirstThrowable();
323         if (t != null) {
324             throwWrappedException(t);
325         }
326     }
327 
328     private void throwWrappedException(final Throwable tt) {
329         Throwable t = tt;
330         if (t instanceof ThreadPoolException) {
331             t = ((ThreadPoolException) tt).getCause();
332         }
333         if (t instanceof NoClassDefFoundError) {
334             if (t.getMessage().indexOf("lotus/domino") >= 0) {
335                 throw new NotesServiceRuntimeException(RESOURCES.getString("notes.jar.missing"), t);
336             }
337         } else if (t instanceof UnsatisfiedLinkError) {
338             if (t.getMessage().indexOf("java.library.path") >= 0) {
339                 throw new NotesServiceRuntimeException(RESOURCES.getString("notes.installation.not.found"), t);
340             } else if (t.getMessage().indexOf("NGetWrapper") >= 0 || t.getMessage().indexOf("NCreateSession") >= 0) {
341                 throw new NotesServiceRuntimeException(RESOURCES.getString("notes.installation.not.found"), t);
342             }
343         }
344         throw new NotesServiceRuntimeException(t.getMessage(), t);
345     }
346 
347     /***
348      * Invokes a method within a Thread from the thread pool.
349      *
350      * @param object the object to invoke the method on
351      * @param method the method to invoke
352      * @param args the arguments for the method
353      * @return result object
354      * @throws Throwable if the method cannot be invoked
355      */
356     Object invoke(final Object object, final Method method, final Object[] args)
357             throws Throwable {
358         final InvocationTask task = new InvocationTask(object, method, args);
359         threadPool.invokeLater(task);
360         while (!task.isCompleted()) {
361             try {
362                 synchronized (task) {
363                     // We need to check for completeness here inside the
364                     // synchronized block again, because on some Operating
365                     // systems the task might be completed while waiting for
366                     // the monitor for synchronization. In this case we would
367                     // every time wait at least once the whole timeout if we
368                     // don't check for completeness again.
369                     if (!task.isCompleted()) {
370                         task.wait(DEFAULT_TIMEOUT);
371                     }
372                 }
373             } catch (InterruptedException e) {
374                 continue;
375             }
376         }
377         final Throwable t = task.getThrowable();
378         if (t != null) {
379             if (t instanceof NotesServiceRuntimeException) {
380                 throw t;
381             } else if (t instanceof RuntimeException) {
382                 throw new NotesServiceRuntimeException(t.getMessage(), t);
383             } else if (t instanceof NotesProxyException) {
384                 throw new NotesServiceException(t.getMessage(), t);
385             } else {
386                 throw new NotesServiceException("NotesServiceFactory.invoke() " + t.getMessage(), t);
387             }
388         }
389         return task.getResult();
390     }
391 
392     /***
393      * @see de.bea.domingo.DNotesFactory#sinitThread()
394      */
395     public void sinitThread() {
396         factory.sinitThread();
397     }
398 
399     /***
400      * @see de.bea.domingo.DNotesFactory#stermThread()
401      */
402     public void stermThread() {
403         factory.stermThread();
404     }
405 
406     /***
407      * {@inheritDoc}
408      *
409      * @see de.bea.domingo.DNotesFactory#getMonitor()
410      */
411     public DNotesMonitor getMonitor() {
412         return monitor;
413     }
414 
415     /***
416      * {@inheritDoc}
417      *
418      * @see de.bea.domingo.DNotesFactory#setMonitor(de.bea.domingo.DNotesMonitor)
419      */
420     public void setMonitor(final DNotesMonitor theMonitor) {
421         monitor = theMonitor;
422         factory.setMonitor(monitor);
423     }
424 
425     // //////////////////////////////////////////////
426     // inner classes
427     // //////////////////////////////////////////////
428 
429     /***
430      * Thread factory for notes enabled threads.
431      */
432     private final class NotesThreadFactory implements ThreadFactory {
433 
434         /*** For auto-numbering anonymous threads. */
435         private int threadInitNumber = 0;
436 
437         /*** The first throwable that occurs in a thread. */
438         private Throwable firstThrowable = null;
439 
440         /*** The last throwable that occurs in a thread. */
441         private Throwable lastThrowable;
442 
443         /***
444          * Private constructor.
445          */
446         private NotesThreadFactory() {
447         }
448 
449         /***
450          * @see de.bea.domingo.threadpool.ThreadFactory#createThread(java.lang.Runnable)
451          */
452         public Thread createThread(final Runnable target) {
453             DNotesThread domingoThread = new DNotesThread(target, "Domingo Thread " + nextThreadNum());
454             domingoThread.setMonitor(monitor);
455             return domingoThread;
456         }
457 
458         /***
459          * @see de.bea.domingo.threadpool.ThreadFactory#initThread()
460          */
461         public void initThread() {
462             sinitThread();
463         }
464 
465         /***
466          * @see de.bea.domingo.threadpool.ThreadFactory#termThread()
467          */
468         public void termThread() {
469             stermThread();
470         }
471 
472         /***
473          * Returns the next free number for a thread.
474          *
475          * @return next free number for a thread
476          */
477         private synchronized int nextThreadNum() {
478             return threadInitNumber++;
479         }
480 
481         /***
482          * Remember the first throwable that occurs, ignore further throwables.
483          * This should be enough for further processing, because the first
484          * throwable mostly is the main cause, and always the first thing to
485          * analyze.
486          *
487          * @see de.bea.domingo.threadpool.ThreadFactory#handleThrowable(java.lang.Throwable)
488          */
489         public void handleThrowable(final Throwable t) {
490             if (firstThrowable == null) {
491                 firstThrowable = t;
492             }
493             lastThrowable = t;
494         }
495 
496         /***
497          * Returns the first throwable that has occurred so far.
498          *
499          * @return first throwable that has occurred
500          */
501         protected Throwable getFirstThrowable() {
502             return firstThrowable;
503         }
504 
505         /***
506          * Returns the last throwable that has occurred so far.
507          *
508          * @return last throwable that has occurred
509          */
510         protected Throwable getLastThrowable() {
511             return lastThrowable;
512         }
513     }
514 }