001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.geronimo.client;
018
019 import java.lang.reflect.InvocationTargetException;
020 import java.lang.reflect.Method;
021 import java.security.PrivilegedAction;
022 import java.util.ArrayList;
023 import java.util.List;
024
025 import javax.naming.Context;
026 import javax.naming.NamingException;
027 import javax.security.auth.Subject;
028 import javax.security.auth.callback.CallbackHandler;
029 import javax.security.auth.login.LoginContext;
030 import javax.security.auth.login.LoginException;
031
032 import org.apache.geronimo.gbean.AbstractName;
033 import org.apache.geronimo.gbean.GBeanInfo;
034 import org.apache.geronimo.gbean.GBeanInfoBuilder;
035 import org.apache.geronimo.gbean.GBeanLifecycle;
036 import org.apache.geronimo.j2ee.annotation.Holder;
037 import org.apache.geronimo.j2ee.annotation.Injection;
038 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
039 import org.apache.geronimo.kernel.Kernel;
040 import org.apache.geronimo.security.Callers;
041 import org.apache.geronimo.security.ContextManager;
042 import org.apache.geronimo.security.credentialstore.CredentialStore;
043 import org.apache.geronimo.security.deploy.SubjectInfo;
044 import org.apache.xbean.recipe.ObjectRecipe;
045 import org.apache.xbean.recipe.Option;
046 import org.apache.xbean.recipe.StaticRecipe;
047
048 /**
049 * @version $Rev: 565912 $ $Date: 2007-08-14 17:03:11 -0400 (Tue, 14 Aug 2007) $
050 */
051 public final class AppClientContainer implements GBeanLifecycle {
052 private static final Class[] MAIN_ARGS = {String[].class};
053
054 private LoginContext loginContext;
055
056 private final String mainClassName;
057 private final AppClientPlugin jndiContext;
058 private final AbstractName appClientModuleName;
059 private final String realmName;
060 private final String callbackHandlerClass;
061 private final Subject defaultSubject;
062 private final Method mainMethod;
063 private final ClassLoader classLoader;
064 private final Kernel kernel;
065 private final Holder holder;
066 private CallbackHandler callbackHandler;
067
068 public AppClientContainer(String mainClassName,
069 AbstractName appClientModuleName,
070 String realmName,
071 String callbackHandlerClassName,
072 SubjectInfo defaultSubject,
073 Holder holder,
074 AppClientPlugin jndiContext,
075 CredentialStore credentialStore,
076 ClassLoader classLoader,
077 Kernel kernel
078 ) throws Exception {
079 // set the geronimo identity resolver hook for openejb
080 System.setProperty("openejb.client.identityResolver", "geronimo");
081
082 this.mainClassName = mainClassName;
083 this.appClientModuleName = appClientModuleName;
084 if ((realmName == null) != (callbackHandlerClassName == null)) {
085 throw new IllegalArgumentException("You must supply both realmName and callbackHandlerClass or neither");
086 }
087 this.realmName = realmName;
088 this.callbackHandlerClass = callbackHandlerClassName;
089
090 if (defaultSubject != null) {
091 this.defaultSubject = credentialStore.getSubject(defaultSubject.getRealm(), defaultSubject.getId());
092 } else {
093 this.defaultSubject = null;
094 }
095 this.holder = holder == null ? Holder.EMPTY : holder;
096 this.classLoader = classLoader;
097 this.kernel = kernel;
098 this.jndiContext = jndiContext;
099
100 try {
101 Class mainClass = classLoader.loadClass(mainClassName);
102 mainMethod = mainClass.getMethod("main", MAIN_ARGS);
103 } catch (ClassNotFoundException e) {
104 throw new AppClientInitializationException("Unable to load Main-Class " + mainClassName, e);
105 } catch (NoSuchMethodException e) {
106 throw new AppClientInitializationException("Main-Class " + mainClassName + " does not have a main method", e);
107 }
108 }
109
110 public AbstractName getAppClientModuleName() {
111 return appClientModuleName;
112 }
113
114 public String getMainClassName() {
115 return mainClassName;
116 }
117
118 public void main(final String[] args) throws Exception {
119 //TODO reorganize this so it makes more sense. maybe use an interceptor stack.
120 //TODO track resource ref shared and app managed security
121 Thread thread = Thread.currentThread();
122
123 ClassLoader oldClassLoader = thread.getContextClassLoader();
124 Callers oldCallers = ContextManager.getCallers();
125 Subject clientSubject = defaultSubject;
126 try {
127 thread.setContextClassLoader(classLoader);
128 jndiContext.startClient(appClientModuleName, kernel, classLoader);
129 Context componentContext = jndiContext.getJndiContext();
130
131 if (callbackHandlerClass != null) {
132 callbackHandler = (CallbackHandler) holder.newInstance(callbackHandlerClass, classLoader, componentContext);
133 loginContext = ContextManager.login(realmName, callbackHandler);
134 clientSubject = loginContext.getSubject();
135 }
136 ContextManager.setCallers(clientSubject, clientSubject);
137 ObjectRecipe objectRecipe = new ObjectRecipe(mainClassName);
138 objectRecipe.allow(Option.FIELD_INJECTION);
139 objectRecipe.allow(Option.PRIVATE_PROPERTIES);
140 objectRecipe.allow(Option.STATIC_PROPERTIES);
141 Class mainClass = classLoader.loadClass(mainClassName);
142 List<Injection> injections = new ArrayList<Injection>();
143 while (mainClass != null && mainClass != Object.class) {
144 List<Injection> perClass = holder.getInjections(mainClass.getName());
145 if (perClass != null) {
146 injections.addAll(perClass);
147 }
148 mainClass = mainClass.getSuperclass();
149 }
150 if (injections != null) {
151 List<NamingException> problems = new ArrayList<NamingException>();
152 for (Injection injection : injections) {
153 try {
154 String jndiName = injection.getJndiName();
155 //our componentContext is attached to jndi at "java:comp" so we remove that when looking stuff up in it
156 Object object = componentContext.lookup("env/" + jndiName);
157 if (object instanceof String) {
158 String string = (String) object;
159 // Pass it in raw so it could be potentially converted to
160 // another data type by an xbean-reflect property editor
161 objectRecipe.setProperty(injection.getTargetName(), string);
162 } else {
163 objectRecipe.setProperty(injection.getTargetName(), new StaticRecipe(object));
164 }
165 } catch (NamingException e) {
166 problems.add(e);
167 }
168 }
169 if (!problems.isEmpty()) {
170 throw new Exception("Some objects to be injected were not found in jndi: " + problems);
171 }
172 }
173 Class clazz = objectRecipe.setStaticProperties(classLoader);
174 if (holder.getPostConstruct() != null) {
175 Holder.apply(null, clazz, holder.getPostConstruct());
176 }
177
178 if (clientSubject == null) {
179 mainMethod.invoke(null, new Object[]{args});
180 } else {
181 Subject.doAs(clientSubject, new PrivilegedAction() {
182 public Object run() {
183 try {
184 mainMethod.invoke(null, new Object[]{args});
185 } catch (IllegalAccessException e) {
186 throw new RuntimeException(e);
187 } catch (InvocationTargetException e) {
188 throw new RuntimeException(e);
189 }
190 return null;
191 }
192 });
193 }
194 } catch (InvocationTargetException e) {
195 Throwable cause = e.getCause();
196 if (cause instanceof Exception) {
197 throw (Exception) cause;
198 } else if (cause instanceof Error) {
199 throw (Error) cause;
200 }
201 throw new Error(e);
202 } finally {
203 //How can this work??
204 thread.setContextClassLoader(oldClassLoader);
205 ContextManager.popCallers(oldCallers);
206 }
207 }
208
209 public void doStart() throws Exception {
210 }
211
212 public void doStop() throws Exception {
213 if (callbackHandler != null) {
214 holder.destroyInstance(callbackHandler);
215 }
216 if (loginContext != null) {
217 ContextManager.logout(loginContext);
218 }
219 jndiContext.stopClient(appClientModuleName);
220 }
221
222 public void doFail() {
223 try {
224 doStop();
225 } catch (Exception e) {
226 //ignore
227 }
228 }
229
230
231 public static final GBeanInfo GBEAN_INFO;
232
233 static {
234 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(AppClientContainer.class, NameFactory.APP_CLIENT);
235
236
237 infoFactory.addAttribute("mainClassName", String.class, true);
238 infoFactory.addAttribute("appClientModuleName", AbstractName.class, true);
239 infoFactory.addAttribute("realmName", String.class, true);
240 infoFactory.addAttribute("callbackHandlerClassName", String.class, true);
241 infoFactory.addAttribute("defaultSubject", SubjectInfo.class, true);
242 infoFactory.addAttribute("holder", Holder.class, true);
243
244 infoFactory.addReference("JNDIContext", AppClientPlugin.class, NameFactory.GERONIMO_SERVICE);
245 infoFactory.addReference("CredentialStore", CredentialStore.class, NameFactory.GERONIMO_SERVICE);
246
247 infoFactory.addAttribute("classLoader", ClassLoader.class, false);
248 infoFactory.addAttribute("kernel", Kernel.class, false);
249
250
251 infoFactory.setConstructor(new String[]{"mainClassName",
252 "appClientModuleName",
253 "realmName",
254 "callbackHandlerClassName",
255 "defaultSubject",
256 "holder",
257 "JNDIContext",
258 "CredentialStore",
259 "classLoader",
260 "kernel"
261 });
262
263 GBEAN_INFO = infoFactory.getBeanInfo();
264 }
265
266 public static GBeanInfo getGBeanInfo() {
267 return GBEAN_INFO;
268 }
269
270 }