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.Constructor;
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    import java.security.PrivilegedAction;
023    
024    import javax.security.auth.Subject;
025    import javax.security.auth.callback.CallbackHandler;
026    import javax.security.auth.login.LoginContext;
027    import javax.security.auth.login.LoginException;
028    
029    import org.apache.geronimo.gbean.AbstractName;
030    import org.apache.geronimo.gbean.GBeanInfo;
031    import org.apache.geronimo.gbean.GBeanInfoBuilder;
032    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
033    import org.apache.geronimo.kernel.Kernel;
034    import org.apache.geronimo.security.ContextManager;
035    import org.apache.geronimo.security.Callers;
036    import org.apache.geronimo.security.deploy.DefaultPrincipal;
037    import org.apache.geronimo.security.util.ConfigurationUtil;
038    
039    /**
040     * @version $Rev: 487175 $ $Date: 2006-12-14 03:10:31 -0800 (Thu, 14 Dec 2006) $
041     */
042    public final class AppClientContainer {
043        private static final Class[] MAIN_ARGS = {String[].class};
044    
045        private final String mainClassName;
046        private final AppClientPlugin jndiContext;
047        private final AbstractName appClientModuleName;
048        private final String realmName;
049        private final Class callbackHandlerClass;
050        private final Subject defaultSubject;
051        private final Method mainMethod;
052        private final ClassLoader classLoader;
053        private final Kernel kernel;
054    
055        public AppClientContainer(String mainClassName,
056                AbstractName appClientModuleName,
057                String realmName,
058                String callbackHandlerClassName,
059                DefaultPrincipal defaultPrincipal,
060                AppClientPlugin jndiContext,
061                ClassLoader classLoader,
062                Kernel kernel
063        ) throws Exception {
064            this.mainClassName = mainClassName;
065            this.appClientModuleName = appClientModuleName;
066            if ((realmName == null) != (callbackHandlerClassName == null)) {
067                throw new IllegalArgumentException("You must supply both realmName and callbackHandlerClass or neither");
068            }
069            this.realmName = realmName;
070            if (callbackHandlerClassName != null) {
071                try {
072                    this.callbackHandlerClass = classLoader.loadClass(callbackHandlerClassName);
073                } catch (ClassNotFoundException e) {
074                    throw new AppClientInitializationException("Could not load callbackHandlerClass", e);
075                }
076            } else {
077                callbackHandlerClass = null;
078            }
079            if (defaultPrincipal != null) {
080                defaultSubject = ConfigurationUtil.generateDefaultSubject(defaultPrincipal, classLoader);
081            } else {
082                defaultSubject = null;
083            }
084            this.classLoader = classLoader;
085            this.kernel = kernel;
086            this.jndiContext = jndiContext;
087    
088            try {
089                Class mainClass = classLoader.loadClass(mainClassName);
090                mainMethod = mainClass.getMethod("main", MAIN_ARGS);
091            } catch (ClassNotFoundException e) {
092                throw new AppClientInitializationException("Unable to load Main-Class " + mainClassName, e);
093            } catch (NoSuchMethodException e) {
094                throw new AppClientInitializationException("Main-Class " + mainClassName + " does not have a main method", e);
095            }
096        }
097    
098        public AbstractName getAppClientModuleName() {
099            return appClientModuleName;
100        }
101    
102        public String getMainClassName() {
103            return mainClassName;
104        }
105    
106        public void main(final String[] args) throws Exception {
107            //TODO reorganize this so it makes more sense.  maybe use an interceptor stack.
108            //TODO track resource ref shared and app managed security
109            Thread thread = Thread.currentThread();
110    
111            ClassLoader oldClassLoader = thread.getContextClassLoader();
112            Callers oldCallers = ContextManager.getCallers();
113            Subject clientSubject = defaultSubject;
114            LoginContext loginContext = null;
115            try {
116                thread.setContextClassLoader(classLoader);
117                if (callbackHandlerClass != null) {
118                    //look for a constructor taking the args
119                    CallbackHandler callbackHandler;
120                    try {
121                        Constructor cArgs = callbackHandlerClass.getConstructor(new Class[] {String[].class});
122                        callbackHandler = (CallbackHandler) cArgs.newInstance(new Object[] {args});
123                    } catch (NoSuchMethodException e) {
124                        callbackHandler = (CallbackHandler) callbackHandlerClass.newInstance();
125                    }
126                    loginContext = new LoginContext(realmName, callbackHandler);
127                    try {
128                        loginContext.login();
129                    } catch (LoginException e) {
130                        loginContext = null;
131                        throw e;
132                    }
133                    clientSubject = loginContext.getSubject();
134                }
135                ContextManager.setCallers(clientSubject, clientSubject);
136                jndiContext.startClient(appClientModuleName, kernel, classLoader);
137                if (clientSubject == null) {
138                    mainMethod.invoke(null, new Object[]{args});
139                } else {
140                    Subject.doAs(clientSubject, new PrivilegedAction() {
141                        public Object run() {
142                            try {
143                                mainMethod.invoke(null, new Object[]{args});
144                            } catch (IllegalAccessException e) {
145                                throw new RuntimeException(e);
146                            } catch (InvocationTargetException e) {
147                                throw new RuntimeException(e);
148                            }
149                            return null;
150                        }
151                    });
152                }
153            } catch (InvocationTargetException e) {
154                Throwable cause = e.getCause();
155                if (cause instanceof Exception) {
156                    throw (Exception) cause;
157                } else if (cause instanceof Error) {
158                    throw (Error) cause;
159                }
160                throw new Error(e);
161            } finally {
162                if (loginContext != null) {
163                    loginContext.logout();
164                }
165                jndiContext.stopClient(appClientModuleName);
166    
167                thread.setContextClassLoader(oldClassLoader);
168                ContextManager.popCallers(oldCallers);
169            }
170        }
171    
172        public static final GBeanInfo GBEAN_INFO;
173    
174        static {
175            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(AppClientContainer.class, NameFactory.APP_CLIENT);
176    
177            infoFactory.addOperation("main", new Class[]{String[].class});
178    
179            infoFactory.addAttribute("mainClassName", String.class, true);
180            infoFactory.addAttribute("appClientModuleName", AbstractName.class, true);
181            infoFactory.addAttribute("realmName", String.class, true);
182            infoFactory.addAttribute("callbackHandlerClassName", String.class, true);
183            infoFactory.addAttribute("defaultPrincipal", DefaultPrincipal.class, true);
184    
185            infoFactory.addReference("JNDIContext", AppClientPlugin.class, NameFactory.GERONIMO_SERVICE);
186    
187            infoFactory.addAttribute("classLoader", ClassLoader.class, false);
188            infoFactory.addAttribute("kernel", Kernel.class, false);
189    
190    
191            infoFactory.setConstructor(new String[]{"mainClassName",
192                                                    "appClientModuleName",
193                                                    "realmName",
194                                                    "callbackHandlerClassName",
195                                                    "defaultPrincipal",
196                                                    "JNDIContext",
197                                                    "classLoader",
198                                                    "kernel"
199            });
200    
201            GBEAN_INFO = infoFactory.getBeanInfo();
202        }
203    
204        public static GBeanInfo getGBeanInfo() {
205            return GBEAN_INFO;
206        }
207    }