001: /*
002: * Copyright 2004-2005 OpenSymphony
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy
006: * of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations
014: * under the License.
015: *
016: */
017:
018: /*
019: * Previously Copyright (c) 2001-2004 James House
020: */
021: package org.quartz.jobs.ee.ejb;
022:
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.Method;
025: import java.rmi.RemoteException;
026: import java.util.Hashtable;
027:
028: import javax.ejb.EJBHome;
029: import javax.ejb.EJBMetaData;
030: import javax.ejb.EJBObject;
031: import javax.naming.Context;
032: import javax.naming.InitialContext;
033: import javax.naming.NamingException;
034: import javax.rmi.PortableRemoteObject;
035:
036: import org.quartz.Job;
037: import org.quartz.JobDataMap;
038: import org.quartz.JobDetail;
039: import org.quartz.JobExecutionContext;
040: import org.quartz.JobExecutionException;
041:
042: /**
043: * <p>
044: * A <code>Job</code> that invokes a method on an EJB.
045: * </p>
046: *
047: * <p>
048: * Expects the properties corresponding to the following keys to be in the
049: * <code>JobDataMap</code> when it executes:
050: * <ul>
051: * <li><code>EJB_JNDI_NAME_KEY</code>- the JNDI name (location) of the
052: * EJB's home interface.</li>
053: * <li><code>EJB_METHOD_KEY</code>- the name of the method to invoke on the
054: * EJB.</li>
055: * <li><code>EJB_ARGS_KEY</code>- an Object[] of the args to pass to the
056: * method (optional, if left out, there are no arguments).</li>
057: * <li><code>EJB_ARG_TYPES_KEY</code>- an Class[] of the types of the args to
058: * pass to the method (optional, if left out, the types will be derived by
059: * calling getClass() on each of the arguments).</li>
060: * </ul>
061: * <br/>
062: * The following keys can also be used at need:
063: * <ul>
064: * <li><code>INITIAL_CONTEXT_FACTORY</code> - the context factory used to
065: * build the context.</li>
066: * <li><code>PROVIDER_URL</code> - the name of the environment property
067: * for specifying configuration information for the service provider to use.
068: * </li>
069: * </ul>
070: * </p>
071: *
072: * <p>
073: * The result of the EJB method invocation will be available to
074: * <code>Job/TriggerListener</code>s via
075: * <code>{@link org.quartz.JobExecutionContext#getResult()}</code>.
076: * </p>
077: *
078: * @author Andrew Collins
079: * @author James House
080: * @author Joel Shellman
081: * @author <a href="mailto:bonhamcm@thirdeyeconsulting.com">Chris Bonham</a>
082: */
083: public class EJBInvokerJob implements Job {
084:
085: /*
086: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
087: *
088: * Constants.
089: *
090: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
091: */
092:
093: public static final String EJB_JNDI_NAME_KEY = "ejb";
094:
095: public static final String EJB_METHOD_KEY = "method";
096:
097: public static final String EJB_ARG_TYPES_KEY = "argTypes";
098:
099: public static final String EJB_ARGS_KEY = "args";
100:
101: public static final String INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial";
102:
103: public static final String PROVIDER_URL = "java.naming.provider.url";
104:
105: public static final String PRINCIPAL = "java.naming.security.principal";
106:
107: public static final String CREDENTIALS = "java.naming.security.credentials";
108:
109: /*
110: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
111: *
112: * Constructors.
113: *
114: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
115: */
116:
117: public EJBInvokerJob() {
118: // nothing
119: }
120:
121: /*
122: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123: *
124: * Interface.
125: *
126: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
127: */
128:
129: public void execute(JobExecutionContext context)
130: throws JobExecutionException {
131: JobDetail detail = context.getJobDetail();
132:
133: JobDataMap dataMap = context.getMergedJobDataMap();
134:
135: String ejb = dataMap.getString(EJB_JNDI_NAME_KEY);
136: String method = dataMap.getString(EJB_METHOD_KEY);
137: Object[] arguments = (Object[]) dataMap.get(EJB_ARGS_KEY);
138: if (arguments == null) {
139: arguments = new Object[0];
140: }
141:
142: if (ejb == null) {
143: // must specify remote home
144: throw new JobExecutionException();
145: }
146:
147: InitialContext jndiContext = null;
148:
149: // get initial context
150: try {
151: jndiContext = getInitialContext(dataMap);
152: } catch (NamingException ne) {
153: throw new JobExecutionException(ne);
154: }
155:
156: try {
157: Object value = null;
158:
159: // locate home interface
160: try {
161: value = jndiContext.lookup(ejb);
162: } catch (NamingException ne) {
163: throw new JobExecutionException(ne);
164: }
165:
166: // get home interface
167: EJBHome ejbHome = (EJBHome) PortableRemoteObject.narrow(
168: value, EJBHome.class);
169:
170: // get meta data
171: EJBMetaData metaData = null;
172:
173: try {
174: metaData = ejbHome.getEJBMetaData();
175: } catch (RemoteException re) {
176: throw new JobExecutionException(re);
177: }
178:
179: // get home interface class
180: Class homeClass = metaData.getHomeInterfaceClass();
181:
182: // get remote interface class
183: Class remoteClass = metaData.getRemoteInterfaceClass();
184:
185: // get home interface
186: ejbHome = (EJBHome) PortableRemoteObject.narrow(ejbHome,
187: homeClass);
188:
189: Method methodCreate = null;
190:
191: try {
192: // create method 'create()' on home interface
193: methodCreate = homeClass.getMethod("create",
194: ((Class[]) null));
195: } catch (NoSuchMethodException nsme) {
196: throw new JobExecutionException(nsme);
197: }
198:
199: // create remote object
200: EJBObject remoteObj = null;
201:
202: try {
203: // invoke 'create()' method on home interface
204: remoteObj = (EJBObject) methodCreate.invoke(ejbHome,
205: ((Object[]) null));
206: } catch (IllegalAccessException iae) {
207: throw new JobExecutionException(iae);
208: } catch (InvocationTargetException ite) {
209: throw new JobExecutionException(ite);
210: }
211:
212: // execute user-specified method on remote object
213: Method methodExecute = null;
214:
215: try {
216: // create method signature
217:
218: Class[] argTypes = (Class[]) dataMap
219: .get(EJB_ARG_TYPES_KEY);
220: if (argTypes == null) {
221: argTypes = new Class[arguments.length];
222: for (int i = 0; i < arguments.length; i++) {
223: argTypes[i] = arguments[i].getClass();
224: }
225: }
226:
227: // get method on remote object
228: methodExecute = remoteClass.getMethod(method, argTypes);
229: } catch (NoSuchMethodException nsme) {
230: throw new JobExecutionException(nsme);
231: }
232:
233: try {
234: // invoke user-specified method on remote object
235: Object returnObj = methodExecute.invoke(remoteObj,
236: arguments);
237:
238: // Return any result in the JobExecutionContext so it will be
239: // available to Job/TriggerListeners
240: context.setResult(returnObj);
241: } catch (IllegalAccessException iae) {
242: throw new JobExecutionException(iae);
243: } catch (InvocationTargetException ite) {
244: throw new JobExecutionException(ite);
245: }
246: } finally {
247: // Don't close jndiContext until after method execution because
248: // WebLogic requires context to be open to keep the user credentials
249: // available. See JIRA Issue: QUARTZ-401
250: if (jndiContext != null) {
251: try {
252: jndiContext.close();
253: } catch (NamingException e) {
254: // Ignore any errors closing the initial context
255: }
256: }
257: }
258: }
259:
260: private InitialContext getInitialContext(JobDataMap jobDataMap)
261: throws NamingException {
262: Hashtable params = new Hashtable(2);
263:
264: String initialContextFactory = jobDataMap
265: .getString(INITIAL_CONTEXT_FACTORY);
266: if (initialContextFactory != null) {
267: params.put(Context.INITIAL_CONTEXT_FACTORY,
268: initialContextFactory);
269: }
270:
271: String providerUrl = jobDataMap.getString(PROVIDER_URL);
272: if (providerUrl != null) {
273: params.put(Context.PROVIDER_URL, providerUrl);
274: }
275:
276: String principal = jobDataMap.getString(PRINCIPAL);
277: if (principal != null) {
278: params.put(Context.SECURITY_PRINCIPAL, principal);
279: }
280:
281: String credentials = jobDataMap.getString(CREDENTIALS);
282: if (credentials != null) {
283: params.put(Context.SECURITY_CREDENTIALS, credentials);
284: }
285:
286: return (params.size() == 0) ? new InitialContext()
287: : new InitialContext(params);
288: }
289: }
|