001    /**
002     *
003     *  Licensed to the Apache Software Foundation (ASF) under one or more
004     *  contributor license agreements.  See the NOTICE file distributed with
005     *  this work for additional information regarding copyright ownership.
006     *  The ASF licenses this file to You under the Apache License, Version 2.0
007     *  (the "License"); you may not use this file except in compliance with
008     *  the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    package org.apache.geronimo.client;
019    
020    import java.lang.reflect.Constructor;
021    import java.lang.reflect.InvocationTargetException;
022    import java.lang.reflect.Method;
023    import java.security.PrivilegedAction;
024    
025    import javax.security.auth.Subject;
026    import javax.security.auth.callback.CallbackHandler;
027    import javax.security.auth.login.LoginContext;
028    import javax.security.auth.login.LoginException;
029    
030    import org.apache.geronimo.gbean.AbstractName;
031    import org.apache.geronimo.gbean.GBeanInfo;
032    import org.apache.geronimo.gbean.GBeanInfoBuilder;
033    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
034    import org.apache.geronimo.kernel.Kernel;
035    import org.apache.geronimo.security.ContextManager;
036    import org.apache.geronimo.security.Callers;
037    import org.apache.geronimo.security.deploy.DefaultPrincipal;
038    import org.apache.geronimo.security.util.ConfigurationUtil;
039    
040    /**
041     * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
042     */
043    public final class AppClientContainer {
044        private static final Class[] MAIN_ARGS = {String[].class};
045    
046        private final String mainClassName;
047        private final AppClientPlugin jndiContext;
048        private final AbstractName appClientModuleName;
049        private final String realmName;
050        private final Class callbackHandlerClass;
051        private final Subject defaultSubject;
052        private final Method mainMethod;
053        private final ClassLoader classLoader;
054        private final Kernel kernel;
055    
056        public AppClientContainer(String mainClassName,
057                AbstractName appClientModuleName,
058                String realmName,
059                String callbackHandlerClassName,
060                DefaultPrincipal defaultPrincipal,
061                AppClientPlugin jndiContext,
062                ClassLoader classLoader,
063                Kernel kernel
064        ) throws Exception {
065            this.mainClassName = mainClassName;
066            this.appClientModuleName = appClientModuleName;
067            if ((realmName == null) != (callbackHandlerClassName == null)) {
068                throw new IllegalArgumentException("You must supply both realmName and callbackHandlerClass or neither");
069            }
070            this.realmName = realmName;
071            if (callbackHandlerClassName != null) {
072                try {
073                    this.callbackHandlerClass = classLoader.loadClass(callbackHandlerClassName);
074                } catch (ClassNotFoundException e) {
075                    throw new AppClientInitializationException("Could not load callbackHandlerClass", e);
076                }
077            } else {
078                callbackHandlerClass = null;
079            }
080            if (defaultPrincipal != null) {
081                defaultSubject = ConfigurationUtil.generateDefaultSubject(defaultPrincipal, classLoader);
082            } else {
083                defaultSubject = null;
084            }
085            this.classLoader = classLoader;
086            this.kernel = kernel;
087            this.jndiContext = jndiContext;
088    
089            try {
090                Class mainClass = classLoader.loadClass(mainClassName);
091                mainMethod = mainClass.getMethod("main", MAIN_ARGS);
092            } catch (ClassNotFoundException e) {
093                throw new AppClientInitializationException("Unable to load Main-Class " + mainClassName, e);
094            } catch (NoSuchMethodException e) {
095                throw new AppClientInitializationException("Main-Class " + mainClassName + " does not have a main method", e);
096            }
097        }
098    
099        public AbstractName getAppClientModuleName() {
100            return appClientModuleName;
101        }
102    
103        public String getMainClassName() {
104            return mainClassName;
105        }
106    
107        public void main(final String[] args) throws Exception {
108            //TODO reorganize this so it makes more sense.  maybe use an interceptor stack.
109            //TODO track resource ref shared and app managed security
110            Thread thread = Thread.currentThread();
111    
112            ClassLoader oldClassLoader = thread.getContextClassLoader();
113            Callers oldCallers = ContextManager.getCallers();
114            Subject clientSubject = defaultSubject;
115            LoginContext loginContext = null;
116            try {
117                thread.setContextClassLoader(classLoader);
118                if (callbackHandlerClass != null) {
119                    //look for a constructor taking the args
120                    CallbackHandler callbackHandler;
121                    try {
122                        Constructor cArgs = callbackHandlerClass.getConstructor(new Class[] {String[].class});
123                        callbackHandler = (CallbackHandler) cArgs.newInstance(new Object[] {args});
124                    } catch (NoSuchMethodException e) {
125                        callbackHandler = (CallbackHandler) callbackHandlerClass.newInstance();
126                    }
127                    loginContext = new LoginContext(realmName, callbackHandler);
128                    try {
129                        loginContext.login();
130                    } catch (LoginException e) {
131                        loginContext = null;
132                        throw e;
133                    }
134                    clientSubject = loginContext.getSubject();
135                }
136                ContextManager.setCallers(clientSubject, clientSubject);
137                jndiContext.startClient(appClientModuleName, kernel, classLoader);
138                if (clientSubject == null) {
139                    mainMethod.invoke(null, new Object[]{args});
140                } else {
141                    Subject.doAs(clientSubject, new PrivilegedAction() {
142                        public Object run() {
143                            try {
144                                mainMethod.invoke(null, new Object[]{args});
145                            } catch (IllegalAccessException e) {
146                                throw new RuntimeException(e);
147                            } catch (InvocationTargetException e) {
148                                throw new RuntimeException(e);
149                            }
150                            return null;
151                        }
152                    });
153                }
154            } catch (InvocationTargetException e) {
155                Throwable cause = e.getCause();
156                if (cause instanceof Exception) {
157                    throw (Exception) cause;
158                } else if (cause instanceof Error) {
159                    throw (Error) cause;
160                }
161                throw new Error(e);
162            } finally {
163                if (loginContext != null) {
164                    loginContext.logout();
165                }
166                jndiContext.stopClient(appClientModuleName);
167    
168                thread.setContextClassLoader(oldClassLoader);
169                ContextManager.popCallers(oldCallers);
170            }
171        }
172    
173        public static final GBeanInfo GBEAN_INFO;
174    
175        static {
176            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(AppClientContainer.class, NameFactory.APP_CLIENT);
177    
178            infoFactory.addOperation("main", new Class[]{String[].class});
179    
180            infoFactory.addAttribute("mainClassName", String.class, true);
181            infoFactory.addAttribute("appClientModuleName", AbstractName.class, true);
182            infoFactory.addAttribute("realmName", String.class, true);
183            infoFactory.addAttribute("callbackHandlerClassName", String.class, true);
184            infoFactory.addAttribute("defaultPrincipal", DefaultPrincipal.class, true);
185    
186            infoFactory.addReference("JNDIContext", AppClientPlugin.class, NameFactory.GERONIMO_SERVICE);
187    
188            infoFactory.addAttribute("classLoader", ClassLoader.class, false);
189            infoFactory.addAttribute("kernel", Kernel.class, false);
190    
191    
192            infoFactory.setConstructor(new String[]{"mainClassName",
193                                                    "appClientModuleName",
194                                                    "realmName",
195                                                    "callbackHandlerClassName",
196                                                    "defaultPrincipal",
197                                                    "JNDIContext",
198                                                    "classLoader",
199                                                    "kernel"
200            });
201    
202            GBEAN_INFO = infoFactory.getBeanInfo();
203        }
204    
205        public static GBeanInfo getGBeanInfo() {
206            return GBEAN_INFO;
207        }
208    }