1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
103
104
105 /*** Reference to associated ThreadPool. */
106 private ThreadPool threadPool = null;
107
108
109
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
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
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
364
365
366
367
368
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
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 }