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 }