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    }