Thanks!
On Thu, Aug 20, 2009 at 2:02 PM, Andy Tripp<[email protected]> wrote: > Jira issue created: > https://issues.apache.org/jira/browse/KI-82 > >> -----Original Message----- >> From: [email protected] [mailto:[email protected]] On >> Behalf Of Les Hazlewood >> Sent: Thursday, August 20, 2009 11:53 AM >> To: [email protected] >> Subject: Re: need more help with SSO >> >> Hi Andy, >> >> Can you please make note of this in a Jira issue? I'll fix it right >> away, but I'd like a record of this so when I modify the code, I can >> comment exactly why the change is required to ensure someone in the >> future doesn't accidentally revert the change. >> >> Thanks! >> >> Les >> >> On Thu, Aug 20, 2009 at 11:27 AM, Andy Tripp<[email protected]> >> wrote: >> > Les, >> > >> > I finally found the problem. This line... >> > securityManager.sessionDAO = $sessionDAO >> > ...was being processed BEFORE this line... >> > securityManager.sessionManager = $sessionManager >> > >> > In ReflectionBuilder.buildObjects(), instanceMap and propertyMap need to >> be LinkedHashMap type, not just HashMap. With HashMap, the properties in >> ShiroFilter are being processed in arbitrary order, rather than the order >> listed. That would explain why it works for you and not me - you got >> unlucky :) >> > >> > Andy >> > >> > >> >> -----Original Message----- >> >> From: Andy Tripp [mailto:[email protected]] >> >> Sent: Wednesday, August 19, 2009 4:02 PM >> >> To: [email protected] >> >> Subject: RE: need more help with SSO >> >> >> >> Les, >> >> Sorry, that last email was a mistake on my part. >> >> >> >> Les, >> >> >> >> What I'm seeing now is that the DefaultWebSecurityManager instance's >> >> SessionManager is always set to ServletContainerSessionManager, when it >> >> should be a DefaultWebSessionManager. I tried adding these to my >> config: >> >> >> >> sessionManager = >> org.apache.shiro.web.session.DefaultWebSessionManager >> >> securityManager.sessionManager = $sessionManager >> >> >> >> ...but still, the DefaultWebSecurityManager.sessionManager field is an >> >> instance of ServletContainerSessionManager. >> >> >> >> I'm stumped. I guess I'm not clear on what SecurityManager instance is >> >> being called by this config stuff. Perhaps I'm missing some sort of: >> >> something.securityManager = securityManager >> >> >> >> Andy >> >> p.s. here's my full [main] section of my filter: >> >> >> >> realmA = org.apache.shiro.realm.text.PropertiesRealm >> >> securityManager = org.apache.shiro.web.DefaultWebSecurityManager >> >> sessionManager = >> >> org.apache.shiro.web.session.DefaultWebSessionManager >> >> securityManager.sessionManager = $sessionManager >> >> >> >> securityManager.sessionMode = native >> >> >> >> cacheManager = org.apache.shiro.cache.DefaultCacheManager >> >> >> >> sessionDAO = org.apache.shiro.session.mgt.eis.MemorySessionDAO >> >> sessionDAO.cacheManager = $cacheManager >> >> securityManager.sessionDAO = $sessionDAO >> >> securityManager.cacheManager = $cacheManager >> >> >> >> securityManager.realm = $realmA >> >> >> >> > -----Original Message----- >> >> > From: [email protected] [mailto:[email protected]] >> On >> >> > Behalf Of Les Hazlewood >> >> > Sent: Wednesday, August 19, 2009 2:20 PM >> >> > To: [email protected] >> >> > Subject: Re: need more help with SSO >> >> > >> >> > Hrm - that would be very odd if the DefaultWebSecurityManager was not >> >> > the instance - that is what the ShiroFilter enables at startup by >> >> > default. Just in case, try this as your very first config line: >> >> > >> >> > securityManager = org.apache.shiro.web.DefaultWebSecurityManager >> >> > >> >> > What does your debugger say is the securityManager instance? >> >> > Something is very strange... >> >> > >> >> > Thanks for the extra info. Are there any JUnit tests you might be >> >> > able to send our way? >> >> > >> >> > - Les >> >> > >> >> > On Wed, Aug 19, 2009 at 1:56 PM, Andy Tripp<[email protected]> >> >> > wrote: >> >> > > Les, >> >> > > I put tracing code in DefaultWebSecurityManager.setSessionMode(), >> and >> >> it >> >> > appears that this method is not getting called. So the >> >> > ServletContainerSessionManager is not getting replace by a >> >> > DefaultWebSessionManager. So it appears that this line in the filter >> >> > config: >> >> > > >> >> > > securityManager.sessionMode = native >> >> > > >> >> > > is having no effect (note that it's securityManager, not >> >> sessionManager >> >> > as you suggest in the previous response). >> >> > > >> >> > > I'll keep trying to track it down further, any pointers would be >> >> > appreciated. I'm off to try to find the some SecurityManager >> instance, >> >> > which I suspect is something other than a DefaultWebSecurityManager, >> >> which >> >> > would mean that this config line is failing silently. >> >> > > >> >> > > Obviously, all this dependency injection via XML is driving me >> >> > completely crazy. I may be allergic to server-side Java :) >> >> > > >> >> > > Andy >> >> > > >> >> > >> -----Original Message----- >> >> > >> From: [email protected] >> [mailto:[email protected]] >> >> On >> >> > >> Behalf Of Les Hazlewood >> >> > >> Sent: Wednesday, August 19, 2009 12:46 PM >> >> > >> To: [email protected] >> >> > >> Subject: Re: need more help with SSO >> >> > >> >> >> > >> Hi Andy, >> >> > >> >> >> > >> A quick note about the message: that was a bug in the exception >> >> > >> message, but the code is working as expected: if the wrapped >> >> > >> SessionManager does not implement the SessionDAOAware interface, >> it >> >> > >> cannot be injected with a SessionDAO. I have since fixed the >> message >> >> > >> to be correct and committed this change, although the code logic >> has >> >> > >> not been changed. >> >> > >> >> >> > >> Also, make sure that you do this: >> >> > >> >> >> > >> sessionManager.sessionMode = native >> >> > >> >> >> > >> before you try to inject the SessionDAO. The above call will >> >> > >> automatically substitute the ServletContainerSessionManager for a >> >> > >> DefaultWebSessionManager implementation on the fly. This latter >> >> > >> implementation does in fact implement SessionDAOAware and should >> >> > >> readily accept SessionDAO instances that are passed through the >> >> > >> securityManager.setSessionDAO(...) call. >> >> > >> >> >> > >> In the meantime, I'll try to create a unit test with the >> ShiroFilter >> >> > >> to see I can accurately recreate your issue, but I've been >> strapped >> >> > >> for time lately - if you could create one (if possible) and post >> it >> >> to >> >> > >> a Jira issue, that would help a lot. >> >> > >> >> >> > >> Regards, >> >> > >> >> >> > >> Les >> >> > >> >> >> > >> On Tue, Aug 18, 2009 at 11:34 AM, Les >> >> Hazlewood<[email protected]> >> >> > >> wrote: >> >> > >> > Hi Andy, >> >> > >> > >> >> > >> > Thanks very much for sending this along - it is very helpful. >> I'll >> >> > be >> >> > >> > able to look into this a bit more later tonight. >> >> > >> > >> >> > >> > Regards, >> >> > >> > >> >> > >> > Les >> >> > >> > >> >> > >> > On Tue, Aug 18, 2009 at 11:21 AM, Andy >> >> Tripp<[email protected]> >> >> > >> wrote: >> >> > >> >> Les, >> >> > >> >> I tracked this problem down through a maze of try/catch blocks, >> I >> >> > see >> >> > >> this exception: >> >> > >> >> >> >> > >> >> javax.servlet.ServletException: Unable to load from text >> >> > configuration. >> >> > >> e2=org.apache.shiro.config.ConfigurationException: >> >> > >> org.apache.shiro.config.ConfigurationException: Unable to set >> >> property >> >> > >> [sessionDAO] with value [$sessionDAO]. If '$sessionDAO' is a >> >> reference >> >> > to >> >> > >> another (previously defined) object, please prefix it with '$' to >> >> > indicate >> >> > >> that the referenced object should be used as the actual value. >> For >> >> > >> example, $$sessionDAO >> >> > >> >> >> >> > >> >> ...which I tracked down to the >> ReflectionBuilder.applyProperty() >> >> > method >> >> > >> calling BeanUtils.setProperty() and catching an >> InvocationException. >> >> > The >> >> > >> cause of that exception is: >> >> > >> >> >> >> > >> >> java.lang.IllegalArgumentException: The underlying session >> manager >> >> > is >> >> > >> null or does not implement the >> >> > org.apache.shiro.session.mgt.eis.SessionDAO >> >> > >> >> interface, which is required if the underlying instance is to >> >> > receive >> >> > >> the sessionDAO argument. >> >> > >> >> >> >> > >> >> >> >> > >> >> ...which comes from SessionsSecurityManager.setSessionDAO(), >> which >> >> > >> checks >> >> > >> >> to see that the SessionDAO parameter implements >> SessionDAOAware. >> >> The >> >> > >> passed value is actually of class ServletContainerSessionManager, >> >> which >> >> > >> does NOT >> >> > >> >> implement SessionDAOAware. >> >> > >> >> >> >> > >> >> So I guess the mystery is why we're getting setSessionDAO() >> being >> >> > >> passed a ServletContainerSessionManager, when in fact we have this >> >> > config >> >> > >> line: >> >> > >> >> >> >> > >> >> sessionDAO = >> org.apache.shiro.session.mgt.eis.MemorySessionDAO >> >> > >> >> >> >> > >> >> Hope this helps, >> >> > >> >> Andy >> >> > >> >> >> >> > >> >> >> >> > >> >> >> >> > >> >> >> >> > >> >>> -----Original Message----- >> >> > >> >>> From: Andy Tripp [mailto:[email protected]] >> >> > >> >>> Sent: Tuesday, August 18, 2009 10:04 AM >> >> > >> >>> To: [email protected] >> >> > >> >>> Subject: RE: need more help with SSO >> >> > >> >>> >> >> > >> >>> Les, >> >> > >> >>> >> >> > >> >>> I tried what you have below and still get the same "Unable to >> >> load >> >> > >> from >> >> > >> >>> text configuration" error. I tried it with the latest Shiro. I >> >> > >> narrowed >> >> > >> >>> the problem down to this line: >> >> > >> >>> >> >> > >> >>> securityManager.sessionDAO = $sessionDAO >> >> > >> >>> >> >> > >> >>> I get no errors with that line commented out. >> >> > >> >>> >> >> > >> >>> Any ideas? If not, I could put some tracing in the >> >> > >> OncePerRequestFilter >> >> > >> >>> class to narrow the problem down further. >> >> > >> >>> >> >> > >> >>> Andy >> >> > >> >>> >> >> > >> >>> > -----Original Message----- >> >> > >> >>> > From: [email protected] >> >> > [mailto:[email protected]] >> >> > >> On >> >> > >> >>> > Behalf Of Les Hazlewood >> >> > >> >>> > Sent: Monday, August 17, 2009 5:11 PM >> >> > >> >>> > To: [email protected] >> >> > >> >>> > Subject: Re: need more help with SSO >> >> > >> >>> > >> >> > >> >>> > Hi Andy, >> >> > >> >>> > >> >> > >> >>> > I just verified that this simple test config works, although >> >> not >> >> > in >> >> > >> a >> >> > >> >>> > web environment: >> >> > >> >>> > >> >> > >> >>> > ---- >> >> > >> >>> > realmA = org.apache.shiro.realm.text.PropertiesRealm >> >> > >> >>> > >> >> > >> >>> > securityManager.sessionMode = native >> >> > >> >>> > >> >> > >> >>> > cacheManager = org.apache.shiro.cache.DefaultCacheManager >> >> > >> >>> > >> >> > >> >>> > sessionDAO = >> org.apache.shiro.session.mgt.eis.MemorySessionDAO >> >> > >> >>> > sessionDAO.cacheManager = $cacheManager >> >> > >> >>> > securityManager.sessionDAO = $sessionDAO >> >> > >> >>> > securityManager.cacheManager = $cacheManager >> >> > >> >>> > >> >> > >> >>> > securityManager.realm = $realmA >> >> > >> >>> > ---- >> >> > >> >>> > >> >> > >> >>> > Could you please try that out and see if it works in your >> web >> >> > >> >>> > environment? If so, can you try substituting the >> >> > >> DefaultCacheManager >> >> > >> >>> > implementation (and your realm implementation) with with >> your >> >> > >> >>> > implementations and see what happens? >> >> > >> >>> > >> >> > >> >>> > - Les >> >> > >> >>> > >> >> > >> >>> > On Mon, Aug 17, 2009 at 4:27 PM, Andy >> >> > Tripp<[email protected]> >> >> > >> >>> > wrote: >> >> > >> >>> > > Here's the complete tomcat log file: >> >> > >> >>> > > >> >> > >> >>> > > Aug 17, 2009 3:40:13 PM >> >> > org.apache.catalina.core.StandardContext >> >> > >> >>> > filterStart >> >> > >> >>> > > SEVERE: Exception starting filter ShiroFilter >> >> > >> >>> > > javax.servlet.ServletException: Unable to load from text >> >> > >> >>> configuration. >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.shiro.web.servlet.OncePerRequestFilter.init(OncePerRequestFilte >> >> > >> >>> > r.java:148) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilt >> >> > >> >>> > erConfig.java:221) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationF >> >> > >> >>> > ilterConfig.java:302) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterC >> >> > >> >>> > onfig.java:78) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.core.StandardContext.filterStart(StandardContext.java: >> >> > >> >>> > 3635) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.core.StandardContext.start(StandardContext.java:4222) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java >> >> > >> >>> > :760) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >> >> org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:740) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >> org.apache.catalina.core.StandardHost.addChild(StandardHost.java:544) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:927 >> >> > >> >>> > ) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:8 >> >> > >> >>> > 90) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >> >> org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:492) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > org.apache.catalina.startup.HostConfig.start(HostConfig.java:1150) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupp >> >> > >> >>> > ort.java:120) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >> org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1022) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > org.apache.catalina.core.StandardHost.start(StandardHost.java:736) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >> org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1014) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >> >> org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >> >> > >> org.apache.catalina.core.StandardService.start(StandardService.java:448) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >> >> org.apache.catalina.core.StandardServer.start(StandardServer.java:700) >> >> > >> >>> > > at >> >> > >> >>> org.apache.catalina.startup.Catalina.start(Catalina.java:552) >> >> > >> >>> > > at >> sun.reflect.NativeMethodAccessorImpl.invoke0(Native >> >> > >> Method) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: >> >> > >> >>> > 39) >> >> > >> >>> > > at >> >> > >> >>> > >> >> > >> >>> >> >> > >> >> >> > >> >> >> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorIm >> >> > >> >>> > pl.java:25) >> >> > >> >>> > > at java.lang.reflect.Method.invoke(Method.java:597) >> >> > >> >>> > > at >> >> > >> >>> > >> org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:295) >> >> > >> >>> > > at >> >> > >> >>> org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:433) >> >> > >> >>> > > Aug 17, 2009 3:40:13 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: org.apache.webapp.balancer.BalancerFilter: init(): >> >> > >> ruleChain: >> >> > >> >>> > [org.apache.webapp.balancer.RuleChain: >> >> > >> >>> > [org.apache.webapp.balancer.rules.URLStringMatchRule: Target >> >> > string: >> >> > >> >>> News >> >> > >> >>> > / Redirect URL: http://www.cnn.com], >> >> > >> >>> > [org.apache.webapp.balancer.rules.RequestParameterRule: >> Target >> >> > param >> >> > >> >>> name: >> >> > >> >>> > paramName / Target param value: paramValue / Redirect URL: >> >> > >> >>> > http://www.yahoo.com], >> >> > >> >>> > [org.apache.webapp.balancer.rules.AcceptEverythingRule: >> >> Redirect >> >> > >> URL: >> >> > >> >>> > http://jakarta.apache.org]] >> >> > >> >>> > > Aug 17, 2009 3:40:13 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: ContextListener: contextInitialized() >> >> > >> >>> > > Aug 17, 2009 3:40:13 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: SessionListener: contextInitialized() >> >> > >> >>> > > Aug 17, 2009 3:40:13 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: ContextListener: contextInitialized() >> >> > >> >>> > > Aug 17, 2009 3:40:13 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: SessionListener: contextInitialized() >> >> > >> >>> > > Aug 17, 2009 4:25:59 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: SessionListener: contextDestroyed() >> >> > >> >>> > > Aug 17, 2009 4:25:59 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: ContextListener: contextDestroyed() >> >> > >> >>> > > Aug 17, 2009 4:25:59 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: SessionListener: contextDestroyed() >> >> > >> >>> > > Aug 17, 2009 4:25:59 PM >> >> > >> org.apache.catalina.core.ApplicationContext >> >> > >> >>> log >> >> > >> >>> > > INFO: ContextListener: contextDestroyed() >> >> > >> >>> > > >> >> > >> >>> > >> -----Original Message----- >> >> > >> >>> > >> From: Les Hazlewood [mailto:[email protected]] >> >> > >> >>> > >> Sent: Monday, August 17, 2009 4:24 PM >> >> > >> >>> > >> To: [email protected] >> >> > >> >>> > >> Subject: Re: need more help with SSO >> >> > >> >>> > >> >> >> > >> >>> > >> Hi Andy, >> >> > >> >>> > >> >> >> > >> >>> > >> It goes in the main section, definitely. Is there any >> more >> >> to >> >> > >> the >> >> > >> >>> > >> exception? I'd like to see the entire stack trace if >> >> > possible. >> >> > >> >>> > >> >> >> > >> >>> > >> - Les >> >> > >> >>> > >> >> >> > >> >>> > >> On Mon, Aug 17, 2009 at 3:41 PM, Andy >> >> > >> Tripp<[email protected]> >> >> > >> >>> > >> wrote: >> >> > >> >>> > >> > I created my own Cache and CacheManager: >> >> > >> >>> > >> > >> >> > >> >>> > >> > public class VonageDistributedSessionCache implements >> >> Cache >> >> > { >> >> > >> >>> > >> > public VonageDistributedSessionCache(String name) { >> >> > >> >>> > >> > >> System.err.println("VonageDistributedSessionCache >> >> > >> >>> > >> > constructor."); >> >> > >> >>> > >> > } >> >> > >> >>> > >> > ... >> >> > >> >>> > >> > } >> >> > >> >>> > >> > >> >> > >> >>> > >> > public class VonageDistributedSessionCacheManager >> >> implements >> >> > >> >>> > >> > CacheManager { >> >> > >> >>> > >> > public Cache getCache(String name) throws >> >> CacheException >> >> > { >> >> > >> >>> > >> > return new VonageDistributedSessionCache(name); >> >> > >> >>> > >> > } >> >> > >> >>> > >> > } >> >> > >> >>> > >> > >> >> > >> >>> > >> > Then in [main] section of my ShiroFilter in web.xml, I >> >> have: >> >> > >> >>> > >> > [main] >> >> > >> >>> > >> > realmA = >> >> com.vonage.auth.client.VonageAuthenticationRealm >> >> > >> >>> > >> > >> >> > >> >>> > >> > securityManager.sessionMode = native >> >> > >> >>> > >> > >> >> > >> >>> > >> > And when I add this: >> >> > >> >>> > >> > # pull in vonage centralized authentication: >> >> > >> >>> > >> > cacheManager = >> >> > >> >>> > >> > >> >> com.vonage.auth.client.VonageDistributedSessionCacheManager >> >> > >> >>> > >> > sessionDAO = >> >> > org.apache.shiro.session.mgt.eis.MemorySessionDAO >> >> > >> >>> > >> > sessionDAO.cacheManager = $cacheManager >> >> > >> >>> > >> > securityManager.sessionDAO = $sessionDAO >> >> > >> >>> > >> > securityManager.cacheManager = $cacheManager >> >> > >> >>> > >> > >> >> > >> >>> > >> > ...I get this error: >> >> > >> >>> > >> > javax.servlet.ServletException: Unable to load from >> text >> >> > >> >>> > configuration. >> >> > >> >>> > >> > >> >> > >> >>> > >> > So...does this injection go here in the [main] section >> of >> >> > >> >>> > ShiroFilter, >> >> > >> >>> > >> > or somewhere else? >> >> > >> >>> > >> > >> >> > >> >>> > >> > Thanks, >> >> > >> >>> > >> > Andy >> >> > >> >>> > >> > >> >> > >> >>> > > >> >> > >> >> >> >> > >> > >> >> > > >> > >
