From da627b843fe81fa0fe52a046c1be8595630e9ae7 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 24 Mar 2009 21:07:27 +0000 Subject: [PATCH] jetty @ eclipse initial commit git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@8 7e9141cc-0065-0410-87d8-b60c137991c4 --- LICENSE-APACHE-2.0.txt | 202 + LICENSE-CONTRIBUTOR/CDDLv1.0.txt | 119 + LICENSE-CONTRIBUTOR/ccla-exist.pdf | Bin 0 -> 431935 bytes LICENSE-CONTRIBUTOR/ccla-simulalabs.txt | 157 + LICENSE-CONTRIBUTOR/ccla-template.txt | 176 + LICENSE-CONTRIBUTOR/cla-djencks.txt | 141 + LICENSE-CONTRIBUTOR/cla-gregw.txt | 141 + LICENSE-CONTRIBUTOR/cla-janb.txt | 139 + LICENSE-CONTRIBUTOR/cla-jesse.txt | 141 + LICENSE-CONTRIBUTOR/cla-jfarcand.txt | 142 + LICENSE-CONTRIBUTOR/cla-jstrachan.txt | 143 + LICENSE-CONTRIBUTOR/cla-jules.txt | 141 + LICENSE-CONTRIBUTOR/cla-ngonzalez.txt | 141 + LICENSE-CONTRIBUTOR/cla-sbordet.txt | 140 + LICENSE-CONTRIBUTOR/cla-template.txt | 131 + LICENSE-CONTRIBUTOR/cla-tvernum.txt | 141 + LICENSE-ECLIPSE-1.0.html | 320 + NOTICE.txt | 21 + README.txt | 17 + VERSION.txt | 3968 ++++++ jetty-ajp/pom.xml | 76 + jetty-ajp/src/main/config/etc/jetty-ajp.xml | 18 + .../eclipse/jetty/ajp/Ajp13Connection.java | 252 + .../org/eclipse/jetty/ajp/Ajp13Generator.java | 803 ++ .../org/eclipse/jetty/ajp/Ajp13Packet.java | 63 + .../eclipse/jetty/ajp/Ajp13PacketMethods.java | 69 + .../org/eclipse/jetty/ajp/Ajp13Parser.java | 876 ++ .../org/eclipse/jetty/ajp/Ajp13Request.java | 113 + .../jetty/ajp/Ajp13RequestHeaders.java | 62 + .../eclipse/jetty/ajp/Ajp13RequestPacket.java | 85 + .../jetty/ajp/Ajp13ResponseHeaders.java | 43 + .../jetty/ajp/Ajp13SocketConnector.java | 115 + .../jetty/ajp/Ajp13ConnectionTest.java | 313 + .../org/eclipse/jetty/ajp/TestAjpParser.java | 620 + jetty-annotations/pom.xml | 85 + .../jetty/annotations/AnnotationFinder.java | 729 ++ .../annotations/AnnotationProcessor.java | 782 ++ .../jetty/annotations/ClassNameResolver.java | 37 + .../jetty/annotations/Configuration.java | 145 + .../org/eclipse/jetty/annotations/Util.java | 49 + .../org/eclipse/jetty/annotations/ClassA.java | 95 + .../org/eclipse/jetty/annotations/ClassB.java | 53 + .../org/eclipse/jetty/annotations/ClassC.java | 75 + .../org/eclipse/jetty/annotations/Sample.java | 26 + .../annotations/ServletAnnotationTest.java | 155 + .../annotations/TestAnnotationFinder.java | 105 + .../TestAnnotationInheritance.java | 249 + .../annotations/resources/ResourceA.java | 115 + .../annotations/resources/ResourceB.java | 40 + jetty-assembly-descriptor/pom.xml | 10 + .../src/main/resources/assemblies/config.xml | 17 + .../resources/assemblies/site-component.xml | 14 + jetty-assembly/pom.xml | 503 + .../src/main/assembly/jetty-src.xml | 21 + jetty-assembly/src/main/assembly/jetty.xml | 19 + .../src/main/assembly/site-component.xml | 18 + .../resources/bin/build_release_bundles.sh | 27 + .../src/main/resources/bin/jetty-xinetd.sh | 14 + .../src/main/resources/bin/jetty.sh | 656 + .../resources/contexts-available/README.TXT | 3 + .../src/main/resources/contexts/README.TXT | 15 + .../src/main/resources/contexts/javadoc.xml | 28 + .../src/main/resources/javadoc/contents.html | 7 + .../main/resources/resources/log4j.properties | 9 + jetty-client/pom.xml | 85 + .../org/eclipse/jetty/client/Address.java | 86 + .../eclipse/jetty/client/CachedExchange.java | 68 + .../eclipse/jetty/client/ContentExchange.java | 126 + .../org/eclipse/jetty/client/HttpClient.java | 699 ++ .../eclipse/jetty/client/HttpConnection.java | 574 + .../eclipse/jetty/client/HttpDestination.java | 546 + .../jetty/client/HttpEventListener.java | 63 + .../client/HttpEventListenerWrapper.java | 139 + .../eclipse/jetty/client/HttpExchange.java | 645 + .../eclipse/jetty/client/SelectConnector.java | 198 + .../eclipse/jetty/client/SocketConnector.java | 87 + .../jetty/client/security/Authorization.java | 28 + .../client/security/BasicAuthorization.java | 52 + .../client/security/DigestAuthorization.java | 138 + .../client/security/HashRealmResolver.java | 40 + .../client/security/ProxyAuthorization.java | 52 + .../eclipse/jetty/client/security/Realm.java | 27 + .../jetty/client/security/RealmResolver.java | 22 + .../client/security/SecurityListener.java | 260 + .../client/security/SimpleRealmResolver.java | 39 + .../jetty/client/webdav/MkcolExchange.java | 55 + .../jetty/client/webdav/PropfindExchange.java | 48 + .../jetty/client/webdav/WebdavListener.java | 312 + .../webdav/WebdavSupportedExchange.java | 65 + .../client/AsyncSslHttpExchangeTest.java | 34 + .../client/AsyncSslSecurityListenerTest.java | 25 + .../org/eclipse/jetty/client/ExpireTest.java | 161 + ...ernalKeyStoreAsyncSslHttpExchangeTest.java | 38 + .../jetty/client/HttpExchangeTest.java | 391 + .../jetty/client/SecurityListenerTest.java | 330 + .../jetty/client/SslHttpExchangeTest.java | 59 + .../jetty/client/SslSecurityListenerTest.java | 232 + .../jetty/client/WebdavListenerTest.java | 147 + .../client/security/SecurityResolverTest.java | 45 + jetty-client/src/test/resources/foo.txt | 1 + jetty-client/src/test/resources/keystore | Bin 0 -> 1426 bytes .../src/test/resources/realm.properties | 22 + jetty-deploy/pom.xml | 78 + .../jetty/deploy/ConfigurationManager.java | 28 + .../eclipse/jetty/deploy/ContextDeployer.java | 371 + .../deploy/FileConfigurationManager.java | 69 + .../eclipse/jetty/deploy/WebAppDeployer.java | 253 + jetty-eclipse-codetemplates.xml | 50 + jetty-eclipse-java-format.xml | 267 + jetty-http/pom.xml | 70 + .../eclipse/jetty/http/AbstractGenerator.java | 491 + .../eclipse/jetty/http/EncodedHttpURI.java | 163 + .../org/eclipse/jetty/http/Generator.java | 95 + .../org/eclipse/jetty/http/HttpContent.java | 36 + .../org/eclipse/jetty/http/HttpCookie.java | 186 + .../org/eclipse/jetty/http/HttpFields.java | 1463 +++ .../org/eclipse/jetty/http/HttpGenerator.java | 947 ++ .../eclipse/jetty/http/HttpHeaderValues.java | 100 + .../org/eclipse/jetty/http/HttpHeaders.java | 226 + .../org/eclipse/jetty/http/HttpMethods.java | 59 + .../org/eclipse/jetty/http/HttpParser.java | 1137 ++ .../org/eclipse/jetty/http/HttpSchemes.java | 33 + .../org/eclipse/jetty/http/HttpStatus.java | 1033 ++ .../org/eclipse/jetty/http/HttpTokens.java | 37 + .../java/org/eclipse/jetty/http/HttpURI.java | 592 + .../org/eclipse/jetty/http/HttpVersions.java | 42 + .../org/eclipse/jetty/http/MimeTypes.java | 368 + .../java/org/eclipse/jetty/http/Parser.java | 36 + .../java/org/eclipse/jetty/http/PathMap.java | 544 + .../eclipse/jetty/http/security/B64Code.java | 280 + .../jetty/http/security/Constraint.java | 214 + .../jetty/http/security/Credential.java | 207 + .../eclipse/jetty/http/security/Password.java | 229 + .../jetty/http/security/UnixCrypt.java | 460 + .../http/ssl/SslSelectChannelEndPoint.java | 789 ++ .../eclipse/jetty/http/encoding.properties | 3 + .../org/eclipse/jetty/http/mime.properties | 181 + .../org/eclipse/jetty/http/useragents | 57 + .../jetty/http/HttpGeneratorClientTest.java | 355 + .../eclipse/jetty/http/HttpGeneratorTest.java | 270 + .../eclipse/jetty/http/HttpHeaderTest.java | 487 + .../eclipse/jetty/http/HttpParserTest.java | 560 + .../jetty/http/HttpStatusCodeTest.java | 26 + .../org/eclipse/jetty/http/PathMapTest.java | 187 + jetty-io/pom.xml | 64 + .../org/eclipse/jetty/io/AbstractBuffer.java | 707 ++ .../org/eclipse/jetty/io/AbstractBuffers.java | 193 + .../org/eclipse/jetty/io/AsyncEndPoint.java | 32 + .../java/org/eclipse/jetty/io/Buffer.java | 371 + .../org/eclipse/jetty/io/BufferCache.java | 154 + .../org/eclipse/jetty/io/BufferDateCache.java | 56 + .../java/org/eclipse/jetty/io/BufferUtil.java | 311 + .../java/org/eclipse/jetty/io/Buffers.java | 30 + .../org/eclipse/jetty/io/ByteArrayBuffer.java | 428 + .../eclipse/jetty/io/ByteArrayEndPoint.java | 338 + .../java/org/eclipse/jetty/io/Connection.java | 23 + .../java/org/eclipse/jetty/io/EndPoint.java | 152 + .../org/eclipse/jetty/io/EofException.java | 34 + .../org/eclipse/jetty/io/HttpException.java | 88 + .../org/eclipse/jetty/io/SimpleBuffers.java | 74 + .../main/java/org/eclipse/jetty/io/View.java | 235 + .../eclipse/jetty/io/WriterOutputStream.java | 91 + .../eclipse/jetty/io/bio/SocketEndPoint.java | 171 + .../eclipse/jetty/io/bio/StreamEndPoint.java | 288 + .../eclipse/jetty/io/bio/StringEndPoint.java | 87 + .../eclipse/jetty/io/nio/ChannelEndPoint.java | 435 + .../eclipse/jetty/io/nio/DirectNIOBuffer.java | 331 + .../jetty/io/nio/IndirectNIOBuffer.java | 56 + .../org/eclipse/jetty/io/nio/NIOBuffer.java | 32 + .../jetty/io/nio/RandomAccessFileBuffer.java | 186 + .../jetty/io/nio/SelectChannelEndPoint.java | 516 + .../eclipse/jetty/io/nio/SelectorManager.java | 658 + .../eclipse/jetty/io/AbstractBuffersTest.java | 181 + .../org/eclipse/jetty/io/BufferCacheTest.java | 162 + .../java/org/eclipse/jetty/io/BufferTest.java | 279 + .../org/eclipse/jetty/io/BufferUtilTest.java | 108 + .../java/org/eclipse/jetty/io/IOTest.java | 98 + jetty-jaspi/pom.xml | 78 + .../security/jaspi/JaspiAuthenticator.java | 149 + .../jaspi/JaspiAuthenticatorFactory.java | 157 + .../security/jaspi/JaspiMessageInfo.java | 215 + .../jaspi/ServletCallbackHandler.java | 134 + .../security/jaspi/SimpleAuthConfig.java | 71 + .../CredentialValidationCallback.java | 74 + .../jaspi/modules/BaseAuthModule.java | 146 + .../jaspi/modules/BasicAuthModule.java | 96 + .../jaspi/modules/ClientCertAuthModule.java | 89 + .../jaspi/modules/DigestAuthModule.java | 357 + .../jaspi/modules/FormAuthModule.java | 436 + .../security/jaspi/modules/UserInfo.java | 48 + jetty-jmx/pom.xml | 77 + jetty-jmx/src/main/config/etc/jetty-jmx.xml | 60 + .../org/eclipse/jetty/jmx/MBeanContainer.java | 274 + .../org/eclipse/jetty/jmx/ObjectMBean.java | 719 ++ .../org/eclipse/jetty/jmx/ServerMBean.java | 44 + .../jmx/handler/ContextHandlerMBean.java | 78 + .../jetty/jmx/servlet/HolderMBean.java | 38 + .../jetty/jmx/webapp/WebAppContextMBean.java | 43 + .../AbstractBayeux-mbean.properties | 9 + .../ContextDeployer-mbean.properties | 11 + .../WebAppDeployer-mbean.properties | 14 + .../ContextHandler-mbean.properties | 28 + .../ContextHandlerCollection-mbean.properties | 2 + .../HandlerCollection-mbean.properties | 2 + .../HandlerContainer-mbean.properties | 2 + .../HandlerWrapper-mbean.properties | 2 + .../StatisticsHandler-mbean.properties | 17 + .../AbstractConnector-mbean.properties | 18 + .../management/Connector-mbean.properties | 29 + .../jetty/management/Handler-mbean.properties | 3 + .../NCSARequestLog-mbean.properties | 6 + .../jetty/management/Server-mbean.properties | 7 + .../SelectChannelConnector-mbean.properties | 3 + .../AbstractSessionManager-mbean.properties | 15 + .../management/Context-mbean.properties | 4 + .../management/FilterMapping-mbean.properties | 4 + .../management/Holder-mbean.properties | 6 + .../ServletHandler-mbean.properties | 5 + .../management/ServletHolder-mbean.properties | 4 + .../ServletMapping-mbean.properties | 3 + .../management/LifeCycle-mbean.properties | 9 + .../log/management/Logger-mbean.properties | 2 + .../log/management/Slf4jLog-mbean.properties | 1 + .../log/management/StdErrLog-mbean.properties | 1 + .../BoundedThreadPool-mbean.properties | 7 + .../QueuedThreadPool-mbean.properties | 9 + .../management/ThreadPool-mbean.properties | 4 + .../management/WebAppContext-mbean.properties | 14 + jetty-jmx/src/test/java/com/acme/Base.java | 87 + jetty-jmx/src/test/java/com/acme/Derived.java | 45 + .../src/test/java/com/acme/Signature.java | 21 + .../eclipse/jetty/jmx/ObjectMBeanTest.java | 59 + .../acme/management/Derived-mbean.properties | 5 + jetty-jndi/pom.xml | 87 + .../eclipse/jetty/jndi/ContextFactory.java | 192 + .../jetty/jndi/InitialContextFactory.java | 79 + .../org/eclipse/jetty/jndi/NamingContext.java | 1446 +++ .../org/eclipse/jetty/jndi/NamingUtil.java | 139 + .../jndi/factories/MailSessionReference.java | 183 + .../jetty/jndi/java/javaNameParser.java | 52 + .../jetty/jndi/java/javaRootURLContext.java | 329 + .../jndi/java/javaURLContextFactory.java | 103 + .../jetty/jndi/local/localContextRoot.java | 435 + jetty-jndi/src/main/resources/jndi.properties | 2 + .../factories/TestMailSessionReference.java | 72 + .../org/eclipse/jetty/jndi/java/TestJNDI.java | 295 + .../jetty/jndi/java/TestLocalJNDI.java | 88 + jetty-plus/pom.xml | 103 + jetty-plus/src/main/config/etc/jetty-plus.xml | 74 + .../jetty/plus/annotation/Injection.java | 236 + .../plus/annotation/InjectionCollection.java | 152 + .../plus/annotation/LifeCycleCallback.java | 170 + .../LifeCycleCallbackCollection.java | 159 + .../plus/annotation/PojoContextListener.java | 76 + .../jetty/plus/annotation/PojoFilter.java | 82 + .../jetty/plus/annotation/PojoServlet.java | 174 + .../jetty/plus/annotation/PojoWrapper.java | 20 + .../annotation/PostConstructCallback.java | 60 + .../plus/annotation/PreDestroyCallback.java | 70 + .../eclipse/jetty/plus/annotation/RunAs.java | 75 + .../plus/annotation/RunAsCollection.java | 79 + .../eclipse/jetty/plus/jaas/JAASGroup.java | 147 + .../jetty/plus/jaas/JAASLoginService.java | 280 + .../jetty/plus/jaas/JAASPrincipal.java | 79 + .../org/eclipse/jetty/plus/jaas/JAASRole.java | 32 + .../jetty/plus/jaas/JAASUserPrincipal.java | 74 + .../jetty/plus/jaas/RoleCheckPolicy.java | 31 + .../plus/jaas/StrictRoleCheckPolicy.java | 58 + .../callback/AbstractCallbackHandler.java | 55 + .../jaas/callback/DefaultCallbackHandler.java | 92 + .../plus/jaas/callback/ObjectCallback.java | 62 + .../callback/RequestParameterCallback.java | 56 + .../jaas/spi/AbstractDatabaseLoginModule.java | 136 + .../plus/jaas/spi/AbstractLoginModule.java | 278 + .../plus/jaas/spi/DataSourceLoginModule.java | 85 + .../jetty/plus/jaas/spi/JDBCLoginModule.java | 127 + .../jetty/plus/jaas/spi/LdapLoginModule.java | 680 ++ .../jaas/spi/PropertyFileLoginModule.java | 156 + .../eclipse/jetty/plus/jaas/spi/UserInfo.java | 66 + .../org/eclipse/jetty/plus/jndi/EnvEntry.java | 56 + .../org/eclipse/jetty/plus/jndi/Link.java | 43 + .../eclipse/jetty/plus/jndi/NamingEntry.java | 206 + .../jetty/plus/jndi/NamingEntryUtil.java | 246 + .../org/eclipse/jetty/plus/jndi/Resource.java | 45 + .../eclipse/jetty/plus/jndi/Transaction.java | 113 + .../plus/security/DataSourceLoginService.java | 499 + .../jetty/plus/servlet/ServletHandler.java | 123 + .../plus/webapp/AbstractConfiguration.java | 471 + .../jetty/plus/webapp/Configuration.java | 259 + .../jetty/plus/webapp/EnvConfiguration.java | 205 + .../jetty/plus/jndi/TestNamingEntries.java | 287 + .../jetty/plus/jndi/TestNamingEntryUtil.java | 126 + .../jetty/plus/webapp/TestConfiguration.java | 119 + jetty-rewrite/pom.xml | 87 + .../src/main/config/etc/jetty-rewrite.xml | 77 + .../rewrite/handler/CookiePatternRule.java | 81 + .../handler/ForwardedSchemeHeaderRule.java | 53 + .../rewrite/handler/HeaderPatternRule.java | 126 + .../jetty/rewrite/handler/HeaderRule.java | 102 + .../jetty/rewrite/handler/LegacyRule.java | 94 + .../jetty/rewrite/handler/MsieSslRule.java | 91 + .../jetty/rewrite/handler/PatternRule.java | 78 + .../rewrite/handler/RedirectPatternRule.java | 64 + .../jetty/rewrite/handler/RegexRule.java | 83 + .../rewrite/handler/ResponsePatternRule.java | 86 + .../jetty/rewrite/handler/RewriteHandler.java | 339 + .../rewrite/handler/RewritePatternRule.java | 67 + .../rewrite/handler/RewriteRegexRule.java | 72 + .../eclipse/jetty/rewrite/handler/Rule.java | 88 + .../jetty/rewrite/handler/RuleContainer.java | 281 + .../handler/VirtualHostRuleContainer.java | 111 + .../rewrite/handler/AbstractRuleTestCase.java | 76 + .../handler/CookiePatternRuleTest.java | 81 + .../ForwardedSchemeHeaderRuleTest.java | 89 + .../handler/HeaderPatternRuleTest.java | 105 + .../jetty/rewrite/handler/LegacyRuleTest.java | 62 + .../rewrite/handler/MsieSslRuleTest.java | 222 + .../rewrite/handler/PatternRuleTest.java | 148 + .../handler/RedirectPatternRuleTest.java | 46 + .../jetty/rewrite/handler/RegexRuleTest.java | 110 + .../handler/ResponsePatternRuleTest.java | 78 + .../rewrite/handler/RewriteHandlerTest.java | 141 + .../handler/RewritePatternRuleTest.java | 51 + .../rewrite/handler/RewriteRegexRuleTest.java | 47 + .../handler/VirtualHostRuleContainerTest.java | 184 + .../jetty-rewrite.xml | 283 + jetty-security/pom.xml | 73 + .../jetty/security/Authentication.java | 66 + .../eclipse/jetty/security/Authenticator.java | 53 + .../jetty/security/ConstraintAware.java | 28 + .../jetty/security/ConstraintMapping.java | 79 + .../security/ConstraintSecurityHandler.java | 316 + .../security/CrossContextPsuedoSession.java | 31 + .../jetty/security/DefaultAuthentication.java | 59 + .../security/DefaultAuthenticatorFactory.java | 88 + .../security/DefaultIdentityService.java | 119 + .../jetty/security/DefaultUserIdentity.java | 71 + .../HashCrossContextPsuedoSession.java | 90 + .../jetty/security/HashLoginService.java | 247 + .../jetty/security/IdentityService.java | 99 + .../jetty/security/JDBCLoginService.java | 263 + .../jetty/security/LazyAuthentication.java | 85 + .../eclipse/jetty/security/LoginService.java | 28 + .../jetty/security/MappedLoginService.java | 292 + .../org/eclipse/jetty/security/RoleInfo.java | 135 + .../jetty/security/RoleRunAsToken.java | 39 + .../eclipse/jetty/security/RunAsToken.java | 22 + .../jetty/security/SecurityHandler.java | 524 + .../jetty/security/ServerAuthException.java | 42 + .../jetty/security/UserDataConstraint.java | 35 + .../authentication/BasicAuthenticator.java | 106 + .../ClientCertAuthenticator.java | 98 + .../authentication/DelegateAuthenticator.java | 57 + .../authentication/DigestAuthenticator.java | 333 + .../authentication/FormAuthenticator.java | 219 + .../authentication/LazyAuthenticator.java | 45 + .../authentication/LoginAuthenticator.java | 44 + .../authentication/LoginCallback.java | 50 + .../authentication/LoginCallbackImpl.java | 104 + .../SessionCachingAuthenticator.java | 59 + .../XCPSCachingAuthenticator.java | 55 + .../jetty/security/ConstraintTest.java | 607 + jetty-server/pom.xml | 106 + .../src/main/assembly/site-component.xml | 15 + .../src/main/config/etc/jdbcRealm.properties | 72 + .../src/main/config/etc/jetty-bio-ssl.xml | 25 + .../src/main/config/etc/jetty-bio.xml | 23 + .../src/main/config/etc/jetty-proxy.xml | 64 + .../src/main/config/etc/jetty-ssl.xml | 29 + .../src/main/config/etc/jetty-stats.xml | 22 + .../src/main/config/etc/jetty-xinetd.xml | 56 + jetty-server/src/main/config/etc/jetty.xml | 189 + jetty-server/src/main/config/etc/keystore | Bin 0 -> 1416 bytes .../src/main/config/etc/realm.properties | 21 + .../jetty/server/AbstractConnector.java | 990 ++ .../eclipse/jetty/server/AsyncRequest.java | 708 ++ .../org/eclipse/jetty/server/Connector.java | 322 + .../eclipse/jetty/server/CookieCutter.java | 296 + .../org/eclipse/jetty/server/Dispatcher.java | 571 + .../org/eclipse/jetty/server/Handler.java | 48 + .../jetty/server/HandlerContainer.java | 33 + .../eclipse/jetty/server/HttpConnection.java | 1114 ++ .../org/eclipse/jetty/server/HttpInput.java | 62 + .../eclipse/jetty/server/HttpOnlyCookie.java | 45 + .../org/eclipse/jetty/server/HttpOutput.java | 173 + .../org/eclipse/jetty/server/HttpWriter.java | 266 + .../jetty/server/InclusiveByteRange.java | 211 + .../eclipse/jetty/server/LocalConnector.java | 221 + .../eclipse/jetty/server/NCSARequestLog.java | 513 + .../org/eclipse/jetty/server/Request.java | 1866 +++ .../org/eclipse/jetty/server/RequestLog.java | 26 + .../eclipse/jetty/server/ResourceCache.java | 555 + .../org/eclipse/jetty/server/Response.java | 1191 ++ .../eclipse/jetty/server/RetryRequest.java | 29 + .../java/org/eclipse/jetty/server/Server.java | 779 ++ .../jetty/server/Servlet3Continuation.java | 115 + .../jetty/server/SessionIdManager.java | 83 + .../eclipse/jetty/server/SessionManager.java | 327 + .../eclipse/jetty/server/UserIdentity.java | 136 + .../jetty/server/bio/SocketConnector.java | 264 + .../jetty/server/handler/AbstractHandler.java | 97 + .../handler/AbstractHandlerContainer.java | 88 + .../jetty/server/handler/CompleteHandler.java | 37 + .../jetty/server/handler/ContextHandler.java | 1903 +++ .../handler/ContextHandlerCollection.java | 319 + .../jetty/server/handler/DefaultHandler.java | 193 + .../jetty/server/handler/ErrorHandler.java | 175 + .../server/handler/HandlerCollection.java | 220 + .../jetty/server/handler/HandlerList.java | 54 + .../jetty/server/handler/HandlerWrapper.java | 179 + .../server/handler/MovedContextHandler.java | 158 + .../server/handler/RequestLogHandler.java | 132 + .../jetty/server/handler/ResourceHandler.java | 335 + .../server/handler/StatisticsHandler.java | 369 + .../server/nio/AbstractNIOConnector.java | 77 + .../server/nio/BlockingChannelConnector.java | 189 + .../server/nio/InheritedChannelConnector.java | 66 + .../jetty/server/nio/NIOConnector.java | 26 + .../server/nio/SelectChannelConnector.java | 328 + .../session/AbstractSessionIdManager.java | 161 + .../session/AbstractSessionManager.java | 1182 ++ .../server/session/HashSessionIdManager.java | 256 + .../server/session/HashSessionManager.java | 652 + .../server/session/JDBCSessionIdManager.java | 695 ++ .../server/session/JDBCSessionManager.java | 1080 ++ .../jetty/server/session/SessionHandler.java | 291 + .../eclipse/jetty/server/ssl/ServletSSL.java | 83 + .../server/ssl/SslSelectChannelConnector.java | 703 ++ .../jetty/server/ssl/SslSocketConnector.java | 665 + .../resources/org/eclipse/jetty/favicon.ico | Bin 0 -> 1150 bytes .../jetty/server/AsyncContextTest.java | 294 + .../server/BlockingChannelServerTest.java | 26 + .../server/BusySelectChannelServerTest.java | 139 + .../server/CheckReverseProxyHeadersTest.java | 177 + .../org/eclipse/jetty/server/DumpHandler.java | 217 + .../jetty/server/EncodedHttpURITest.java | 45 + .../jetty/server/HttpConnectionTest.java | 354 + .../jetty/server/HttpServerTestBase.java | 918 ++ .../org/eclipse/jetty/server/HttpURITest.java | 188 + .../org/eclipse/jetty/server/RFC2616Test.java | 888 ++ .../org/eclipse/jetty/server/RequestTest.java | 428 + .../jetty/server/ResourceCacheTest.java | 155 + .../eclipse/jetty/server/ResponseTest.java | 508 + .../jetty/server/SelectChannelServerTest.java | 26 + .../org/eclipse/jetty/server/ServerTest.java | 51 + .../jetty/server/SocketServerTest.java | 26 + .../eclipse/jetty/server/UnreadInputTest.java | 119 + .../handler/ContextHandlerCollectionTest.java | 176 + .../server/handler/ContextHandlerTest.java | 259 + .../server/handler/StatisticsHandlerTest.java | 383 + .../jetty/server/ssl/SSLEngineTest.java | 266 + .../src/test/resources/fakeRequests.txt | 27 + jetty-server/src/test/resources/keystore | Bin 0 -> 1426 bytes jetty-servlet-tester/pom.xml | 23 + .../org/eclipse/jetty/testing/HttpTester.java | 504 + .../eclipse/jetty/testing/ServletTester.java | 358 + .../eclipse/jetty/testing/HttpTesterTest.java | 42 + .../eclipse/jetty/testing/ServletTest.java | 288 + jetty-servlet/pom.xml | 61 + .../eclipse/jetty/servlet/DefaultServlet.java | 905 ++ .../jetty/servlet/ErrorPageErrorHandler.java | 267 + .../eclipse/jetty/servlet/FilterHolder.java | 194 + .../eclipse/jetty/servlet/FilterMapping.java | 280 + .../org/eclipse/jetty/servlet/Holder.java | 379 + .../org/eclipse/jetty/servlet/Invoker.java | 303 + .../jetty/servlet/NIOResourceCache.java | 86 + .../eclipse/jetty/servlet/NoJspServlet.java | 35 + .../jetty/servlet/ServletContextHandler.java | 502 + .../eclipse/jetty/servlet/ServletHandler.java | 1370 +++ .../eclipse/jetty/servlet/ServletHolder.java | 685 ++ .../eclipse/jetty/servlet/ServletMapping.java | 80 + .../jetty/servlet/StatisticsServlet.java | 240 + .../jetty/servlet/AbstractSessionTest.java | 176 + .../eclipse/jetty/servlet/DispatcherTest.java | 295 + .../eclipse/jetty/servlet/InvokerTest.java | 82 + .../jetty/servlet/SessionTestClient.java | 125 + .../jetty/servlet/SessionTestServer.java | 348 + jetty-servlets/pom.xml | 83 + .../jetty/servlets/AsyncProxyServlet.java | 352 + .../java/org/eclipse/jetty/servlets/CGI.java | 417 + .../eclipse/jetty/servlets/ConcatServlet.java | 120 + .../eclipse/jetty/servlets/GzipFilter.java | 578 + .../jetty/servlets/IncludableGzipFilter.java | 75 + .../jetty/servlets/MultiPartFilter.java | 456 + .../eclipse/jetty/servlets/ProxyServlet.java | 316 + .../org/eclipse/jetty/servlets/PutFilter.java | 327 + .../org/eclipse/jetty/servlets/QoSFilter.java | 227 + .../jetty/servlets/UserAgentFilter.java | 147 + .../eclipse/jetty/servlets/WelcomeFilter.java | 65 + .../jetty/servlets/AsyncProxyServer.java | 45 + .../eclipse/jetty/servlets/PutFilterTest.java | 250 + .../eclipse/jetty/servlets/QoSFilterTest.java | 235 + jetty-start/pom.xml | 28 + .../org/eclipse/jetty/start/Classpath.java | 201 + .../java/org/eclipse/jetty/start/Main.java | 698 ++ .../java/org/eclipse/jetty/start/Monitor.java | 128 + .../java/org/eclipse/jetty/start/README.txt | 38 + .../java/org/eclipse/jetty/start/Version.java | 109 + .../org/eclipse/jetty/start/start.config | 141 + jetty-test-webapp/jetty-chat.jmx | 318 + jetty-test-webapp/pom.xml | 138 + .../src/main/config/contexts/demo.xml | 11 + .../config/contexts/test.d/override-web.xml | 42 + .../src/main/config/contexts/test.xml | 78 + .../src/main/java/com/acme/ChatServlet.java | 276 + .../src/main/java/com/acme/CometServlet.java | 85 + .../src/main/java/com/acme/CookieDump.java | 114 + .../src/main/java/com/acme/Counter.java | 37 + .../src/main/java/com/acme/Date2Tag.java | 48 + .../src/main/java/com/acme/DateTag.java | 65 + .../main/java/com/acme/DispatchServlet.java | 260 + .../src/main/java/com/acme/Dump.java | 899 ++ .../src/main/java/com/acme/HelloWorld.java | 67 + .../src/main/java/com/acme/SessionDump.java | 176 + .../src/main/java/com/acme/TagListener.java | 135 + .../src/main/java/com/acme/TestFilter.java | 91 + .../src/main/java/com/acme/TestListener.java | 152 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/acme-taglib.tld | 28 + .../src/main/webapp/WEB-INF/acme-taglib2.tld | 35 + .../src/main/webapp/WEB-INF/jetty-web.xml | 14 + .../src/main/webapp/WEB-INF/tags/CVS/Entries | 2 + .../main/webapp/WEB-INF/tags/CVS/Repository | 1 + .../src/main/webapp/WEB-INF/tags/CVS/Root | 1 + .../src/main/webapp/WEB-INF/tags/panel.tag | 17 + .../src/main/webapp/WEB-INF/web.xml | 303 + .../src/main/webapp/auth/file.txt | 10 + .../src/main/webapp/auth/relax.txt | 10 + .../src/main/webapp/cgi-bin/hello.sh | 4 + jetty-test-webapp/src/main/webapp/d.txt | 10 + jetty-test-webapp/src/main/webapp/da.txt | 1000 ++ jetty-test-webapp/src/main/webapp/da.txt.gz | Bin 0 -> 2565 bytes jetty-test-webapp/src/main/webapp/dat.txt | 4000 +++++++ jetty-test-webapp/src/main/webapp/data.txt | 10000 ++++++++++++++++ jetty-test-webapp/src/main/webapp/data.txt.gz | Bin 0 -> 25691 bytes jetty-test-webapp/src/main/webapp/favicon.ico | Bin 0 -> 1150 bytes jetty-test-webapp/src/main/webapp/index.html | 53 + .../src/main/webapp/jetty_banner.gif | Bin 0 -> 65384 bytes .../src/main/webapp/jsp/bean1.jsp | 15 + .../src/main/webapp/jsp/bean2.jsp | 15 + .../src/main/webapp/jsp/dump.jsp | 23 + .../src/main/webapp/jsp/expr.jsp | 23 + .../src/main/webapp/jsp/index.html | 15 + jetty-test-webapp/src/main/webapp/jsp/tag.jsp | 16 + .../src/main/webapp/jsp/tag2.jsp | 19 + .../src/main/webapp/jsp/tagfile.jsp | 37 + jetty-test-webapp/src/main/webapp/logon.html | 20 + .../src/main/webapp/logonError.html | 5 + .../src/main/webapp/rewrite.html | 14 + jetty-test-webapp/src/main/webapp/snoop.jsp | 201 + jetty-util/pom.xml | 84 + jetty-util/src/main/assembly/config.xml | 20 + .../src/main/config/etc/jetty-logging.xml | 32 + jetty-util/src/main/java/META-INF/MANIFEST.MF | 3 + .../org/eclipse/jetty/util/ArrayQueue.java | 352 + .../org/eclipse/jetty/util/Attributes.java | 31 + .../org/eclipse/jetty/util/AttributesMap.java | 119 + .../jetty/util/BlockingArrayQueue.java | 585 + .../jetty/util/ByteArrayISO8859Writer.java | 261 + .../jetty/util/ByteArrayOutputStream2.java | 44 + .../org/eclipse/jetty/util/DateCache.java | 306 + .../main/java/org/eclipse/jetty/util/IO.java | 461 + .../eclipse/jetty/util/IntrospectionUtil.java | 299 + .../java/org/eclipse/jetty/util/LazyList.java | 477 + .../java/org/eclipse/jetty/util/Loader.java | 148 + .../eclipse/jetty/util/MultiException.java | 175 + .../java/org/eclipse/jetty/util/MultiMap.java | 378 + .../jetty/util/MultiPartOutputStream.java | 135 + .../eclipse/jetty/util/MultiPartWriter.java | 132 + .../jetty/util/QuotedStringTokenizer.java | 786 ++ .../jetty/util/RolloverFileOutputStream.java | 328 + .../java/org/eclipse/jetty/util/Scanner.java | 498 + .../org/eclipse/jetty/util/SingletonList.java | 99 + .../org/eclipse/jetty/util/StringMap.java | 682 ++ .../org/eclipse/jetty/util/StringUtil.java | 383 + .../java/org/eclipse/jetty/util/TypeUtil.java | 564 + .../java/org/eclipse/jetty/util/URIUtil.java | 595 + .../org/eclipse/jetty/util/UrlEncoded.java | 865 ++ .../eclipse/jetty/util/Utf8StringBuffer.java | 161 + .../eclipse/jetty/util/Utf8StringBuilder.java | 160 + .../eclipse/jetty/util/ajax/Continuation.java | 117 + .../jetty/util/ajax/ContinuationSupport.java | 35 + .../org/eclipse/jetty/util/ajax/JSON.java | 1379 +++ .../jetty/util/ajax/JSONDateConvertor.java | 100 + .../jetty/util/ajax/JSONEnumConvertor.java | 87 + .../jetty/util/ajax/JSONObjectConvertor.java | 110 + .../jetty/util/ajax/JSONPojoConvertor.java | 392 + .../jetty/util/ajax/WaitingContinuation.java | 153 + .../util/component/AbstractLifeCycle.java | 195 + .../jetty/util/component/Container.java | 298 + .../jetty/util/component/LifeCycle.java | 114 + .../java/org/eclipse/jetty/util/log/Log.java | 307 + .../org/eclipse/jetty/util/log/Logger.java | 37 + .../org/eclipse/jetty/util/log/LoggerLog.java | 154 + .../org/eclipse/jetty/util/log/Slf4jLog.java | 113 + .../org/eclipse/jetty/util/log/StdErrLog.java | 156 + .../jetty/util/resource/BadResource.java | 114 + .../jetty/util/resource/FileResource.java | 363 + .../jetty/util/resource/JarFileResource.java | 329 + .../jetty/util/resource/JarResource.java | 242 + .../eclipse/jetty/util/resource/Resource.java | 499 + .../util/resource/ResourceCollection.java | 442 + .../jetty/util/resource/ResourceFactory.java | 25 + .../jetty/util/resource/URLResource.java | 292 + .../jetty/util/thread/ExecutorThreadPool.java | 159 + .../util/thread/OldQueuedThreadPool.java | 608 + .../jetty/util/thread/QueuedThreadPool.java | 463 + .../eclipse/jetty/util/thread/ThreadPool.java | 51 + .../eclipse/jetty/util/thread/Timeout.java | 387 + .../eclipse/jetty/util/ArrayQueueTest.java | 163 + .../jetty/util/BlockingArrayQueueTest.java | 311 + .../org/eclipse/jetty/util/DateCacheTest.java | 94 + .../org/eclipse/jetty/util/LazyListTest.java | 355 + .../jetty/util/QuotedStringTokenizerTest.java | 170 + .../org/eclipse/jetty/util/StringMapTest.java | 296 + .../eclipse/jetty/util/StringUtilTest.java | 191 + .../jetty/util/TestIntrospectionUtil.java | 209 + .../java/org/eclipse/jetty/util/URITest.java | 246 + .../eclipse/jetty/util/URLEncodedTest.java | 187 + .../jetty/util/Utf8StringBufferTest.java | 32 + .../jetty/util/Utf8StringBuilderTest.java | 32 + .../util/ajax/JSONPojoConvertorTest.java | 382 + .../org/eclipse/jetty/util/ajax/JSONTest.java | 451 + .../util/component/LifeCycleListenerTest.java | 230 + .../org/eclipse/jetty/util/log/LogTest.java | 33 + .../util/resource/ResourceCollectionTest.java | 76 + .../jetty/util/resource/ResourceTest.java | 347 + .../jetty/util/resource/TestData/alphabet.txt | 1 + .../jetty/util/resource/TestData/alt.zip | Bin 0 -> 840 bytes .../jetty/util/resource/TestData/test.zip | Bin 0 -> 1406 bytes .../util/thread/QueuedThreadPoolTest.java | 156 + .../jetty/util/thread/TimeoutTest.java | 255 + .../org/eclipse/jetty/util/resource/one/1.txt | 1 + .../eclipse/jetty/util/resource/one/dir/1.txt | 1 + .../eclipse/jetty/util/resource/three/2.txt | 1 + .../eclipse/jetty/util/resource/three/3.txt | 1 + .../jetty/util/resource/three/dir/3.txt | 1 + .../org/eclipse/jetty/util/resource/two/1.txt | 1 + .../org/eclipse/jetty/util/resource/two/2.txt | 1 + .../eclipse/jetty/util/resource/two/dir/2.txt | 1 + jetty-util/src/test/resources/resource.txt | 1 + jetty-webapp/pom.xml | 94 + .../src/main/config/etc/webdefault.xml | 414 + .../eclipse/jetty/webapp/Configuration.java | 77 + .../org/eclipse/jetty/webapp/JarScanner.java | 167 + .../webapp/JettyWebXmlConfiguration.java | 121 + .../jetty/webapp/TagLibConfiguration.java | 324 + .../jetty/webapp/WebAppClassLoader.java | 369 + .../eclipse/jetty/webapp/WebAppContext.java | 1309 ++ .../jetty/webapp/WebInfConfiguration.java | 94 + .../jetty/webapp/WebXmlConfiguration.java | 1040 ++ jetty-xml/pom.xml | 90 + .../src/main/assembly/site-component.xml | 15 + .../eclipse/jetty/xml/XmlConfiguration.java | 993 ++ .../java/org/eclipse/jetty/xml/XmlParser.java | 796 ++ .../org/eclipse/jetty/xml/configure_6_0.dtd | 265 + .../eclipse/jetty/xml/TestConfiguration.java | 79 + .../jetty/xml/XmlConfigurationTest.java | 135 + .../org/eclipse/jetty/xml/configure.xml | 131 + pom.xml | 230 + 660 files changed, 149998 insertions(+) create mode 100644 LICENSE-APACHE-2.0.txt create mode 100644 LICENSE-CONTRIBUTOR/CDDLv1.0.txt create mode 100644 LICENSE-CONTRIBUTOR/ccla-exist.pdf create mode 100644 LICENSE-CONTRIBUTOR/ccla-simulalabs.txt create mode 100644 LICENSE-CONTRIBUTOR/ccla-template.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-djencks.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-gregw.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-janb.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-jesse.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-jfarcand.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-jstrachan.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-jules.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-ngonzalez.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-sbordet.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-template.txt create mode 100644 LICENSE-CONTRIBUTOR/cla-tvernum.txt create mode 100644 LICENSE-ECLIPSE-1.0.html create mode 100644 NOTICE.txt create mode 100644 README.txt create mode 100644 VERSION.txt create mode 100644 jetty-ajp/pom.xml create mode 100644 jetty-ajp/src/main/config/etc/jetty-ajp.xml create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Packet.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13PacketMethods.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestHeaders.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestPacket.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13ResponseHeaders.java create mode 100644 jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13SocketConnector.java create mode 100644 jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java create mode 100644 jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java create mode 100644 jetty-annotations/pom.xml create mode 100644 jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationFinder.java create mode 100644 jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationProcessor.java create mode 100644 jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassNameResolver.java create mode 100644 jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Configuration.java create mode 100644 jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassA.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassB.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassC.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/Sample.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletAnnotationTest.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationFinder.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceA.java create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceB.java create mode 100644 jetty-assembly-descriptor/pom.xml create mode 100644 jetty-assembly-descriptor/src/main/resources/assemblies/config.xml create mode 100644 jetty-assembly-descriptor/src/main/resources/assemblies/site-component.xml create mode 100644 jetty-assembly/pom.xml create mode 100644 jetty-assembly/src/main/assembly/jetty-src.xml create mode 100644 jetty-assembly/src/main/assembly/jetty.xml create mode 100644 jetty-assembly/src/main/assembly/site-component.xml create mode 100755 jetty-assembly/src/main/resources/bin/build_release_bundles.sh create mode 100755 jetty-assembly/src/main/resources/bin/jetty-xinetd.sh create mode 100755 jetty-assembly/src/main/resources/bin/jetty.sh create mode 100644 jetty-assembly/src/main/resources/contexts-available/README.TXT create mode 100644 jetty-assembly/src/main/resources/contexts/README.TXT create mode 100644 jetty-assembly/src/main/resources/contexts/javadoc.xml create mode 100644 jetty-assembly/src/main/resources/javadoc/contents.html create mode 100644 jetty-assembly/src/main/resources/resources/log4j.properties create mode 100644 jetty-client/pom.xml create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/Address.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/CachedExchange.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/ContentExchange.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListener.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/Authorization.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/BasicAuthorization.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/DigestAuthorization.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/HashRealmResolver.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/ProxyAuthorization.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/Realm.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/RealmResolver.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/SecurityListener.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/webdav/MkcolExchange.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/webdav/PropfindExchange.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavListener.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslSecurityListenerTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/ExpireTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/WebdavListenerTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/security/SecurityResolverTest.java create mode 100644 jetty-client/src/test/resources/foo.txt create mode 100644 jetty-client/src/test/resources/keystore create mode 100644 jetty-client/src/test/resources/realm.properties create mode 100644 jetty-deploy/pom.xml create mode 100644 jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ConfigurationManager.java create mode 100644 jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ContextDeployer.java create mode 100644 jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java create mode 100644 jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java create mode 100644 jetty-eclipse-codetemplates.xml create mode 100644 jetty-eclipse-java-format.xml create mode 100644 jetty-http/pom.xml create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/EncodedHttpURI.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaders.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethods.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpSchemes.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersions.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/Parser.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/security/B64Code.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/security/Constraint.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/security/Credential.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/security/Password.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/security/UnixCrypt.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslSelectChannelEndPoint.java create mode 100644 jetty-http/src/main/resources/org/eclipse/jetty/http/encoding.properties create mode 100644 jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties create mode 100644 jetty-http/src/main/resources/org/eclipse/jetty/http/useragents create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/HttpHeaderTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/HttpStatusCodeTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java create mode 100644 jetty-io/pom.xml create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffer.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/AsyncEndPoint.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/Buffer.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/BufferUtil.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/Buffers.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayBuffer.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/EofException.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/HttpException.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/SimpleBuffers.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/View.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/nio/NIOBuffer.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java create mode 100644 jetty-io/src/test/java/org/eclipse/jetty/io/AbstractBuffersTest.java create mode 100644 jetty-io/src/test/java/org/eclipse/jetty/io/BufferCacheTest.java create mode 100644 jetty-io/src/test/java/org/eclipse/jetty/io/BufferTest.java create mode 100644 jetty-io/src/test/java/org/eclipse/jetty/io/BufferUtilTest.java create mode 100644 jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java create mode 100644 jetty-jaspi/pom.xml create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiMessageInfo.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/CredentialValidationCallback.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java create mode 100644 jetty-jmx/pom.xml create mode 100644 jetty-jmx/src/main/config/etc/jetty-jmx.xml create mode 100644 jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java create mode 100644 jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java create mode 100644 jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ServerMBean.java create mode 100644 jetty-jmx/src/main/java/org/eclipse/jetty/jmx/handler/ContextHandlerMBean.java create mode 100644 jetty-jmx/src/main/java/org/eclipse/jetty/jmx/servlet/HolderMBean.java create mode 100644 jetty-jmx/src/main/java/org/eclipse/jetty/jmx/webapp/WebAppContextMBean.java create mode 100644 jetty-jmx/src/main/resources/org/eclipse/cometd/management/AbstractBayeux-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/deployer/management/ContextDeployer-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/deployer/management/WebAppDeployer-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/ContextHandler-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/ContextHandlerCollection-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerCollection-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerContainer-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerWrapper-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/StatisticsHandler-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/management/AbstractConnector-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/management/Connector-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/management/Handler-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/management/NCSARequestLog-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/management/Server-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/nio/management/SelectChannelConnector-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/AbstractSessionManager-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/Context-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/FilterMapping-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/Holder-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletHandler-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletHolder-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletMapping-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/management/LifeCycle-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/Logger-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/Slf4jLog-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/StdErrLog-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/BoundedThreadPool-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/QueuedThreadPool-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/ThreadPool-mbean.properties create mode 100644 jetty-jmx/src/main/resources/org/eclipse/jetty/webapp/management/WebAppContext-mbean.properties create mode 100644 jetty-jmx/src/test/java/com/acme/Base.java create mode 100644 jetty-jmx/src/test/java/com/acme/Derived.java create mode 100644 jetty-jmx/src/test/java/com/acme/Signature.java create mode 100644 jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java create mode 100644 jetty-jmx/src/test/resources/com/acme/management/Derived-mbean.properties create mode 100644 jetty-jndi/pom.xml create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/InitialContextFactory.java create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingUtil.java create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaNameParser.java create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaURLContextFactory.java create mode 100644 jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java create mode 100644 jetty-jndi/src/main/resources/jndi.properties create mode 100644 jetty-jndi/src/test/java/org/eclipse/jetty/jndi/factories/TestMailSessionReference.java create mode 100644 jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java create mode 100644 jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java create mode 100644 jetty-plus/pom.xml create mode 100644 jetty-plus/src/main/config/etc/jetty-plus.xml create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/InjectionCollection.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallback.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollection.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PojoContextListener.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PojoFilter.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PojoServlet.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PojoWrapper.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PostConstructCallback.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PreDestroyCallback.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASGroup.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASLoginService.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASPrincipal.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASRole.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASUserPrincipal.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/RoleCheckPolicy.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/StrictRoleCheckPolicy.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/AbstractCallbackHandler.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/ObjectCallback.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/RequestParameterCallback.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractLoginModule.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/DataSourceLoginModule.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/JDBCLoginModule.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/EnvEntry.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Link.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntry.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntryUtil.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Resource.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Transaction.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/AbstractConfiguration.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/Configuration.java create mode 100644 jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java create mode 100644 jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntries.java create mode 100644 jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntryUtil.java create mode 100644 jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/TestConfiguration.java create mode 100644 jetty-rewrite/pom.xml create mode 100644 jetty-rewrite/src/main/config/etc/jetty-rewrite.xml create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/LegacyRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/LegacyRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java create mode 100644 jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml create mode 100644 jetty-security/pom.xml create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/Authentication.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintMapping.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthentication.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/LazyAuthentication.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/LoginService.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/RoleRunAsToken.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/RunAsToken.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/ServerAuthException.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/UserDataConstraint.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DelegateAuthenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LazyAuthenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallback.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionCachingAuthenticator.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/authentication/XCPSCachingAuthenticator.java create mode 100644 jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java create mode 100644 jetty-server/pom.xml create mode 100644 jetty-server/src/main/assembly/site-component.xml create mode 100644 jetty-server/src/main/config/etc/jdbcRealm.properties create mode 100644 jetty-server/src/main/config/etc/jetty-bio-ssl.xml create mode 100644 jetty-server/src/main/config/etc/jetty-bio.xml create mode 100644 jetty-server/src/main/config/etc/jetty-proxy.xml create mode 100644 jetty-server/src/main/config/etc/jetty-ssl.xml create mode 100644 jetty-server/src/main/config/etc/jetty-stats.xml create mode 100644 jetty-server/src/main/config/etc/jetty-xinetd.xml create mode 100644 jetty-server/src/main/config/etc/jetty.xml create mode 100644 jetty-server/src/main/config/etc/keystore create mode 100644 jetty-server/src/main/config/etc/realm.properties create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequest.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpOnlyCookie.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/Request.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/RequestLog.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/Response.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/RetryRequest.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/Server.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/Servlet3Continuation.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/CompleteHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java create mode 100644 jetty-server/src/main/resources/org/eclipse/jetty/favicon.ico create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContextTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelServerTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/RFC2616Test.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/UnreadInputTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java create mode 100644 jetty-server/src/test/resources/fakeRequests.txt create mode 100644 jetty-server/src/test/resources/keystore create mode 100644 jetty-servlet-tester/pom.xml create mode 100644 jetty-servlet-tester/src/main/java/org/eclipse/jetty/testing/HttpTester.java create mode 100644 jetty-servlet-tester/src/main/java/org/eclipse/jetty/testing/ServletTester.java create mode 100644 jetty-servlet-tester/src/test/java/org/eclipse/jetty/testing/HttpTesterTest.java create mode 100644 jetty-servlet-tester/src/test/java/org/eclipse/jetty/testing/ServletTest.java create mode 100644 jetty-servlet/pom.xml create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NIOResourceCache.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NoJspServlet.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java create mode 100644 jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AbstractSessionTest.java create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SessionTestClient.java create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SessionTestServer.java create mode 100644 jetty-servlets/pom.xml create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncProxyServlet.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/QoSFilter.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/UserAgentFilter.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java create mode 100644 jetty-start/pom.xml create mode 100644 jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java create mode 100644 jetty-start/src/main/java/org/eclipse/jetty/start/Main.java create mode 100644 jetty-start/src/main/java/org/eclipse/jetty/start/Monitor.java create mode 100644 jetty-start/src/main/java/org/eclipse/jetty/start/README.txt create mode 100644 jetty-start/src/main/java/org/eclipse/jetty/start/Version.java create mode 100644 jetty-start/src/main/resources/org/eclipse/jetty/start/start.config create mode 100644 jetty-test-webapp/jetty-chat.jmx create mode 100644 jetty-test-webapp/pom.xml create mode 100644 jetty-test-webapp/src/main/config/contexts/demo.xml create mode 100644 jetty-test-webapp/src/main/config/contexts/test.d/override-web.xml create mode 100644 jetty-test-webapp/src/main/config/contexts/test.xml create mode 100644 jetty-test-webapp/src/main/java/com/acme/ChatServlet.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/CometServlet.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/CookieDump.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/Counter.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/Date2Tag.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/DateTag.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/DispatchServlet.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/Dump.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/HelloWorld.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/SessionDump.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/TagListener.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/TestFilter.java create mode 100644 jetty-test-webapp/src/main/java/com/acme/TestListener.java create mode 100644 jetty-test-webapp/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 jetty-test-webapp/src/main/webapp/WEB-INF/acme-taglib.tld create mode 100644 jetty-test-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld create mode 100644 jetty-test-webapp/src/main/webapp/WEB-INF/jetty-web.xml create mode 100644 jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Entries create mode 100644 jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Repository create mode 100644 jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Root create mode 100644 jetty-test-webapp/src/main/webapp/WEB-INF/tags/panel.tag create mode 100644 jetty-test-webapp/src/main/webapp/WEB-INF/web.xml create mode 100644 jetty-test-webapp/src/main/webapp/auth/file.txt create mode 100644 jetty-test-webapp/src/main/webapp/auth/relax.txt create mode 100755 jetty-test-webapp/src/main/webapp/cgi-bin/hello.sh create mode 100644 jetty-test-webapp/src/main/webapp/d.txt create mode 100644 jetty-test-webapp/src/main/webapp/da.txt create mode 100644 jetty-test-webapp/src/main/webapp/da.txt.gz create mode 100644 jetty-test-webapp/src/main/webapp/dat.txt create mode 100644 jetty-test-webapp/src/main/webapp/data.txt create mode 100644 jetty-test-webapp/src/main/webapp/data.txt.gz create mode 100644 jetty-test-webapp/src/main/webapp/favicon.ico create mode 100644 jetty-test-webapp/src/main/webapp/index.html create mode 100644 jetty-test-webapp/src/main/webapp/jetty_banner.gif create mode 100644 jetty-test-webapp/src/main/webapp/jsp/bean1.jsp create mode 100644 jetty-test-webapp/src/main/webapp/jsp/bean2.jsp create mode 100644 jetty-test-webapp/src/main/webapp/jsp/dump.jsp create mode 100644 jetty-test-webapp/src/main/webapp/jsp/expr.jsp create mode 100644 jetty-test-webapp/src/main/webapp/jsp/index.html create mode 100644 jetty-test-webapp/src/main/webapp/jsp/tag.jsp create mode 100644 jetty-test-webapp/src/main/webapp/jsp/tag2.jsp create mode 100644 jetty-test-webapp/src/main/webapp/jsp/tagfile.jsp create mode 100644 jetty-test-webapp/src/main/webapp/logon.html create mode 100644 jetty-test-webapp/src/main/webapp/logonError.html create mode 100644 jetty-test-webapp/src/main/webapp/rewrite.html create mode 100644 jetty-test-webapp/src/main/webapp/snoop.jsp create mode 100644 jetty-util/pom.xml create mode 100644 jetty-util/src/main/assembly/config.xml create mode 100644 jetty-util/src/main/config/etc/jetty-logging.xml create mode 100644 jetty-util/src/main/java/META-INF/MANIFEST.MF create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/IO.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/IntrospectionUtil.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartWriter.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/SingletonList.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ajax/Continuation.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ajax/ContinuationSupport.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java create mode 100755 jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/ajax/WaitingContinuation.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/component/LifeCycle.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/thread/OldQueuedThreadPool.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/ArrayQueueTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/alphabet.txt create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/alt.zip create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/test.zip create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/thread/TimeoutTest.java create mode 100644 jetty-util/src/test/resources/org/eclipse/jetty/util/resource/one/1.txt create mode 100644 jetty-util/src/test/resources/org/eclipse/jetty/util/resource/one/dir/1.txt create mode 100644 jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/2.txt create mode 100644 jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/3.txt create mode 100644 jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/dir/3.txt create mode 100644 jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/1.txt create mode 100644 jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/2.txt create mode 100644 jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/dir/2.txt create mode 100644 jetty-util/src/test/resources/resource.txt create mode 100644 jetty-webapp/pom.xml create mode 100644 jetty-webapp/src/main/config/etc/webdefault.xml create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java create mode 100644 jetty-xml/pom.xml create mode 100644 jetty-xml/src/main/assembly/site-component.xml create mode 100644 jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java create mode 100644 jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java create mode 100644 jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd create mode 100644 jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java create mode 100644 jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java create mode 100644 jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml create mode 100644 pom.xml diff --git a/LICENSE-APACHE-2.0.txt b/LICENSE-APACHE-2.0.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/LICENSE-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-CONTRIBUTOR/CDDLv1.0.txt b/LICENSE-CONTRIBUTOR/CDDLv1.0.txt new file mode 100644 index 00000000000..1154e0aeec5 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/CDDLv1.0.txt @@ -0,0 +1,119 @@ +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + +1. Definitions. + +1.1. Contributor means each individual or entity that creates or contributes to the creation of Modifications. + +1.2. Contributor Version means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor. + +1.3. Covered Software means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof. + +1.4. Executable means the Covered Software in any form other than Source Code. + +1.5. Initial Developer means the individual or entity that first makes Original Software available under this License. + +1.6. Larger Work means a work which combines Covered Software or portions thereof with code not governed by the terms of this License. + +1.7. License means this document. + +1.8. Licensable means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein. + +1.9. Modifications means the Source Code and Executable form of any of the following: + +A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications; + +B. Any new file that contains any part of the Original Software or previous Modification; or + +C. Any new file that is contributed or otherwise made available under the terms of this License. + +1.10. Original Software means the Source Code and Executable form of computer software code that is originally released under this License. + +1.11. Patent Claims means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor. + +1.12. Source Code means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code. + +1.13. You (or Your) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, You includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, control means (a)áthe power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b)áownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. + +2. License Grants. + +2.1. The Initial Developer Grant. +Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license: +(a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and +(b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof). +(c) The licenses granted in Sectionsá2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License. +(d) Notwithstanding Sectioná2.1(b) above, no patent license is granted: (1)áfor code that You delete from the Original Software, or (2)áfor infringements caused by: (i)áthe modification of the Original Software, or (ii)áthe combination of the Original Software with other software or devices. + +2.2. Contributor Grant. +Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: +(a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and +(b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1)áModifications made by that Contributor (or portions thereof); and (2)áthe combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination). +(c) The licenses granted in Sectionsá2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party. +(d) Notwithstanding Sectioná2.2(b) above, no patent license is granted: (1)áfor any code that Contributor has deleted from the Contributor Version; (2)áfor infringements caused by: (i)áthird party modifications of Contributor Version, or (ii)áthe combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3)áunder Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor. + +3. Distribution Obligations. + +3.1. Availability of Source Code. + +Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange. + +3.2. Modifications. + +The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License. + +3.3. Required Notices. +You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer. + +3.4. Application of Additional Terms. +You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer. + +3.5. Distribution of Executable Versions. +You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipients rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer. + +3.6. Larger Works. +You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software. + +4. Versions of the License. + +4.1. New Versions. +Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License. + +4.2. Effect of New Versions. + +You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward. +4.3. Modified Versions. + +When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a)árename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b)áotherwise make it clear that the license contains terms which differ from this License. + +5. DISCLAIMER OF WARRANTY. + +COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +6. TERMINATION. + +6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive. + +6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as Participant) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sectionsá2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant. + +6.3. In the event of termination under Sectionsá6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + +UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTYS NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + +The Covered Software is a commercial item, as that term is defined in 48áC.F.R.á2.101 (Oct. 1995), consisting of commercial computer software (as that term is defined at 48 C.F.R. á252.227-7014(a)(1)) and commercial computer software documentation as such terms are used in 48áC.F.R.á12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License. + +9. MISCELLANEOUS. + +This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdictions conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + +As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) +The GlassFish code released under the CDDL shall be governed by the laws of the State of California (excluding conflict-of-law provisions). Any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California and the state courts of the State of California, with venue lying in Santa Clara County, California. + + + diff --git a/LICENSE-CONTRIBUTOR/ccla-exist.pdf b/LICENSE-CONTRIBUTOR/ccla-exist.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f9a6889d1e16b26410aaa2d256e386267b18fa5d GIT binary patch literal 431935 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�$Oi?P*q=_>-@NKn-(gJkKo#%ng4XwpF_+ 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%0Ac;1lZWHJ*#F>v{cC~NVE+a_FQD=7Bt1q!YyKC| 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

$ufA5PHm;-aJ6(qVU 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 z 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 R|C|l_#xlo`+q&Dy{2wp)9CH8w literal 0 HcmV?d00001 diff --git a/LICENSE-CONTRIBUTOR/ccla-simulalabs.txt b/LICENSE-CONTRIBUTOR/ccla-simulalabs.txt new file mode 100644 index 00000000000..0d85d0c7e2b --- /dev/null +++ b/LICENSE-CONTRIBUTOR/ccla-simulalabs.txt @@ -0,0 +1,157 @@ +Jetty Project +Corporate Contributor License Agreement V1.1 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay Consulting +Pty. Ltd. Australia ("MortBay"). In order to clarify the intellectual +property license granted with Contributions from any person or entity, +MortBay must have a Contributor License Agreement ("CLA") that has been +signed by each Contributor, indicating agreement to the license terms +below. This license is for your protection as a Contributor as well as +the protection of MortBay and its users; it does not change your rights +to use your own Contributions for any other purpose. + +This version of the Agreement allows an entity (the "Corporation") to +submit Contributions to Mort Bay, to authorize Contributions submitted by +its designated employees to Mort Bay, and to grant copyright and patent +licenses thereto. + +If you have not already done so, please complete this agreement and +commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty at +LICENSES/ccla-CORPORATE-NAME.txt. If you do not have commit privilege to the +repository, please email the file to eclipse@eclipse.com. If possible, +digitally sign the committed file, otherwise send a signed Agreement +to MortBay. + +Each developer covered by this agreement should have their name appended +the Schedule A and the copy commited to LICENSES/ccla-CORPORATE-NAME.txt +using their authenticated codehaus ssh login. If possible, digitally sign +the committed file, otherwise send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Corporation name: Simula Labs, Inc. + Mailing Address: 4676 Admiralty Way, Suite 520 + Marina Del Rey, CA 90292 + + Point of Contact: + Full name: Gordon King + E-Mail: gordon.king@simulalabs.com + Fax: +1 800 822 0471 + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement with + MortBay. For legal entities, the entity making a Contribution and all + other entities that control, are controlled by, or are under common + control with that entity are considered to be a single Contributor. For + the purposes of this definition, "control" means (i) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (ii) ownership of fifty percent + (50%) or more of the outstanding shares, or (iii) beneficial ownership + of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion in, or + documentation of, any of the products owned or managed by MortBay (the + "Work"). For the purposes of this definition, "submitted" means any + form of electronic, verbal, or written communication sent to MortBay + or its representatives, including but not limited to communication + on electronic mailing lists, source code control systems, and issue + tracking systems that are managed by, or on behalf of, MortBay for + the purpose of discussing and improving the Work, but excluding + communication that is conspicuously marked or otherwise designated + in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions + of this Agreement, You hereby grant to MortBay and to recipients of + software distributed by MortBay a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable copyright license to reproduce, + prepare derivative works of, publicly display, publicly perform, + sublicense, and distribute Your Contributions and such derivative + works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to recipients of + software distributed by MortBay a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable (except as stated in this section) + patent license to make, have made, use, offer to sell, sell, import, + and otherwise transfer the Work, where such license applies only to + those patent claims licensable by You that are necessarily infringed by + Your Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) were submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for that + Contribution or Work shall terminate as of the date such litigation + is filed. + +4. You represent that You are legally entitled to grant the above + license. You represent further that each employee of the Corporation + designated on Schedule A below (or in a subsequent written modification + to that Schedule) is authorized to submit Contributions on behalf of + the Corporation. + +5. You represent that each of Your Contributions is Your original creation + (see section 7 for submissions on behalf of others). You represent + that Your Contribution submissions include complete details of any + third-party license or other restriction (including, but not limited + to, related patents and trademarks) of which you are personally aware + and which are associated with any part of Your Contributions. + +6. You are not expected to provide support for Your Contributions, except + to the extent You desire to provide support. You may provide support + for free, for a fee, or not at all. Unless required by applicable + law or agreed to in writing, You provide Your Contributions on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied, including, without limitation, any warranties or + conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS + FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any Contribution, + identifying the complete details of its source and of any license or + other restriction (including, but not limited to, related patents, + trademarks, and license agreements) of which you are personally + aware, and conspicuously marking the work as "Submitted on behalf of + a third-party: [named here]". + +8. It is your responsibility to notify MortBay when any change is required + to the list of designated employees authorized to submit Contributions + on behalf of the Corporation, or to the Corporation's Point of Contact + with MortBay. + +Date: + +Signature: + +Name: Gordon King + +Positions: Chief Operational Officer + + + +Schedule A + + Name Date added + + Simone Bordet 12 September 2006 + + ______________________________________ ________________ + + ______________________________________ ________________ + diff --git a/LICENSE-CONTRIBUTOR/ccla-template.txt b/LICENSE-CONTRIBUTOR/ccla-template.txt new file mode 100644 index 00000000000..0ba4d66aee5 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/ccla-template.txt @@ -0,0 +1,176 @@ +Jetty Project +Corporate Contributor License Agreement V1.1 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay Consulting +Pty. Ltd. Australia ("MortBay"). In order to clarify the intellectual +property license granted with Contributions from any person or entity, +MortBay must have a Contributor License Agreement ("CLA") that has been +signed by each Contributor, indicating agreement to the license terms +below. This license is for your protection as a Contributor as well as +the protection of MortBay and its users; it does not change your rights +to use your own Contributions for any other purpose. + +This version of the Agreement allows an entity (the "Corporation") to +submit Contributions to Mort Bay, to authorize Contributions submitted by +its designated employees to Mort Bay, and to grant copyright and patent +licenses thereto. + +If you have not already done so, please complete this agreement and +commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty at +LICENSES/ccla-CORPORATE-NAME.txt. If you do not have commit privilege to the +repository, please email the file to eclipse@eclipse.com. If possible, +digitally sign the committed file, otherwise send a signed Agreement +to MortBay. + +Each developer covered by this agreement should have their name appended +the Schedule A and the copy commited to LICENSES/ccla-CORPORATE-NAME.txt +using their authenticated codehaus ssh login. If possible, digitally sign +the committed file, otherwise send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Corporation name: + Mailing Address: + + Point of Contact: + Full name: + E-Mail: + Fax: + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement with + MortBay. For legal entities, the entity making a Contribution and all + other entities that control, are controlled by, or are under common + control with that entity are considered to be a single Contributor. For + the purposes of this definition, "control" means (i) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (ii) ownership of fifty percent + (50%) or more of the outstanding shares, or (iii) beneficial ownership + of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion in, or + documentation of, any of the products owned or managed by MortBay (the + "Work"). For the purposes of this definition, "submitted" means any + form of electronic, verbal, or written communication sent to MortBay + or its representatives, including but not limited to communication + on electronic mailing lists, source code control systems, and issue + tracking systems that are managed by, or on behalf of, MortBay for + the purpose of discussing and improving the Work, but excluding + communication that is conspicuously marked or otherwise designated + in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions + of this Agreement, You hereby grant to MortBay and to recipients of + software distributed by MortBay a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable copyright license to reproduce, + prepare derivative works of, publicly display, publicly perform, + sublicense, and distribute Your Contributions and such derivative + works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to recipients of + software distributed by MortBay a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable (except as stated in this section) + patent license to make, have made, use, offer to sell, sell, import, + and otherwise transfer the Work, where such license applies only to + those patent claims licensable by You that are necessarily infringed by + Your Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) were submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for that + Contribution or Work shall terminate as of the date such litigation + is filed. + +4. You represent that You are legally entitled to grant the above + license. You represent further that each employee of the Corporation + designated on Schedule A below (or in a subsequent written modification + to that Schedule) is authorized to submit Contributions on behalf of + the Corporation. + +5. You represent that each of Your Contributions is Your original creation + (see section 7 for submissions on behalf of others). You represent + that Your Contribution submissions include complete details of any + third-party license or other restriction (including, but not limited + to, related patents and trademarks) of which you are personally aware + and which are associated with any part of Your Contributions. + +6. You are not expected to provide support for Your Contributions, except + to the extent You desire to provide support. You may provide support + for free, for a fee, or not at all. Unless required by applicable + law or agreed to in writing, You provide Your Contributions on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied, including, without limitation, any warranties or + conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS + FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any Contribution, + identifying the complete details of its source and of any license or + other restriction (including, but not limited to, related patents, + trademarks, and license agreements) of which you are personally + aware, and conspicuously marking the work as "Submitted on behalf of + a third-party: [named here]". + +8. It is your responsibility to notify MortBay when any change is required + to the list of designated employees authorized to submit Contributions + on behalf of the Corporation, or to the Corporation's Point of Contact + with MortBay. + +Date: + +Signature: + +Name: + +Positions: + + + +Schedule A + + Name Date added + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + + ______________________________________ ________________ + diff --git a/LICENSE-CONTRIBUTOR/cla-djencks.txt b/LICENSE-CONTRIBUTOR/cla-djencks.txt new file mode 100644 index 00000000000..8af1b918161 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-djencks.txt @@ -0,0 +1,141 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: David Jencks + E-Mail: david_jencks@yahoo.com + Mailing Address: 2215 SE 39th Ave, Portland OR 97214 USA + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: 27 June 2008 +Please sign: David Jencks + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.7 (Darwin) + +iD8DBQFIZT2ToF6+5lbz4BsRAs3wAJ9puXC26Nr8nhFvTZ9oNwxDFV/DVACgnC8O +VFUWPZrfLOJesKa0/rYNJlM= +=jC7I +-----END PGP SIGNATURE----- diff --git a/LICENSE-CONTRIBUTOR/cla-gregw.txt b/LICENSE-CONTRIBUTOR/cla-gregw.txt new file mode 100644 index 00000000000..c9f40fc49de --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-gregw.txt @@ -0,0 +1,141 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Gregory John Wilkins + E-Mail: gregw@eclipse.com + Mailing Address: 62 Church St. Balmain, NSW 2041, Australia + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: +Please sign: + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +iD8DBQFEEaStXR9WPTAwnLARAjsNAJ4jBB6wCEqucFljGge7yrAMSrFv/gCgoMC+ +5hdry6ZjXRcUhQEyNz2F/T4= +=I4Co +-----END PGP SIGNATURE----- diff --git a/LICENSE-CONTRIBUTOR/cla-janb.txt b/LICENSE-CONTRIBUTOR/cla-janb.txt new file mode 100644 index 00000000000..f4dbd7a37ba --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-janb.txt @@ -0,0 +1,139 @@ +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Jan Bartel + E-Mail: janb@eclipse.com + Mailing Address: 62 Church St Balmain NSW 2041 Australia + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: 10 March 2006 +Please sign: PGP + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +iD8DBQFDjMI6J97Uv2IW248RAmGNAJ9/krpkiYJRrJTMXVkL3cdnVvfU+QCfYFEh +pN0h9U/xdFTRMFsXYFHQeN4= +=24Hd +-----END PGP SIGNATURE----- + diff --git a/LICENSE-CONTRIBUTOR/cla-jesse.txt b/LICENSE-CONTRIBUTOR/cla-jesse.txt new file mode 100644 index 00000000000..f38ac0c8229 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-jesse.txt @@ -0,0 +1,141 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Jesse C. McConnell + E-Mail: jmcconnell@apache.org + Mailing Address: 7717 S 167th Street, Omaha, Ne. 68136 + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: December 19 2007 +Please sign: GPG + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.6 (GNU/Linux) + +iD8DBQFHacMO+jg6q+ULyBMRAky4AJ9CdNKsmg8n2aFcpQAvcEPXxEjIJACgrvjM +C/W/GuQFfCJJykkL2jd9/Ag= +=ufUh +-----END PGP SIGNATURE----- diff --git a/LICENSE-CONTRIBUTOR/cla-jfarcand.txt b/LICENSE-CONTRIBUTOR/cla-jfarcand.txt new file mode 100644 index 00000000000..df8b908551e --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-jfarcand.txt @@ -0,0 +1,142 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Jeanfrancois Arcand + E-Mail: jfarcand@apache.org + Mailing Address: 1800 McGill College Avenue, Suite 800, H3A 3J6 Montreal, + Quebec (Canada) + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: /28/08/06 +Please sign: + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.0.7 (GNU/Linux) + +iD8DBQFE85cgaq9Frj/CIrIRAmuJAKCFgi4W0UOH8IUn+SV6PBHRF3BnLgCcDqqC +Zokttk0bTHfwaa5TtxQbScw= +=N/w/ +-----END PGP SIGNATURE----- diff --git a/LICENSE-CONTRIBUTOR/cla-jstrachan.txt b/LICENSE-CONTRIBUTOR/cla-jstrachan.txt new file mode 100644 index 00000000000..e3508a6aa17 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-jstrachan.txt @@ -0,0 +1,143 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: James Strachan + E-Mail: jstrachan@apache.org + Mailing Address: 1A Leigh Road, London, UK, N5 1ST + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In +return, MortBay shall not use Your Contributions in a way that +is contrary to the public benefit or inconsistent with its nonprofit +status and bylaws in effect at the time of the Contribution. Except +for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, +and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: January 30th 2006 +Please sign: GPG + + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.1 (Darwin) + +iD8DBQFD37/4dL6IZr4c+6kRAtsIAJ41tfd3lj4OM6sIMfJfTOdYdT1bxwCdGgWv +8sfMxEDZquIqhVbfZU2c76U= +=8WW7 +-----END PGP SIGNATURE----- diff --git a/LICENSE-CONTRIBUTOR/cla-jules.txt b/LICENSE-CONTRIBUTOR/cla-jules.txt new file mode 100644 index 00000000000..c3911480668 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-jules.txt @@ -0,0 +1,141 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Julian Anthony Fox Gosnell + E-Mail: jules@coredevelopers.net + Mailing Address: 2, Tannery Cottages, Tannery Lane, Bramley, Surrey, GU5 0AB, UK. + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: 10th March 2006 +Please sign: + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2.1 (GNU/Linux) + +iD8DBQFEEbh9SoT4b97cQk4RAnCMAKCuNGYLHa6n/Ot3GEdwCCLeQxsMPACdEhnE +I/stizRWWZZkeLbcglzdQCE= +=piHm +-----END PGP SIGNATURE----- diff --git a/LICENSE-CONTRIBUTOR/cla-ngonzalez.txt b/LICENSE-CONTRIBUTOR/cla-ngonzalez.txt new file mode 100644 index 00000000000..1954fd025f0 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-ngonzalez.txt @@ -0,0 +1,141 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Nicanor Gonzalez + E-Mail: ngonzalez@exist.com + Mailing Address: 37 TwinHill St., New Manila Rolling Hills, Q.C., Philippines + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: July 14, 2006 +Please sign: + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.4 (MingW32) + +iD8DBQFEt1ZxHR/ESK2w6H8RApbOAJ9c1eooNr2oN59WZVitJExGJjUvKgCfaKji +6etDJ6AUj0jTuSl59hUsWMQ= +=HmqH +-----END PGP SIGNATURE----- diff --git a/LICENSE-CONTRIBUTOR/cla-sbordet.txt b/LICENSE-CONTRIBUTOR/cla-sbordet.txt new file mode 100644 index 00000000000..af7f827bb9f --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-sbordet.txt @@ -0,0 +1,140 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Simone Bordet + E-Mail: simone.bordet@gmail.com + Mailing Address: + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: 8 January 2007 +Please sign: +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.3 (GNU/Linux) + +iD8DBQFFoniQJVhlFus9dGQRAmJmAJwL5y1loonhVQIICsparvjHMQuwqwCgiZFy +LBDVaad1bJ1v1EHY901kPcg= +=6rqm +-----END PGP SIGNATURE----- diff --git a/LICENSE-CONTRIBUTOR/cla-template.txt b/LICENSE-CONTRIBUTOR/cla-template.txt new file mode 100644 index 00000000000..6d99754cb59 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-template.txt @@ -0,0 +1,131 @@ +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: + E-Mail: + Mailing Address: + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: +Please sign: + diff --git a/LICENSE-CONTRIBUTOR/cla-tvernum.txt b/LICENSE-CONTRIBUTOR/cla-tvernum.txt new file mode 100644 index 00000000000..46c5cf9b221 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-tvernum.txt @@ -0,0 +1,141 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Timothy Philip Vernum + E-Mail: tim@adjective.org + Mailing Address: 7/9-11 Cook St, Sutherland, NSW 2232, Australia + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: +Please sign: + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.1 (Darwin) + +iD8DBQFEnjfA9nwdoZUd8/ERArwdAJ4lzyXEi4zSlIiJwEAxknGPhzMRswCfRsdI +RUIoI0BYmYpaETSqxt2oLFU= +=Tr57 +-----END PGP SIGNATURE----- diff --git a/LICENSE-ECLIPSE-1.0.html b/LICENSE-ECLIPSE-1.0.html new file mode 100644 index 00000000000..9320c9f37cf --- /dev/null +++ b/LICENSE-ECLIPSE-1.0.html @@ -0,0 +1,320 @@ + + + + + + + + +Eclipse Public License - Version 1.0 + + + + +

+ +

Eclipse Public License - v 1.0 +

+ +

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER +THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, +REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE +OF THIS AGREEMENT.

+ +

1. DEFINITIONS

+ +

"Contribution" means:

+ +

a) +in the case of the initial Contributor, the initial code and documentation +distributed under this Agreement, and
+b) in the case of each subsequent Contributor:

+ +

i) +changes to the Program, and

+ +

ii) +additions to the Program;

+ +

where +such changes and/or additions to the Program originate from and are distributed +by that particular Contributor. A Contribution 'originates' from a Contributor +if it was added to the Program by such Contributor itself or anyone acting on +such Contributor's behalf. Contributions do not include additions to the +Program which: (i) are separate modules of software distributed in conjunction +with the Program under their own license agreement, and (ii) are not derivative +works of the Program.

+ +

"Contributor" means any person or +entity that distributes the Program.

+ +

"Licensed Patents " mean patent +claims licensable by a Contributor which are necessarily infringed by the use +or sale of its Contribution alone or when combined with the Program.

+ +

"Program" means the Contributions +distributed in accordance with this Agreement.

+ +

"Recipient" means anyone who +receives the Program under this Agreement, including all Contributors.

+ +

2. GRANT OF RIGHTS

+ +

a) +Subject to the terms of this Agreement, each Contributor hereby grants Recipient +a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly +display, publicly perform, distribute and sublicense the Contribution of such +Contributor, if any, and such derivative works, in source code and object code +form.

+ +

b) +Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free +patent license under Licensed Patents to make, use, sell, offer to sell, import +and otherwise transfer the Contribution of such Contributor, if any, in source +code and object code form. This patent license shall apply to the combination +of the Contribution and the Program if, at the time the Contribution is added +by the Contributor, such addition of the Contribution causes such combination +to be covered by the Licensed Patents. The patent license shall not apply to +any other combinations which include the Contribution. No hardware per se is +licensed hereunder.

+ +

c) +Recipient understands that although each Contributor grants the licenses to its +Contributions set forth herein, no assurances are provided by any Contributor +that the Program does not infringe the patent or other intellectual property +rights of any other entity. Each Contributor disclaims any liability to Recipient +for claims brought by any other entity based on infringement of intellectual +property rights or otherwise. As a condition to exercising the rights and +licenses granted hereunder, each Recipient hereby assumes sole responsibility +to secure any other intellectual property rights needed, if any. For example, +if a third party patent license is required to allow Recipient to distribute +the Program, it is Recipient's responsibility to acquire that license before +distributing the Program.

+ +

d) +Each Contributor represents that to its knowledge it has sufficient copyright +rights in its Contribution, if any, to grant the copyright license set forth in +this Agreement.

+ +

3. REQUIREMENTS

+ +

A Contributor may choose to distribute the +Program in object code form under its own license agreement, provided that: +

+ +

a) +it complies with the terms and conditions of this Agreement; and

+ +

b) +its license agreement:

+ +

i) +effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose;

+ +

ii) +effectively excludes on behalf of all Contributors all liability for damages, +including direct, indirect, special, incidental and consequential damages, such +as lost profits;

+ +

iii) +states that any provisions which differ from this Agreement are offered by that +Contributor alone and not by any other party; and

+ +

iv) +states that source code for the Program is available from such Contributor, and +informs licensees how to obtain it in a reasonable manner on or through a +medium customarily used for software exchange.

+ +

When the Program is made available in source +code form:

+ +

a) +it must be made available under this Agreement; and

+ +

b) a +copy of this Agreement must be included with each copy of the Program.

+ +

Contributors may not remove or alter any +copyright notices contained within the Program.

+ +

Each Contributor must identify itself as the +originator of its Contribution, if any, in a manner that reasonably allows +subsequent Recipients to identify the originator of the Contribution.

+ +

4. COMMERCIAL DISTRIBUTION

+ +

Commercial distributors of software may +accept certain responsibilities with respect to end users, business partners +and the like. While this license is intended to facilitate the commercial use +of the Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes the +Program in a commercial product offering, such Contributor ("Commercial +Contributor") hereby agrees to defend and indemnify every other +Contributor ("Indemnified Contributor") against any losses, damages and +costs (collectively "Losses") arising from claims, lawsuits and other +legal actions brought by a third party against the Indemnified Contributor to +the extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor +to control, and cooperate with the Commercial Contributor in, the defense and +any related settlement negotiations. The Indemnified Contributor may participate +in any such claim at its own expense.

+ +

For example, a Contributor might include the +Program in a commercial product offering, Product X. That Contributor is then a +Commercial Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance claims and +warranties are such Commercial Contributor's responsibility alone. Under this +section, the Commercial Contributor would have to defend claims against the +other Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages.

+ +

5. NO WARRANTY

+ +

EXCEPT AS EXPRESSLY SET FORTH IN THIS +AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, +WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, +MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and distributing the +Program and assumes all risks associated with its exercise of rights under this +Agreement , including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs or +equipment, and unavailability or interruption of operations.

+ +

6. DISCLAIMER OF LIABILITY

+ +

EXCEPT AS EXPRESSLY SET FORTH IN THIS +AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF +THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES.

+ +

7. GENERAL

+ +

If any provision of this Agreement is invalid +or unenforceable under applicable law, it shall not affect the validity or +enforceability of the remainder of the terms of this Agreement, and without +further action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable.

+ +

If Recipient institutes patent litigation +against any entity (including a cross-claim or counterclaim in a lawsuit) +alleging that the Program itself (excluding combinations of the Program with +other software or hardware) infringes such Recipient's patent(s), then such +Recipient's rights granted under Section 2(b) shall terminate as of the date +such litigation is filed.

+ +

All Recipient's rights under this Agreement +shall terminate if it fails to comply with any of the material terms or +conditions of this Agreement and does not cure such failure in a reasonable +period of time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use and +distribution of the Program as soon as reasonably practicable. However, +Recipient's obligations under this Agreement and any licenses granted by +Recipient relating to the Program shall continue and survive.

+ +

Everyone is permitted to copy and distribute +copies of this Agreement, but in order to avoid inconsistency the Agreement is +copyrighted and may only be modified in the following manner. The Agreement +Steward reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement Steward has +the right to modify this Agreement. The Eclipse Foundation is the initial +Agreement Steward. The Eclipse Foundation may assign the responsibility to +serve as the Agreement Steward to a suitable separate entity. Each new version +of the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version of +the Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to +the intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved.

+ +

This Agreement is governed by the laws of the +State of New York and the intellectual property laws of the United States of +America. No party to this Agreement will bring a legal action under this +Agreement more than one year after the cause of action arose. Each party waives +its rights to a jury trial in any resulting litigation.

+ +

 

+ +
+ + \ No newline at end of file diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 00000000000..8cda6d5719d --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,21 @@ +============================================================== + Jetty Web Container + Copyright 1995-2009 Mort Bay Consulting Pty Ltd +============================================================== + +The Jetty Web Container is Copyright Mort Bay Consulting Pty Ltd +unless otherwise noted. It is dual licensed under the apache 2.0 +license and eclipse 1.0 license. Jetty may be distributed under +either license. + +The javax.servlet package used was sourced from the Apache +Software Foundation and is distributed under the apache 2.0 +license. + +The UnixCrypt.java code implements the one way cryptography used by +Unix systems for simple password protection. Copyright 1996 Aki Yoshida, +modified April 2001 by Iris Van den Broeke, Daniel Deville. +Permission to use, copy, modify and distribute UnixCrypt +for non-commercial or commercial purposes and without fee is +granted provided that the copyright notice appears in all copies. + diff --git a/README.txt b/README.txt new file mode 100644 index 00000000000..23fe075f829 --- /dev/null +++ b/README.txt @@ -0,0 +1,17 @@ + +This is the Jetty 7 HTTP server and servlet container. + +For more information about Jetty, please see the Jetty wiki: + + http://docs.codehaus.org/display/JETTY/ + + +BUILDING JETTY +============== + +Jetty uses maven 2 as its build system. Maven will fetch +the dependancies, build the server and assemble a runnable +version: + + mvn install + diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 00000000000..af4eb9bed3b --- /dev/null +++ b/VERSION.txt @@ -0,0 +1,3968 @@ +jetty-7.0.0.incubator0-SNAPSHOT + + JETTY-496 Support inetd/xinetd through use of System.inheritedChannel() + + JETTY-540 Merged 3.0 Public Review changes + + JETTY-567 Delay in initial TLS Handshake With FireFox 3 beta5 and SslSelectChannelConnector + + JETTY-600 Automated tests of WADI integration + upgrade to WADI 2.0 + + JETTY-691 System.getProperty() calls ... wrap them in doPrivileged + + JETTY-713 Expose additional AbstractConnector methods via MBean + + JETTY-731 Completed DeliverListener for cometd + + JETTY-748 RandomAccessFileBuffer for hadoop optimization + + JETTY-748 Reduced retries on async writes + + JETTY-749 Improved ArrayQueue + + JETTY-765 ensure stop mojo works for all execution phases + + JETTY-774 Improved caching of mime types with charsets + + JETTY-775 AbstractSessionTest remove timing related test + + JETTY-778 handle granular windows timer in lifecycle test + + JETTY-779 Fixed line feed in request log + + JETTY-781 Add "mvn jetty:deploy-war" for deploying a pre-assembled war + + JETTY-782 Implement interval advice for BayeuxClient + + JETTY-783 Update jetty self-signed certificate + + JETTY-784 TerracottaSessionManager leaks sessions scavenged in other nodes + + JETTY-786 Allow DataSourceUserRealm to create tables + + JETTY-787 Handle MSIE7 mixed encoding + + JETTY-788 Fix jotm for scoped jndi naming + + JETTY-790 WaitingContinuations can change mutex if not pending + + JETTY-792 TerracottaSessionManager does not unlock new session with requested id + + JETTY-793 Fixed DataCache millisecond rounding + + JETTY-794 WADI integration tests fail intermittently. + + JETTY-795 NullPointerException in SocketConnector.java + + JETTY-801 Bring back 2 arg EnvEntry constructor + + JETTY-802 Modify the default error pages to make association with Jetty clearer + + JETTY-804 HttpClient timeout does not always work + + JETTY-805 Fix jetty-jaas.xml for new UserRealm package + + JETTY-806 Timeout related Deadlocks in HTTP Client + + JETTY-807 HttpTester to handle charsets + + JETTY-808 cometd client demo run.sh + + JETTY-809 Need a way to customize WEB-INF/lib file extensions that are added to the classpath + + JETTY-811 Allow configuration of system properties for the maven plugin using a file + + JETTY-813 Simplify NCSARequestLog.java + + JETTY-814 Add org.eclipse.jetty.client.Address.toString() + + JETTY-816 Implement reconnect on java bayeux client + + JETTY-817 Aborted SSL connections may cause jetty to hang with full cpu + + JETTY-818 Support javax.servlet.request.ssl_session_id + + JETTY-821 Allow lazy loading of persistent sessions + + JETTY-822 Commit when autocommit=true causes error with mysql + + JETTY-823 Extend start.config profiles + + JETTY-824 Access to inbound byte statistics + + JETTY-825 URL decoding of spaces (+) fails for encoding not utf8 + + JETTY-830 Add ability to reserve connections on http client + + JETTY-831 Add ability to stop java bayeux client + + JETTY-832 More UrlDecoded handling in relation to JETTY-825 + + JETTY-834 Configure DTD does not allow children + + JETTY-837 Response headers set via filter are ignored for static resources + + JETTY-840 add default mime types to *.htc and *.pps + + JETTY-841 Duplicate messages when sending private message to yourself with cometd chat demo + + JETTY-842 NPE in jetty client when no path component + + JETTY-843 META-INF/MANIFEST.MF is not present in unpacked webapp + + JETTY-844 Replace reflection with direct invocation in Slf4jLog + + JETTY-848 Temporary folder not fully cleanup after stop (via Sweeper) + + JETTY-854 JNDI scope does not work with applications in a .war + + JETTY-859 MultiPartFilter ignores the query string parameters + + JETTY-861 switched buffer pools to ThreadLocal implementation + + JETTY-862 EncodedHttpURI ignores given encoding in constructor + + JETTY-866 jetty-client test case fix + + JETTY-869 NCSARequestLog locale config + + JETTY-870 NullPointerException in Response when performing redirect to wrong relative URL + + JETTY-871 jetty-client expires() NPE race condition fixed + + JETTY-876 Added new BlockingArrayQueue and new QueuedThreadPool + + JETTY-894 Add android .apk to mime types + + JETTY-897 Remove swing dependency in GzipFilter + + JETTY-898 Allow jetty debs to start with custom java args provided by users + + JETTY-899 Standardize location and build process for configuration files which go into etc + + JETTY-890 merge jaspi branch to trunk + + JETTY-909 Update useragents cache + + JETTY-917 Change for JETTY-811 breaks systemProperties config parameter in maven-jetty-plugin + + JETTY-922 Fixed NPE on getRemoteHost when socket closed + + JETTY-923 Client supports attributes + + JETTY-926 default location for generatedClasses of jspc plugin is incorrect + + JETTY-939 NPE in AbstractConfiguration.callPreDestroyCallbacks + + JETTY-938 Deadlock in the TerracottaSessionManager + + JETTY-946 Redeploys with maven jetty plugin of webapps with overlays don't work + + JETTY-950 Fix double-printing of request URI in request log + + JETTY-953 SSL keystore file input stream is not being closed directly + + JETTY-956 SslSelectChannelConnector - password should be the default value of keyPassword if not specified + + moved to org.eclipse packages + +jetty-6.1.15 4 March 2009 + + JETTY-931 Fix issue with jetty-rewrite.xml + + JETTY-934 fixed stop/start of Bayeux Client + + JETTY-938 Deadlock in the TerracottaSessionManager + + JETTY-939 NPE in AbstractConfiguration.callPreDestroyCallbacks + + JETTY-923 BayeuxClient uses message pools to reduce memory footprint + + JETTY-924 Improved BayeuxClient disconnect handling + + JETTY-925 Lazy bayeux messages + + JETTY-926 default location for generatedClasses of jspc plugin is incorrect + +jetty-6.1.15 2 March 2009 + + JETTY-923 BayeuxClient uses message pools to reduce memory footprint + + JETTY-924 Improved BayeuxClient disconnect handling + + JETTY-925 Lazy bayeux messages + + JETTY-926 default location for generatedClasses of jspc plugin is incorrect + +jetty-6.1.15.rc4 19 February 2009 + + JETTY-496 Support inetd/xinetd through use of System.inheritedChannel() + + JETTY-713 Expose additional AbstractConnector methods via MBean + + JETTY-749 Improved ack extension + + JETTY-811 Allow configuration of system properties for the maven plugin using a file + + JETTY-840 add default mime types to *.htc and *.pps + + JETTY-848 Temporary folder not fully cleanup after stop (via Sweeper) + + JETTY-872 Handshake handler calls wrong extension callback + + JETTY-879 Support extra properties in jQuery comet implementation + + JETTY-802 Modify the default error pages to make association with Jetty clearer + + JETTY-869 NCSARequestLog locale config + + JETTY-870 NullPointerException in Response when performing redirect to wrong relative URL + + JETTY-878 Removed printStackTrace from WaitingContinuation + + JETTY-882 ChannelBayeuxListener called too many times + + JETTY-884 Use hashcode for threadpool ID + + JETTY-815 Add comet support to jQuery javascript library + + JETTY-887 Split configuration and handshaking in jquery comet + + JETTY-888 Fix abort in case of multiple outstanding connections + + JETTY-894 Add android .apk to mime types + + JETTY-898 Allow jetty debs to start with custom java args provided by users + + JETTY-909 Update useragents cache + + +jetty-6.1.15.rc3 28 January 2009 + + JETTY-691 System.getProperty() calls ... wrap them in doPrivileged + + JETTY-844 Replace reflection with direct invocation in Slf4jLog + + JETTY-861 switched buffer pools to ThreadLocal implementation + + JETTY-866 jetty-client test case fix + +jetty-6.1.15.rc2 23 January 2009 + + adjustment to jetty-client assembly packaging + + JETTY-567 Delay in initial TLS Handshake With FireFox 3 beta5 and SslSelectChannelConnector + +jetty-6.1.15.pre0 20 January 2009 + + JETTY-600 Automated tests of WADI integration + upgrade to WADI 2.0 + + JETTY-749 Reliable message delivery + + JETTY-794 WADI integration tests fail intermittently. + + JETTY-781 Add "mvn jetty:deploy-war" for deploying a pre-assembled war + + JETTY-795 NullPointerException in SocketConnector.java + + JETTY-798 Jboss session manager incompatible with LifeCycle.Listener + + JETTY-801 Bring back 2 arg EnvEntry constructor + + JETTY-802 Modify the default error pages to make association with Jetty very clear + + JETTY-804 HttpClient timeout does not always work + + JETTY-806 Timeout related Deadlocks in HTTP Client + + JETTY-807 HttpTester to handle charsets + + JETTY-808 cometd client demo run.sh + + JETTY-809 Need a way to customize WEB-INF/lib file extensions that are added to the classpath + + JETTY-814 Add org.eclipse.jetty.client.Address.toString() + + JETTY-816 Implement reconnect on java bayeux client + + JETTY-817 Aborted SSL connections may cause jetty to hang with full cpu + + JETTY-819 Jetty Plus no more jre 1.4 + + JETTY-821 Allow lazy loading of persistent sessions + + JETTY-824 Access to inbound byte statistics + + JETTY-825 URL decoding of spaces (+) fails for encoding not utf8 + + JETTY-827 Externalize servlet api + + JETTY-830 Add ability to reserve connections on http client + + JETTY-831 Add ability to stop java bayeux client + + JETTY-832 More UrlDecoded handling in relation to JETTY-825 + + JETTY-833 Update debian and rpm packages for new jsp-2.1-glassfish jars and servlet-api jar + + JETTY-834 Configure DTD does not allow children + + JETTY-837 Response headers set via filter are ignored for static resources + + JETTY-841 Duplicate messages when sending private message to yourself with cometd chat demo + + JETTY-842 NPE in jetty client when no path component + + JETTY-843 META-INF/MANIFEST.MF is not present in unpacked webapp + + JETTY-852 Ensure handshake and connect retried on failure for jquery-cometd + + JETTY-854 JNDI scope does not work with applications in a .war + + JETTY-855 jetty-client uber assembly support + + JETTY-858 ContentExchange provides bytes + + JETTY-859 MultiPartFilter ignores the query string parameters + + JETTY-862 EncodedHttpURI ignores given encoding in constructor + +jetty-6.1.14 14 November 2008 + + JETTY-630 jetty6-plus rpm is missing the jetty6-plus jar + + JETTY-748 Reduced flushing of large content + + JETTY-765 ensure stop mojo works for all execution phases + + JETTY-777 include util5 on the jetty debs + + JETTY-778 handle granular windows timer in lifecycle test + + JETTY-779 Fixed line feed in request log + + JETTY-782 Implement interval advice for BayeuxClient + + JETTY-783 Update jetty self-signed certificate + + JETTY-784 TerracottaSessionManager leaks sessions scavenged in other nodes + + JETTY-787 Handle MSIE7 mixed encoding + + JETTY-788 Fix jotm for new scoped jndi + + JETTY-790 WaitingContinuations can change mutex if not pending + + JETTY-791 Ensure jdk1.4 compatibility for jetty-6 + + JETTY-792 TerracottaSessionManager does not unlock new session with requested id + + JETTY-793 Fixed DataCache millisecond rounding + +jetty-6.1.12 4 November 2008 + + JETTY-731 Completed DeliverListener for cometd + + JETTY-772 Increased default threadpool size to 250 + + JETTY-774 Cached text/json content type + + JETTY-775 fix port of openspaces to jetty-6 + +jetty-7.0.0.pre5 30 Oct 2008 + + JETTY-766 Fix npe + + JETTY-767 Fixed SSL Client no progress handshake bug + + JETTY-768 Remove EnvEntry overloaded constructors + + JETTY-769 jquery example error + + JETTY-771 Ensure NamingEntryUtil is jdk1.4 compliant + + JETTY-772 Increased default threadpool size to 250 + +jetty-6.1.12.rc5 30 October 2008 + + JETTY-703 maxStopTimeMs added to QueuedThreadPool + + JETTY-762 improved QueuedThreadPool idle death handling + + JETTY-763 Fixed AJP13 constructor + + JETTY-766 Ensure SystemProperties set early on jetty-maven-plugin + + JETTY-767 Fixed SSL Client no progress handshake bug + + JETTY-768 Remove EnvEntry overloaded constructors + + JETTY-771 Ensure NamingEntryUtil jdk1.4 compliant + +jetty-7.0.0.pre4 28 Oct 2008 + + JETTY-241 Support for web application overlays in rapid application development (jetty:run) + + JETTY-319 improved passing of exception when webapp unavailable + + JETTY-331 SecureRandom hangs on systems with low entropy (connectors slow to start) + + JETTY-591 No server classes for jetty-web.xml + + JETTY-604 AbstractSession.setSessionURL + + JETTY-670 $JETTY_HOME/bin/jetty.sh not worked in Solaris, because of /usr/bin/which has no error-code + + JETTY-676 ResourceHandler doesn't support HTTP HEAD requests + + JETTY-677 GWT serialization issue + + JETTY-680 Can't configure the ResourceCollection with maven + + JETTY-681 JETTY-692 MultiPartFilter is slow for file uploads + + JETTY-682 Added listeners and queue methods to cometd + + JETTY-686 LifeCycle.Listener + + JETTY-687 Issue with servlet-mapping in dynamic servlet invoker + + JETTY-688 Cookie causes NumberFormatException + + JETTY-689 processing of non-servlet related annotations + + JETTY-690 Updated XBean dependencies to XBean version 3.4.3 and Spring 2.0.5. + + JETTY-696 jetty.sh restart not working + + JETTY-698 org.eclipse.resource.JarResource.extract does not close JarInputStream jin + + JETTY-699 Optimized cometd sending of 1 message to many many clients + + JETTY-700 unit test for unread request data + + JETTY-703 maxStopTimeMs added to QueuedThreadPool + + JETTY-708 allow 3 scopes for jndi resources: jvm, server or webapp + + JETTY-709 Jetty plugin's WebAppConfig configured properties gets overridden by AbstractJettyRunMojo even when already set + + JETTY-710 Worked around poor implementation of File.toURL() + + JETTY-711 DataSourceUserRealm implementation + + JETTY-712 HttpClient does not handle request complete after response complete + + JETTY-715 AJP Key size as Integer + + JETTY-716 Fixed NPE on empty cometd message + + JETTY-718 during ssl unwrap, return true if some bytes were read, even if underflow + + JETTY-720 fix HttpExchange.waitForStatus + + JETTY-721 Support wildcard in VirtualHosts configuration + + JETTY-723 jetty.sh does not check if TMP already is set + + JETTY-724 better handle EBCDIC default JVM encoding + + JETTY-728 Improve Terracotta integration and performances + + JETTY-730 Set SAX parse features to defaults + + JETTY-731 DeliverListener for cometd + + JETTY-732 Case Sensitive Basic Authentication Response Header Implementations + + JETTY-733 Expose ssl connectors with xbean + + JETTY-735 Wrong default jndi name on DataSourceUserRealm + + JETTY-736 Client Specific cometd advice + + JETTY-737 refactored jetty.jar into jetty, xml, security, ssl, webapp and deploy jars + + JETTY-738 If jetty.sh finds a pid file is does not check to see if a process with that pid is still running + + JETTY-739 Race in QueuedThreadPool + + JETTY-741 HttpClient connects slowly due to reverse address lookup by InetAddress.getHostName() + + JETTY-742 Private messages in cometd chat demo + + JETTY-747 Handle HttpClient exceptions better + + JETTY-755 Optimized HttpParser and buffers for few busy connections + + JETTY-757 Unhide JAAS classes + + JETTY-758 Update JSP to glassfish tag SJSAS-9_1_1-B51-18_Sept_2008 + + JETTY-759 Fixed JSON small negative real numbers + + JETTY-760 Handle wildcard VirtualHost and normalize hostname in ContextHandlerCollection + + JETTY-762 improved QueuedThreadPool idle death handling + + JETTY-763 Fixed AJP13 constructor + + JETTY-766 Ensure SystemProperties set early on jetty-maven-plugin + +jetty-6.1.12.rc4 21 October 2008 + + JETTY-319 improved passing of exception when webapp unavailable + + JETTY-729 Backport Terracotta integration to Jetty6.1 branch + + JETTY-744 Backport of JETTY-741: HttpClient connects slowly due to reverse address lookup by InetAddress.getHostName() + + JETTY-747 Handle exceptions better in HttpClient + + JETTY-755 Optimized HttpParser and buffers for few busy connections + + JETTY-758 Update JSP 2.1 to glassfish tag SJSAS-9_1_1-B51-18_Sept_2008 + + JETTY-759 Fixed JSON small negative real numbers + + JETTY-760 Handle wildcard VirtualHost and normalize hostname in ContextHandlerCollection + +jetty-6.1.12.rc3 10 October 2008 + + JETTY-241 Support for web application overlays in rapid application development (jetty:run) + + JETTY-686 LifeCycle.Listener + + JETTY-715 AJP key size + + JETTY-716 NPE for empty cometd message + + JETTY-718 during ssl unwrap, return true if some bytes were read, even if underflow + + JETTY-720 fix HttpExchange.waitForStatus + + JETTY-721 Support wildcard in VirtualHosts configuration + + JETTY-722 jndi related threadlocal not cleared after deploying webapp + + JETTY-723 jetty.sh does not check if TMP already is set + + JETTY-725 port JETTY-708 (jndi scoping) to jetty-6 + + JETTY-730 set SAX parser features to defaults + + JETTY-731 DeliverListener for cometd + + JETTY-732 Case Sensitive Basic Authentication Response Header Implementations + + JETTY-736 Client Specific cometd advice + + JETTY-738 If jetty.sh finds a pid file is does not check to see if a process with that pid is still running + + JETTY-739 Race in QueuedThreadPool + + JETTY-742 Private messages in cometd chat demo + +jetty-6.1.12rc2 12 September 2008 + + JETTY-282 Support manually-triggered reloading + + JETTY-331 SecureRandom hangs on systems with low entropy (connectors slow to startup) + + JETTY-591 No server classes for jetty-web.xml + + JETTY-670 $JETTY_HOME/bin/jetty.sh not worked in Solaris, because of /usr/bin/which has no error-code + + JETTY-671 Configure DTD does not allow children + + JETTY-672 Utf8StringBuffer doesn't properly handle null characters (char with byte value 0) + + JETTY-676 ResourceHandler doesn't support HTTP HEAD requests + + JETTY-677 GWT serialization issue + + JETTY-680 Can't configure the ResourceCollection with maven + + JETTY-681 JETTY-692 MultiPartFilter is slow for file uploads + + JETTY-682 Added listeners and queue methods to cometd + + JETTY-683 ResourceCollection works for jsp files but does not work for static resources under DefaultServlet + + JETTY-687 Issue with servlet-mapping in dynamic servlet invoker + + JETTY-688 Cookie causes NumberFormatException + + JETTY-696 ./jetty.sh restart not working + + JETTY-698 org.eclipse.resource.JarResource.extract does not close JarInputStream jin + + JETTY-699 Optimize cometd sending of 1 message to many many clients + + JETTY-709 Jetty plugin's WebAppConfig configured properties gets overridden by AbstractJettyRunMojo even when already set + + JETTY-710 Worked around poor implementation of File.toURL() + + JETTY-712 HttpClient does not handle request complete after response complete + + +jetty-7.0.0pre3 - 6 August 2008 + + Upgrade jsp 2.1 to SJSAS-9_1_02-B04-11_Apr_2008 + + JETTY-30 Externalize servlet-api to own project + + JETTY-182 Support setting explicit system classpath for jasper Jsr199JavaCompiler + + JETTY-319 Get unavailable exception and added startWithUnavailable option + + JETTY-381 JETTY-622 Multiple Web Application Source Directory + + JETTY-442 Accessors for mimeType on ResourceHandler + + JETTY-502 forward of an include should hide include attributes + + JETTY-562 RewriteHandler support for virtual hosts + + JETTY-563 JETTY-482 OpenRemoteServiceServlet for GWT1.5M2+ + + JETTY-564 Consider optionally importing org.apache.jasper.servlet + + JETTY-571 SelectChannelConnector throws Exception on close on Windows + + JETTY-608 Suspend/Resume/Complete request listeners + + JETTY-621 Improved LazyList javadoc + + JETTY-626 Null protect reading the dtd resource from classloader + + JETTY-628 Rewrite rule for rewriting scheme + + JETTY-629 Don't hold timeout lock during expiry call. + + JETTY-632 OSGi tags for Jetty client + + JETTY-633 Default form encoding 8859_1 rather than utf-8 + + JETTY-635 Correctly merge request parameters when doing forward + + JETTY-636 Separate lifeycle of jsp build + + JETTY-637 empty date headers throw IllegalArgumentException + + JETTY-641 JDBC Realm purge cache problem + + JETTY-642 NPE in LdapLoginModule + + JETTY-644 LdapLoginModule uses proper filters when searching + + JETTY-645 Do not provide jetty-util to the webapps + + JETTY-646 Should set Cache-Control header when sending errors to avoid caching + + JETTY-647 suspended POSTs with binary data do too many resumes + + JETTY-650 Parse "*" URI for HTTP OPTIONS request + + JETTY-651 Release resources during destroy + + JETTY-653 Upgrade jta api specs to more recent version + + JETTY-654 Allow Cometd Bayeux object to be JMX manageable + + JETTY-655 Support parsing application/x-www-form-urlencoded parameters via http PUT + + JETTY-656 HttpClient defaults to async mode + + JETTY-659 ContentExchange and missing headers in HttpClient + + JETTY-663 AbstractDatabaseLoginModule handle not found UserInfo and userName + + JETTY-665 Support merging class directories + + JETTY-666 scanTargetPatterns override the values already being set by scanTarget + + JETTY-667 HttpClient handles chunked content + + JETTY-669 Http methods other than GET and POST should not have error page content + + JETTY-671 Configure DTD does not allow children + + JETTY-672 Utf8StringBuffer doesn't properly handle null characters (char with byte value 0) + + JETTY-675 ServletContext.getRealPath("") returns null instead of returning the root dir of the webapp + +jetty-6.1.12rc1 1 August 2008 + + Upgrade jsp 2.1 to SJSAS-9_1_02-B04-11_Apr_2008 + + JETTY-319 Get unavailable exception and added startWithUnavailable option + + JETTY-381 JETTY-622 Multiple Web Application Source Directory + + JETTY-442 Accessors for mimeType on ResourceHandler + + JETTY-502 forward of an include should hide include attributes + + JETTY-562 RewriteHandler support for virtual hosts + + JETTY-563 GWT OpenRemoteServiceServlet GWT1.5M2+ + + JETTY-564 Consider optionally importing org.apache.jasper.servlet + + JETTY-571 SelectChannelConnector throws Exception on close on Windows + + JETTY-596 Proxy authorization support in HttpClient + + JETTY-599 handle buffers consistently handle invalid index for poke + + JETTY-603 Handle IPv6 in HttpURI + + JETTY-605 Added optional threadpool to BayeuxService + + JETTY-606 better writeTo impl for BIO + + JETTY-607 Add GigaSpaces session clustering + + JETTY-610 jetty.class.path not being interpreted + + JETTY-613 website module now generates site-component for jetty-site + + JETTY-614 scanner allocated hashmap on every scan + + JETTY-623 ServletContext.getServerInfo() non compliant + + JETTY-626 Null protect reading the dtd resource from classloader + + JETTY-628 Rewrite rule for rewriting scheme + + JETTY-629 Don't hold timeout lock during expiry call. + + JETTY-632 OSGi tags for Jetty client + + JETTY-633 Default form encoding 8859_1 rather than utf-8 + + JETTY-635 Correctly merge request parameters when doing forward + + JETTY-637 empty date headers throw IllegalArgumentException + + JETTY-641 JDBC Realm purge cache problem + + JETTY-642 NPE in LdapLoginModule + + JETTY-644 LdapLoginModule uses proper filters when searching + + JETTY-646 Should set Cache-Control header when sending errors to avoid caching + + JETTY-647 suspended POSTs with binary data do too many resumes + + JETTY-650 Parse "*" URI for HTTP OPTIONS request + + JETTY-651 Release resources during destroy + + JETTY-654 Allow Cometd Bayeux object to be JMX manageable + + JETTY-655 Support parsing application/x-www-form-urlencoded parameters via http PUT + + JETTY-656 HttpClient defaults to async mode + + JETTY-657 Backport jetty-7 sslengine + + JETTY-658 backport latest HttpClient from jetty-7 to jetty-6 + + JETTY-659 ContentExchange and missing headers in HttpClient + + JETTY-660 Backported QoSFilter + + JETTY-663 AbstractDatabaseLoginModule handle not found UserInfo and userName + + JETTY-665 Support merging class directories + + JETTY-666 scanTargetPatterns override the values already being set by scanTarget + + JETTY-667 HttpClient handles chunked content + + JETTY-669 Http methods other than GET and POST should not have error page content + +jetty-7.0.0pre2 - 30 June 2008 + + JETTY-336 413 error for header buffer full + + JETTY-425 race in stopping SelectManager + + JETTY-568 Avoid freeing DirectBuffers. New locking NIO ResourceCache. + + JETTY-569 Stats for suspending requests + + JETTY-576 servlet dtds and xsds not being loaded locally + + JETTY-572 Unique cometd client ID + + JETTY-578 OSGI Bundle-RequiredExcutionEnvironment set to J2SE-1.5 + + JETTY-579 OSGI resolved management and servlet.resources import error + + JETTY-580 Fixed SSL shutdown + + JETTY-581 ContextPath constructor + + JETTY-582 final ISO_8859_1 + + JETTY-584 handle null contextPath + + JETTY-587 persist sessions to database + + JETTY-588 handle Retry in ServletException + + JETTY-589 Added Statistics Servlet + + JETTY-590 Digest auth domain for root context + + JETTY-592 expired timeout callback without synchronization + + JETTY-595 SessionHandler only deals with base request session + + JETTY-596 proxy support in HttpClient + + JETTY-598 Added more reliable cometd message flush option + + JETTY-599 handle buffers consistently handle invalid index for poke + + JETTY-603 Handle IPv6 in HttpURI + + JETTY-605 Added optional threadpool to BayeuxService + + JETTY-606 better writeTo impl for BIO + + JETTY-607 Add GigaSpaces session clustering + + JETTY-609 jetty-client improvements for http conversations + + JETTY-610 jetty.class.path not being interpreted + + JETTY-611 make general purpose jar scanning mechanism + + JETTY-612 scan for web.xml fragments + + JETTY-613 various distribution related changes + + JETTY-614 scanner allocates hashmap on every iteration + + JETTY-615 Replaced CDDL servlet.jar with Apache-2.0 licensed version + + JETTY-623 ServletContext.getServerInfo() non compliant + +jetty-6.1.11 6 June 2008 + + JETTY-336 413 error for full header buffer + + JETTY-425 race in stopping SelectManager + + JETTY-580 Fixed SSL shutdown + + JETTY-581 ContextPath constructor + + JETTY-582 final ISO_8859_1 + + JETTY-584 handle null contextPath + + JETTY-588 handle Retry in ServletException + + JETTY-590 Digest auth domain for root context + + JETTY-592 expired timeout callback without synchronization + + JETTY-595 SessionHandler only deals with base request session + + JETTY-596 Proxy support in HttpClient + + JETTY-598 Added more reliable cometd message flush option + +jetty-6.1.10 20 May 2008 + + Use QueuedThreadPool as default + + JETTY-440 allow file name patterns for jsp compilation for jspc plugin + + JETTY-529 CNFE when deserializing Array from session resolved + + JETTY-537 JSON handles Locales + + JETTY-547 Shutdown SocketEndpoint output before close + + JETTY-550 Reading 0 bytes corrupts ServletInputStream + + JETTY-551 Upgraded to Wadi 2.0-M10 + + JETTY-556 Encode all URI fragments + + JETTY-557 Allow ServletContext.setAttribute before start + + JETTY-558 optional handling of X-Forwarded-For/Host/Server + + JETTY-566 allow for non-blocking behavior in jetty maven plugin + + JETTY-572 unique cometd client ID + + JETTY-579 osgi fixes with management and servlet resources + +jetty-7.0.0pre1 - 3 May 2008 + + Allow annotations example to be built regularly, copy to contexts-available + + Make annotations example consistent with servlet 3.0 + + Refactor JNDI impl to simplify + + Improved suspend examples + + address osgi bundling issue relating to build resources + + JETTY-529 CNFE when deserializing Array from session resolved + + JETTY-558 optional handling of X-Forwarded-For/Host/Server + + JETTY-559 ignore unsupported shutdownOutput + + JETTY-566 allow for non-blocking behavior in jetty maven plugin + + JETTY-440 allow file name patterns for jsp compilation for jspc plugin + +jetty-7.0.0pre0 - 21 April 2008 + + Jetty-6.1.8 Changes + + Refactor of Continuation towards servlet 3.0 proposal + + JETTY-282 Support manually-triggered reloading by maven plugin + + QueuedThreadPool default + + RetryRequest exception now extends ThreadDeath + + Added option to dispatch to suspended requests. + + Delay 100 continues until getInputStream + + HttpClient supports pipelined request + + BayeuxClient use a single connection for polling + + Make javax.servlet.jsp optional osgi import for jetty module + + Ensure Jotm tx mgr can be found in jetty-env.xml + + Renamed modules management and naming to jmx and jndi. + + JETTY-282 Support manually-triggered reloading by maven plugin + + JETTY-341 100-Continues sent only after getInputStream called. + + JETTY-386 backout fix and replaced with ContextHandler.setCompactPath(boolean) + + JETTY-399 update OpenRemoteServiceServlet to gwt 1.4 + + JETTY-467 allow URL rewriting to be disabled. + + JETTY-468 unique holder names for addServletWithMapping + + JETTY-471 LDAP JAAS Realm + + JETTY-474 Fixed case sensitivity issue with HttpFields + + JETTY-475 AJP connector in RPMs + + JETTY-486 Improved jetty.sh script + + JETTY-487 Handle empty chunked request + + JETTY-494 Client side session replication + + JETTY-519 HttpClient does not recycle closed connection. + + JETTY-522 Add build profile for macos for setuid + + JETTY-523 Default servlet uses ServletContext.getResource + + JETTY-524 Don't synchronize session event listener calls + + JETTY-525 Fixed decoding for long strings + + JETTY-526 Fixed MMBean fields on JMX MBeans + + JETTY-528 Factor our cookie parsing to CookieCutter + + JETTY-530 Improved JMX MBeanContainer lifecycle + + JETTY-531 Optional expires on MovedContextHandler + + JETTY-532 MBean properties for QueuedThreadPool + + JETTY-535 Fixed Bayeux server side client memory leak + + JETTY-537 JSON handles Locales + + JETTY-538 test harness fix for windows + + JETTY-540 Servlet-3.0 & java5 support (work in progress) + + JETTY-543 Atomic batch get and put of files. + + JETTY-545 Rewrite handler + + JETTY-546 Webapp runner. All in one jar to run a webapps + + JETTY-547 Shutdown SocketEndpoint output before close + + JETTY-550 Reading 0 bytes corrupts ServletInputStream + + JETTY-551 Wadi 2.0-M10 + + JETTY-553 Fixed customize override + + JETTY-556 Encode all URI fragments + + JETTY-557 Allow ServletContext.setAttribute before start + + JETTY-560 Allow decoupling of jndi names in web.xml + +jetty-6.1.9 26 March 2008 + + Make javax.servlet.jsp optional osgi import for jetty module + + Ensure Jotm tx mgr can be found in jetty-env.xml + + JETTY-399 update OpenRemoteServiceServlet to gwt 1.4 + + JETTY-471 LDAP JAAS Realm + + JETTY-475 AJP connector in RPMs + + JETTY-482 update to JETTY-399 + + JETTY-519 HttpClient does not recycle closed connection. + + JETTY-522 Add build profile for macos for setuid + + JETTY-525 Fixed decoding for long strings + + JETTY-526 Fixed MMBean fields on JMX MBeans + + JETTY-532 MBean properties for QueuedThreadPool + + JETTY-535 Fixed Bayeux server side client memory leak + + JETTY-538 test harness fix for windows + + JETTY-541 Cometd per client timeouts + +jetty-6.1.8 28 February 2008 + + Added QueuedThreadPool + + Optimized QuotedStringTokenizer.quote() + + further Optimizations and improvements of Cometd + + Optimizations and improvements of Cometd, more pooled objects + + Improved Cometd timeout handling + + Added BayeuxService + + Cookie support in BayeuxClient + + Improved Bayeux API + + add removeHandler(Handler) method to HandlerContainer interface + + Added JSON.Convertor and non static JSON instances + + Long cache for JSON + + Fixed JSON negative numbers + + JSON unquotes / + + Add "mvn jetty:stop" + + allow sessions to be periodically persisted to disk + + grizzly fixed for posts + + Add removeHandler(Handler) method to HandlerContainer interface + + Remove duplicate commons-logging jars and include sslengine in jboss sar + + Allow code ranges on ErrorPageErrorHandler + + AJP handles bad mod_jk methods + + JETTY-350 log ssl errors on SslSocketConnector + + JETTY-417 JETTY_LOGS environment variable not queried by jetty.sh + + JETTY-433 ContextDeployer constructor fails unnecessarily when using a security manager if jetty.home not set + + JETTY-434 ContextDeployer scanning of sub-directories should be optional + + JETTY-481 Handle empty Bayeux response + + JETTY-489 Improve doco on the jetty.port property for plugin + + JETTY-490 Fixed JSONEnumConvertor + + JETTY-491 opendocument mime types + + JETTY-492 Null pointer in HashSSORealm + + JETTY-493 JSON handles BigDecimals + + JETTY-498 Improved cookie parsing + + JETTY-507 Fixed encoding from JETTY-388 and test case + + JETTY-508 Extensible cometd handlers + + JETTY-509 Fixed JSONP transport for changing callback names + + JETTY-511 jetty.sh mishandled JETTY_HOME when launched from a relative path + + JETTY-512 add slf4j as optional to manifest + + JETTY-513 Terracotta session replication does not work when the initial page on each server does not set any attributes + + JETTY-515 Timer is missing scavenging Task in HashSessionManager + + +jetty-6.1.7 - 22 December 2007 + + Added BayeuxService + + Added JSON.Convertor and non static JSON instances + + Add "mvn jetty:stop" + + allow sessions to be periodically persisted to disk + + Cookie support in BayeuxClient + + grizzly fixed for posts + + jetty-6.1 branch created from 6.1.6 and r593 of jetty-contrib trunk + + Optimizations and improvements of Cometd, more pooled objects + + Update java5 patch + + JETTY-386 CERT-553235. backout fix and replaced with ContextHandler.setCompactPath(boolean) + + JETTY-467 allow URL rewriting to be disabled. + + JETTY-468 unique holder names for addServletWithMapping + + JETTY-474 Fixed case sensitivity issue with HttpFields + + JETTY-486 Improved jetty.sh script + + JETTY-487 Handle empty chunked request + +jetty-6.1.6 - 18 November 2007 + + rudimentary debian packaging + + updated grizzly connector to 1.6.1 + + JETTY-455 Optional cometd id + + JETTY-459 Unable to deploy from Eclipse into the root context + + JETTY-461 fixed cometd unknown channel + + JETTY-464 typo in ErrorHandler + + JETTY-465 System.exit() in constructor exception for MultiPartOutputStream + +jetty-6.1.6rc1 - 5 November 2007 + + Upgrade jsp 2.1 to SJSAS-9_1-B58G-FCS-08_Sept_2007 + + Housekeeping on poms + + CERT VU#38616 handle single quotes in cookie names. + + Improved JSON parsing from Readers + + Moved some impl classes from jsp-api-2.1 to jsp-2.1 + + Added configuration file for capturing stderr and stdout + + Updated for dojo 1.0(rc) cometd + + Give bayeux timer name + + Give Terracotta session scavenger a name + + Jetty Eclipse Plugin 1.0.1: force copy of context file on redeploy + + JETTY-388 Handle utf-16 and other multibyte non-utf-8 form content. + + JETTY-409 String params that denote files changed to File + + JETTY-438 handle trailing . in vhosts + + JETTY-439 Fixed 100 continues clash with Connection:close + + JETTY-451 Concurrent modification of session during invalidate + + JETTY-443 windows bug causes Acceptor thread to die + + JETTY-445 removed test code + + JETTY-448 added setReuseAddress on AbstractConnector + + JETTY-450 Bad request for response sent to server + + JETTY-452 CERT VU#237888 Dump Servlet - prevent cross site scripting + + JETTY-453 updated Wadi to 2.0-M7 + + JETTY-454 handle exceptions with themselves as root cause + + JETTY-456 allow null keystore for osX + + JETTY-457 AJP certificate chains + +jetty-6.1.6rc0 - 3 October 2007 + + Added jetty.lib system property to start.config + + AJP13 Fix on chunked post + + Fix cached header optimization for extra characters + + SetUID option to support setgid + + Make mx4j used only if runtime uses jdk<1.5 + + Moved Grizzly to contrib + + Give deployment file Scanner threads a unique name + + Fix Host header for async client + + Fix typo in async client onResponsetHeader method name + + Tweak OSGi manifests to remove unneeded imports + + Allow scan interval to be set after Scanner started + + Add jetty.host system property + + Allow properties files on the XmlConfiguration command line. + + Prevent infinite loop on stopping with temp dir + + Ensure session is completed only when leaving context. + + Update terracotta to 2.4.1 and exclude ssl classes + + Update jasper2.1 to tag SJSAS-9_1-B58C-FCS-22_Aug_2007 + + Removal of unneeded dependencies from management, maven-plugin, naming & plus poms + + Adding setUsername,setGroupname to setuid and mavenizing native build + + UTF-8 for bayeux client + + CVE-2007-5615 CERT21284 Added protection for response splitting with bad headers. + + Cached user agents strings in the /org/eclipse/jetty/useragents resource + + Make default time format for RequestLog match NCSA default + + Use terracotta repo for build; make jetty a terracotta module + + Fix patch for java5 to include cometd module + + Added ConcatServlet to combine javascript and css + + Add ability to persist sessions with HashSessionManager + + Avoid FULL exception in window between blockForOutput and remote close + + Added JPackage RPM support + + Added JSON.Convertable + + Updated README, test index.html file and jetty-plus.xml file + + JETTY-259 SystemRoot set for windows CGI + + JETTY-311 avoid json keywords + + JETTY-376 allow anything but CRLF in reason string + + JETTY-398 Allow same WADI Dispatcher to be used across multiple web-app contexts + + JETTY-400 consume CGI stderr + + JETTY-402 keep HashUserRealm in sync with file + + JETTY-403 Allow long content length for range requests + + JETTY-404 WebAppDeployer sometimes deploys duplicate webapp + + JETTY-405 Default date formate for reqest log + + JETTY-407 AJP handles unknown content length + + JETTY-413 Make rolloveroutputstream timer daemon + + JETTY-422 Allow values to be null in config files + + JETTY-423 Ensure javax.servlet.forward parameters are latched on first forward + + JETTY-425 Handle duplicate stop calls better + + JETTY-430 improved cometd logging + + JETTY-431 HttpClient soTimeout + +jetty-6.1.5 - 19 Jul 2007 + + Upgrade to Jasper 2.1 tag SJSAS-9_1-B50G-BETA3-27_June_2007 + + Fixed GzipFilter for dispatchers + + Fixed reset of reason + + JETTY-392 - updated LikeJettyXml example + +jetty-6.1.5rc0 - 15 Jul 2007 + + update terracotta session clustering to terracotta 2.4 + + SetUID option to only open connectors before setUID. + + Protect SslSelectChannelConnector from exceptions during close + + Improved Request log configuration options + + Added GzipFilter and UserAgentFilter + + make OSGi manifests for jetty jars + + update terracotta configs for tc 2.4 stable1 + + remove call to open connectors in jetty.xml + + update links on website + + make jetty plus example webapps use ContextDeployer + + Dispatch SslEngine expiry (non atomic) + + Make SLF4JLog impl public, add mbean descriptors + + SPR-3682 - dont hide forward attr in include. + + Upgrade to Jasper 2.1 tag SJSAS-9_1-B50G-BETA3-27_June_2007 + + JETTY-253 - Improved graceful shutdown + + JETTY-373 - Stop all dependent lifecycles + + JETTY-374 - HttpTesters handles large requests/responses + + JETTY-375 - IllegalStateException when committed. + + JETTY-376 - allow spaces in reason string + + JETTY-377 - allow sessions to be wrapped with AbstractSesssionManager.SessionIf + + JETTY-378 - handle JVMs with non ISO/UTF default encodings + + JETTY-380 - handle pipelines of more than 4 requests! + + JETTY-385 - EncodeURL for new sessions from dispatch + + JETTY-386 - Allow // in file resources + + +jetty-6.1.4 - 15 Jun 2007 + + fixed early open() call in NIO connectors + + JETTY-370 ensure maxIdleTime<=0 means connections never expire + + JETTY-371 Fixed chunked HEAD response + + JETTY-372 make test for cookie caching more rigorous + +jetty-6.1.4rc1 - 10 Jun 2007 + + Work around IBM JVM socket close issue + + moved documentation for jetty and jspc maven plugins to wiki + + async client improvements + + fixed handling of large streamed files + + Fixed synchronization conflict SslSelectChannel and SelectChannel + + Optional static content cache + + JETTY-310 better exception when no filter file for cometd servlet + + JETTY-323 handle htaccess without a user realm + + JETTY-346 add wildcard support to extra scan targets for maven plugin + + JETTY-355 extensible SslSelectChannelConnector + + JETTY-357 cleaned up ssl buffering + + JETTY-360 allow connectors, userRealms to be added from a for maven plugin + + JETTY-361 prevent url encoding of dir listings for non-link text + + JETTY-362 More object locks + + JETTY-365 make needClientAuth work on SslSelectChannelConnector + + JETTY-366 JETTY-368 Improved bayeux disconnect + +jetty-6.1.4rc0 - 1 Jun 2007 + + Reorganized import of contrib modules + + Unified JMX configuration + + Updated slf4j version to 1.3.1 + + Updated junit to 3.8.2 + + Allow XmlConfiguration properties to be configured + + Add (commented out) jspc precompile to test-webapp + + Add slf4j-api for upgraded version + + Change scope of fields for Session + + Add ability to run cometd webapps to maven plugin + + Delay ssl handshake until after dispatch in sslSocketConnector + + Set so_timeout during ssl handshake as an option on SslSocketConnector + + Optional send Date header. Server.setSendDateHeader(boolean) + + update etc/jetty-ssl.xml with new handshake timeout setting + + fixed JSP close handling + + improved date header handling + + fixed waiting continuation reset + + JETTY-257 fixed comet cross domain + + JETTY-309 fix applied to sslEngine + + JETTY-317 rollback inclusion of cometd jar for maven plugin + + JETTY-318 Prevent meta channels being created + + JETTY-330 Allow dependencies with scope provided for jspc plugin + + JETTY-335 SslEngine overflow fix + + JETTY-337 deprecated get/setCipherSuites and added get/setExcludeCipherSuites + + JETTY-338 protect isMoreInBuffer from destroy + + JETTY-339 MultiPartFiler deletes temp files on IOException + + JETTY-340 FormAuthentication works with null response + + JETTY-344 gready fill in ByteArrayBuffer.readFrom + + JETTY-345 fixed lost content with blocked NIO. + + JETTY-347 Fixed type util init + + JETTY-352 Object locks + +jetty-6.1.3 - 4 May 2007 + + Handle CRLF for content in header optimization + + JETTY-309 don't clear writable status until dispatch + + JETTY-315 suppressed warning + + JETTY-322 AJP13 cping and keep alive + +jetty-6.1.2 - 1 May 2007 + + Improved unavailabile handling + + sendError resets output state + + Fixed session invalidation error in WadiSessionManager + + Updated Wadi to version 2.0-M3 + + Added static member definition in WadiSessionManager + + JETTY-322 fix ajp cpong response and close handling + + JETTY-324 fix ant plugin + + JETTY-328 updated jboss + +jetty-6.1.2rc5 - 24 April 2007 + + set default keystore for SslSocketConnector + + removed some compile warnings + + Allow jsp-file to be / or /* + + JETTY-305 delayed connection destroy + + JETTY-309 handle close in multivalue connection fields. + + JETTY-309 force writable status of endpoints. + + JETTY-314 fix for possible NPE in Request.isRequestedSessionIdValid + +jetty-6.1.2rc4 - 19 April 2007 + + JETTY-294 Fixed authentication reset + + JETTY-299 handle win32 paths for object naming + + JETTY-300 removed synchronized on dispatch + + JETTY-302 correctly parse quoted content encodings + + JETTY-303 fixed dual reset of generator + + JETTY-304 Fixed authentication reset + +jetty-6.1.2rc3 - 16 April 2007 + + Improved performance and exclusions for TLD scanning + + MBean properties assume writeable unless marked RO + + refactor of SessionManager and SessionIdManager for clustering + + Improvements to allow simple setting of Cache-Control headers + + AJP redirects https requests correctly + + Fixed writes of unencoded char arrays. + + JETTY-283 Parse 206 and 304 responses in client + + JETTY-285 enable jndi for mvn jetty:run-war and jetty:run-exploded + + JETTY-289 fixed javax.net.ssl.SSLException on binary file upload + + JETTY-292 Fixed error page handler error pages + + JETTY-293 fixed NPE on fast init + + JETTY-294 Response.reset() resets headers as well as content + + JETTY-295 Optional support of authenticated welcome files + + JETTY-296 Close direct content inputstreams + + JETTY-297 Recreate tmp dir on stop/start + + JETTY-298 Names in JMX ObjectNames for context, servlets and filters + +jetty-6.1.2rc2 - 27 March 2007 + + Enable the SharedStoreContextualiser for the WadiSessionManager(Database store for clustering) + + AJP13 CPING request and CPONG response implemented + + AJP13 Shutdown Request from peer implemented + + AJP13 remoteUser, contextPath, servletPath requests implemented + + Change some JNDI logging to debug level instead of info + + Update jasper to glassfish tag SJSAS-9_1-B39-RC-14_Mar_2007 + + Optimized multi threaded init on startup servlets + + Removed unneeded specialized TagLibConfiguration class from maven plugin + + Refactor Scanner to increase code reuse with maven/ant plugins + + Added RestFilter for PUT and DELETE from Aleksi Kallio + + Make annotations work for maven plugin + + JETTY-125 maven plugin: ensure test dependencies on classpath for + + JETTY-246 path encode cookies rather than quote + + JETTY-254 prevent close of jar entry by bad JVMs + + JETTY-256 fixed isResumed and work around JVM bug + + JETTY-258 duplicate log message in ServletHandler + + JETTY-260 Close connector before stop + + JETTY-262 Allow acceptor thread priority to be adjusted + + JETTY-263 Added implementation for authorizationType Packets + + JETTY-265 Only quote cookie values if needed + + JETTY-266 Fix deadlock with shutdown + + JETTY-271 ResourceHandler uses resource for MimeType mapping + + JETTY-272 Activate and Passivate events for sessions + + JETTY-274 Improve flushing at end of request for blocking + + JETTY-276 Partial fix for reset/close race + + JETTY-277 Improved ContextHandlerCollection + + JETTY-278 Session invalidation delay until no requests + + JETTY-278 Only unwrap one layer of ServletExceptions + + JETTY-280 Fixed deadlock with two flushing threads + + JETTY-284 Fixed stop connector race + + JETTY-286 isIntegral and isConfidential methods overridden in SslSelectChannelConnector + +jetty-6.1.2rc1 - 8 March 2007 + + TagLibConfiguration uses resource input stream + + Improved handling of early close in AJP + + add ajp connector jar to jetty-jboss sar + + Improved Context setters for wadi support + + fix Dump servlet to handle primitive array types + + handle comma separated values for the Connection: header + + Added option to allow null pathInfo within context + + BoundedThreadPool queues rather than blocks excess jobs. + + Support null pathInfo option for webservices deployed to jetty/jboss + + Workaround to call SecurityAssocation.clear() for jboss webservices calls to ejbs + + Ensure jetty/jboss uses servlet-spec classloading order + + call preDestroy() after servlet/filter destroy() + + Fix constructor for Constraint to detect wildcard role + + Added support for lowResourcesIdleTime to SelectChannelConnector + + JETTY-157 make CGI handle binary data + + JETTY-175 JDBCUserRealm use getInt instead of getObject + + JETTY-188 Use timer for session scavaging + + JETTY-235 default realm name + + JETTY-242 fix race condition with scavenging sessions when stopping + + JETTY-243 FULL + + JETTY-244 Fixed UTF-8 buffer overflow + + JETTY-245 Client API improvements + + JETTY-246 spaces in cookies + + JETTY-248 setContentLength after content written + + JETTY-250 protect attribute enumerations from modification + + JETTY-252 Fixed stats handling of close connection + + JETTY-254 prevent close of jar file by bad JVMs + +jetty-6.1.2rc0 - 15 February 2007 + + JETTY-223 Fix disassociate of UserPrincipal on dispatches + + JETTY-226 Fixed SSLEngine close issue + + JETTY-232 Fixed use of override web.xml + + JETTY-236 Buffer leak + + JETTY-237 AJPParser Buffer Data Handling + + JETTY-238 prevent form truncation + + Patches from sybase for ClientCertAuthenticator + + Coma separated cookies + + Cometd timeout clients + +jetty-6.1.2pre1 5 Feb 2007 + + JETTY-224 run build up to process-test before invoking jetty:run + + Added error handling for incorrect keystore/truststore password in SslSelectChannelConnector + + fixed bug with virtual host handling in ContextHandlerCollection + + added win32service to standard build + + refactored cometd to be continuation independent + + allow ResourceHandler to use resource base from an enclosing ContextHandler + +jetty-6.1.2pre0 1 Feb 2007 + + Fixed 1.4 method in jetty plus + + Fixed generation of errors during jsp compilation for jsp-2.1 + + Added cometd jsonp transport from aabeling + + Added terracotta cluster support for cometd + + JETTY-213 request.isUserInRole(String) fixed + + JETTY-215 exclude more transitive dependencies from tomcat jars for jsp-2.0 + + JETTY-216 handle AJP packet fragmentation + + JETTY-218 handle AJP ssl key size and integer + + JETTY-219 fixed trailing encoded chars in cookies + + JETTY-220 fixed AJP content + + JETTY-222 fix problem parsing faces-config.xml + + add support for Annotations in servlet, filter and listener sources + + improved writer buffering + + moved JSON parser to util to support reuse + + handle virtual hosts in ContextHandlerCollection + + enable SslSelectChannelConnector to modify the SslEngine's client authentication settings + +jetty-6.1.1 - 15 Jan 2007 +jetty-6.1.1rc1 - 12 Jan 2007 + + Use timers for Rollover logs and scanner + + JETTY-210 Build jsp-api-2.0 for java 1.4 + +jetty-6.1.1rc0 - 10 Jan 2007 + + Fixed unpacking WAR + + extras/win32service download only if no JavaServiceWrapper exist + + MultiPartFilter deleteFiles option + + CGI servlet fails without exception + + JETTY-209 Added ServletTester.createSocketConnector + + JETTY-210 Build servlet-api-2.5 for java 1.4 + + JETTY-211 fixed jboss build + + ensure response headers on AjaxFilter messsages turn off caching + + start webapps on deployment with jboss, use isDistributed() method from WebAppContext + + simplified chat demo + +jetty-6.1.0 - 9 Jan 2007 + + Fixed unpacking WAR +jetty-6.1.0 - 5 Jan 2007 + + Improved config of java5 threadpool + + Protect context deployer from Errors + + Added WebAppContext.setCopyWebDir to avoid JVM jar caching issues. + + GERONIMO-2677 refactor of session id handling for clustering + + ServletTester sets content length + + Added extras/win32service + + JETTY-206 fixed AJP getServerPort and getRemotePort + +jetty-6.1.0rc3 - 2 Jan 2007 + + JETTY-195 fixed ajp ssl_cert handling + + JETTY-197 fixed getRemoteHost + + JETTY-203 initialize ServletHandler if no Context instance + + JETTY-204 setuid fix + + setLocale does not use default content type + + Use standard releases of servlet and jsp APIs. + + implement resource injection and lifecycle callbacks declared in web.xml + + extras/servlet-tester + +jetty-6.1.0rc2 - 20 December 2006 + + AJP13Parser, throw IllegalStateException on unimplemented AJP13 Requests + + ContextHandlerCollection is noop with no handlers + + ensure servlets initialized if only using ServletHandler + + fixed Jetty-197 AJP13 getRemoteHost() + + Refactored AbstractSessionManager for ehcache + + ensure classpath passed to jspc contains file paths not urls + + JETTY-194 doubles slashes are significant in URIs + + JETTY-167 cometd refactor + + remove code to remove SecurityHandler if no constraints present + + JETTY-201 make run-as work for both web container and ejb container in jboss + + ensure com.sun.el.Messages.properties included in jsp-2.1 jar + +jetty-6.1.0rc1 - 14 December 2006 + + simplified idle timeout handling + + JETTY-193 MailSessionReference without authentication + + JETTY-199 newClassPathResource + + ensure unique name for ServletHolder instances + + added cache session manager(pre-alpha) + +jetty-6.1.0rc0 - 8 December 2006 + + JETTY-181 Allow injection of a java:comp Context + + JETTY-182 Optionally set JSP classpath initparameter + + Dispatcher does not protect javax.servlet attributes + + DefaultHandler links virtual hosts. + + Fixed cachesize on invalidate + + Optimization of writers + + ServletHandler allows non REQUEST exceptions to propogate + + TCK fixes from Sybase: + * Handle request content encodings + * forward query attribute fix + * session attribute listener + * Servlet role ref + * flush if content-length written + * 403 for BASIC authorization failure + * null for unknown named dispatches + + JETTY-184 cometd connect non blocking + + Support for RFC2518 102-processing response + + JETTY-123 fix improved + + Added org.eclipse.thread.concurrent.ThreadPool + + Added extras/gwt + + Fixed idle timeout + + JETTY-189 ProxyConnection + + Added spring ejb3 demo example + + update jasper to glassfish SJSAS-9_1-B27-EA-07_Dec_2006 + + fixed JETTY-185 tmp filename generation + +jetty-6.1.0pre3 - 22 November 2006 + + fixed NIO endpoint flush. Avoid duplicate sends + + CVE-2006-6969 Upgraded session ID generation to use SecureRandom + + updated glassfish jasper to tag SJSAS-9_1-B25-EA-08_Nov_2006 + + Support TLS_DHE_RSA_WITH_AES_256_CBC_SHA + + JETTY-180 XBean support for context deploy + + JETTY-154 Cookies are double quotes only + + Expose isResumed on Continuations + + Refactored AJP generator + + +jetty-6.0.2 - 22 November 2006 + + Moved all modules updates from 6.1pre2 to 6.0 + + Added concept of bufferred endpoint + + Added conversion Object -> ObjectName for the result of method calls made on MBeans + + Added DataFilter configuration to cometd + + added examples/test-jaas-webapp + + Added extraClassPath to WebAppContext + + Added hierarchical destroy of mbeans + + Added ID constructor to AbstractSessionManager.Session + + added isStopped() in LifeCycle and AbstractLifeCycle + + Added override descriptor for deployment of RO webapps + + add replacement in jetty xml config files + + alternate optimizations of writer (use -Dbuffer.writers=true) + + Allow session cookie to be refreshed + + Apply queryEncoding to getQueryString + + CGI example in test webapp + + change examples/test-jndi-webapp so it can be regularly built + + Default soLinger is -1 (disabled) + + ensure "" returned for ServletContext.getContextPath() for root context + + ensure sessions nulled out on request recycle; ensure session null after invalidate + + ensure setContextPath() works when invoked from jetty-web.xml + + fixed NIO endpoint flush. Avoid duplicate sends + + Fixed NPE in bio.SocketEndPoint.getRemoteAddr() + + Fixed resource cache flushing + + Fixed tld parsing for maven plugin + + HttpGenerator can generate requests + + Improved *-mbean.properties files and specialized some MBean + + JETTY-118 ignore extra content after close. + + JETTY-119 cleanedup Security optimizatoin + + JETTY-123 handle windows UNC paths + + JETTY-126 handle content > Integer.MAX_VALUE + + JETTY-129 ServletContextListeners called after servlets are initialized + + JETTY-151 Idle timeout only applies to blocking operations + + JETTY-151 refactored writers + + JETTY-154 Cookies are double quotes only + + JETTY-171 Fixed filter mapping + + JETTY-172 use getName() instead of toString + + JETTY-173 restore servletpath after dispatch + + Major refactor of SelectChannel EndPoint for client selector + + make .tag files work in packed wars + + Plugin shutdown context before stopping it. + + Refactored session lifecycle and additional tests + + release resource lookup in Default servlet + + (re)make JAAS classes available to webapp classloader + + Reverted UnixCrypt to use coersions (that effected results) + + Session IDs can change worker ID + + Simplified ResourceCache and Default servlet + + SocketConnector closes all connections in doStop + + Upgraded session ID generation to use SecureRandom + + updated glassfish jasper to tag SJSAS-9_1-B25-EA-08_Nov_2006 + + Support TLS_DHE_RSA_WITH_AES_256_CBC_SHA + +Jetty-5.1.14 - 9 Aug 2007 + + patched with correct version + + JETTY-155 force close with content length. + + JETTY-369 failed state in Container + +Jetty-5.1.13 + + Sourceforge 1648335: problem setting version for AJP13 + +Jetty-5.1.12 - 22 November 2006 + + Added support for TLS_DHE_RSA_WITH_AES_256_CBC_SHA + + Upgraded session ID generation to use SecureRandom + + Quote single quotes in cookies + + AJP protected against bad requests from mod_jk + + JETTY-154 Cookies ignore single quotes + +Jetty-4.2.27 - 22 November 2006 + + Upgraded session ID generation to use SecureRandom + + AJP protected against bad requests from mod_jk + +jetty-6.1.0pre2 - 20 Nov 2006 + + Added extraClassPath to WebAppContext + + Fixed resource cache flushing + + Clean up jboss module licensing + +jetty-6.1.0pre1 - 19 Nov 2006 + + Use ContextDeployer as main deployer in jetty.xml + + Added extras/jboss + + Major refactor of SelectChannel EndPoint for client selector + + Fixed NPE in bio.SocketEndPoint.getRemoteAddr() + + Reverted UnixCrypt to use coersions (that effected results) + + JETTY-151 Idle timeout only applies to blocking operations + + alternate optimizations of writer (use -Dbuffer.writers=true) + + JETTY-171 Fixed filter mapping + + JETTY-172 use getName() instead of toString + + JETTY-173 restore servletpath after dispatch + + release resource lookup in Default servlet + + Simplified ResourceCache and Default servlet + + Added override descriptor for deployment of RO webapps + + Added hierarchical destroy of mbeans + + JETTY-151 refactored writers + +jetty-6.1.0pre0 - 21 Oct 2006 + + add replacement in jetty xml config files + + make .tag files work in packed wars + + add hot deployment capability + + ensure setContextPath() works when invoked from jetty-web.xml + + ensure sessions nulled out on request recycle; ensure session null after invalidate + + ensure "" returned for ServletContext.getContextPath() for root context + + Fixed tld parsing for maven plugin + + Improved *-mbean.properties files and specialized some MBean + + Added conversion Object -> ObjectName for the result of method calls made on MBeans + + JETTY-129 ServletContextListeners called after servlets are initialized + + change examples/test-jndi-webapp so it can be regularly built + + added isStopped() in LifeCycle and AbstractLifeCycle + + fixed isUserInRole checking for JAASUserRealm + + fixed ClassCastException in JAASUserRealm.setRoleClassNames(String[]) + + add a maven-jetty-jspc-plugin to do jspc precompilation + + added examples/test-jaas-webapp + + (re)make JAAS classes available to webapp classloader + + CGI example in test webapp + + Plugin shutdown context before stopping it. + + Added concept of bufferred endpoint + + Factored ErrorPageErrorHandler out of WebAppContext + + Refactored ErrorHandler to avoid statics + + Transforming classloader does not transform resources. + + SocketConnector closes all connections in doStop + + Improved charset handling in URLs + + minor optimization of bytes to UTF8 strings + + JETTY-112 ContextHandler checks if started + + JETTY-113 support optional query char encoding on requests + + JETTY-114 removed utf8 characters from code + + JETTY-115 Fixed addHeader + + added cometd chat demo + + JETTY-119 cleanedup Security optimizatoin + + Refactored session lifecycle and additional tests + + JETTY-121 init not called on externally constructed servlets + + JETTY-124 always initialize filter caches + + JETTY-126 handle content > Integer.MAX_VALUE + + JETTY-123 handle windows UNC paths + + JETYY-120 SelectChannelConnector closes all connections on stop + + Added ID constructor to AbstractSessionManager.Session + + Allow session cookie to be refreshed + + Added DataFilter configuration to cometd + + Added extras/setuid to support start as root + + Apply queryEncoding to getQueryString + + JETTY-118 ignore extra content after close. + + HttpGenerator can generate requests + + Ported HtAccessHandler + + Start of a client API + + Session IDs can change worker ID + + Default soLinger is -1 (disabled) + + AJP Connector + +Jetty-5.1.11 - 8 October 2006 + + fixed ByteBufferOutputStream capacity calculation + + Fixed AJP handling of certificate length (1494939) + + Fixed AJP chunk header (1507377) + + Fixed order of destruction event calls + + Fix to HttpOutputStream from M.Traverso + + Default servlet only uses setContentLength on wrapped responses + +Jetty-4.2.26 - 8 October 2006 + + Backport of AJP fixes + +jetty-6.0.1 - 24 September 2006 + + fixed isUserInRole checking for JAASUserRealm + + fixed ClassCastException in JAASUserRealm.setRoleClassNames(String[]) + + Improved charset handling in URLs + + Factored ErrorPageErrorHandler out of WebAppContext + + Refactored ErrorHandler to avoid statics + + JETTY-112 ContextHandler checks if started + + JETTY-114 removed utf8 characters from code + + JETTY-115 Fixed addHeader + + JETTY-121 init not called on externally constructed servlets + + Improved charset handling in URLs + + minor optimization of bytes to UTF8 strings + + JETTY-113 support optional query char encoding on requests + + JETTY-124 always initialize filter caches + + JETYY-120 SelectChannelConnector closes all connections on stop + +jetty-6.0.0 - 10 September 2006 + + SocketConnector closes all connections in doStop + + Conveniance builder methods for listeners and filters + + Transforming classloader does not transform resources. + + Plugin shutdown context before stopping it. + +jetty-6.0.0rc4 - 5 September 2006 + + bind jetty-env.xml entries to java:comp/env + + Fix for JETTY-107. Poor cast in SessionDump demo. + + Set charset on error pages + +jetty-6.0.0rc3 - 1 September 2006 + + pulled 6.0.0 branch + + turn URLConnection caching off when searching for tlds [JETTY-103] + + Move MailSessionReference to org.eclipse.naming.factories + + Less verbose handling of BadResources from bad URLs + + Avoid double error handling of Bad requests + + don't warn for content length on head requests + + temp fix for JETTY-104 (raised glassfish ISSUE-1044) hide + JSP forced path attribute + + Fixed JETTY-68. Complete request after sendRedirect + + Transferred the sslengine patch from the patches directory to extras + +jetty-6.0.0rc2 - 25 August 2006 + + use mvn -Dslf4j=false jetty:run to disable use of slf4j logging with jdk1.4/jsp2.0 + + added org.apache.commons.logging package to system classes that can't be overridden by a webapp classloader + + mvn -Djetty.port=x jetty:run uses port number given for the default connector + + Fixed NPE when no resource cache + + Refactored WebXmlConfiguration to allow custom web.xml resource + + Moved more utility packagtes to the util jar + + Direct buffer useage is optional + + Destroy HttpConnection to improve buffer pooling + + Timestamp in StdErrLog + +jetty-6.0.0rc1 - 16 August 2006 + + Support for binding References and Referenceables and javax.mail.Sessions in JNDI + + Added TransformingWebAppClassLoader for spring 2.0 byte code modification support + + Ensure classes come before dependencies for plugin [JETTY-90] + + Fixed FD leak for bad TCP acks. JETTY-63 + + new Server().addHandler(handler) no longer throws NPE [JETTY-87] + + Change path mapping so that a path spec of /foo/* does not match /foo.bar : JETTY-88 + + add config param to jetty plugin + + Improve Ssl config JETTY-85 JETTY-86 (TrustManager and SecureRandom are now configurable; better handling of null/default values) + + parse jsp-property-group in web.xml for additional JSP servlet mappings + + protected setContentType from being set during include + + added toString() on JAASUserPrincipal (JETTY-91) + + added modules/spring with XmlBeanFactory configuration + + removed support for lowResources from SelectChannelConnector + + added start of cometd implementation (JSON only) + + added start of grizzly connector + + removed org.eclipse. from context system classes configuration + + -DSTOP.PORT must be specified. + + moved optional modules to extras + + fixed bug that caused Response.setStatus to ignore the provided message + + refactored resource cache + + Allow direct filling of buffers for uncached static content. + + Added simple ResourceHandler and FileServer example + +jetty-6.0.0rc0 - 7 July 2006 + + change prefix from "jetty6" to just "jetty" for plugin: eg is now mvn jetty:run + + allow or in for plugin + + simplified jetty.xml with new constructor injections + + added setters and getters on SessionManager API for session related config: cookie name, url parameter name, domain, max age and path. + + add ability to have a lib/ext dir from which to recursively add all jars and zips to the classpath + + patch to allow Jetty to use JSP2.1 from Glassfish instead of Jasper from Tomcat + + fixed classesDirectory param for maven plugin to be configurable + + ensure explicitly set tmp directory called "work" is not deleted on exit + + ensure war is only unpacked if war is newer than "work" directory + + change name of generated tmp directory to be "Jetty_"+host+"_"+port+"_"+contextpath+"_"+virtualhost + + Cleaned up idle expiry. + + Ssl algorithm taken from system property + + Added 8 random letters&digits to Jetty-generated tmp work dir name to ensure uniqueness + + Simplify runtime resolution of JSP library for plugin + + Ensure mvn clean cleans the build + + Do not wrap EofException with EofException + + reverse order for destroy event listeners + + added StatisticsHandler and statistics on Connector. + + Simplified Servlet Context API + + Added maximum limit to filter chain cache. + + refactor HttpChannelEndPoint in preparation for SslEngine + + ContextHandlerCollection addContext and setContextClass + + Discard excess bytes in header buffer if connection is closing + + Updated javax code from http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk/java/javax@417727 + + Threadpool does not need to be a LifeCycle + + support graceful shutdown + + Added WebAppContextClassLoader.newInstance to better support exensible loaders. + + immutable getParameterMap() + + support for SingleThreadModel + + changed ServletContext.getResourcePaths() to not return paths containing double slashes + + fixed HttpGenerator convertion of non UTF-8: JETTY-82 + + added html module from jetty 5 - but deprecated until maintainer found + +jetty-6.0.0beta17 - 1/6/2006 + + Added config to disable file memory mapped buffers for windows + + Added Request.isHandled() + + Refactored Synchronization of SelectChannelConnector + + Recovered repository from Codehaus crash + + ContextHandler.setConnectors replace setHosts + + Connector lowResourceMaxIdleTime implemented. + + Default servlet checks for aliases resources + + Added clover reports and enough tests to get >50% coverage + + Fixed IE SSL issue. + + Implemented runAs on servlets + + Flush will flush all bytes rather than just some. + + Protected WEB-INF and META-INF + + don't reset headers during forward + + BoundedThreadPool.doStop waits for threads to complete + +jetty-6.0.0beta16 - 12/5/2006 + + remove a couple of System.err.printlns + + replace backwards compativle API in UrlEncoded + +jetty-6.0.0beta15 - 11/5/2006 + + Added Server attribute org.eclipse.jetty.Request.maxFormContentSize + + Renamed NotFoundHandler to DefaultHandler + + Added automatic scan of all WEB-INF/jetty-*.xml files for plugin + + Added parameter to allow other locations to scan for plugin + + Major refactor to simplify Server and handler hierarchy + + setSendServerVersion method added to Server to control sending of Server: http header + + removed SelectBlockingChannelConnector (unmaintained) + + Improved HttpException + + Moved more resources to resources + + Added ThrottlingFilter and fixed race in Continuations + + Added taglib resources to 2.1 jsp api jar + + Reset of timer task clears expiry + + improved MBeanContainer object removal + + ContextHandler.setContextPath can be called after start. + + Fixed handling of params after forward + + Added --version to start.jar + + Added embedded examples + + Simplified DefaultServlet static content buffering + + readded BoundedThreadPool shrinking (and then fixed resulting deadlock) + + improved MBean names + + improved support for java5 jconsole + + Session scavenger threads from threadpool + + Thread names include URI if debug set + + don't accept partial authority in request line. + + enforce 204 and 304 have no content + +jetty-6.0.0beta14 - 9/4/2006 + + ignore dirs and files that don't exist in plugin scanner + + added support for stopping jetty using "java -jar start.jar --stop" + + added configurability for webdefault.xml in maven plugin + + adding InvokerServlet + + added ProxyServlet + + stop JDBCUserRealm coercing all credentials to String + + Change tmp dir of plugin to work to be in line with jetty convention + + Modify plugin to select JSP impl at runtime + + Use start.config to select which JSP impl at runtime based on jdk version + + Added JSP 2.1 APIs from apache + + Added Jasper 2.1 as jesper (jasper without JCL) + + Started readding logging to jesper using jdk logging + + fixed priority of port from url over host header + + implemented request.isUserInRole + + securityHandler removed if not used. + + moved test webapps to examples directory + + improved contentType handling and test harness + + fixed forward bug (treated as include) + + fixed HttpField iterator + + added jetty-util.jar module + + added reset to Continuation + + +jetty-6.0.0beta12 - 16/3/2006 + + Fixed maven plugin JNDI for redeploys + + Fixed tld discovery for plugin (search dependencies) + + Fixed JettyPlus for root contexts + + Fixed error handling in error page + + Added JSP2.0 demos to test webapp + + Upgraded jasper to 5.5.15 + + Added provider support to SslListener + + Log ERROR for runtimeExceptions + +jetty-6.0.0beta11 - 14/3/2006 + + added JAAS + + added webapp-specific JNDI entries + + added missing Configurations for maven plugin + + fixed FORM authentication + + moved dtd and xsd to standard javax location + + added patch to use joda-time + + refactored session ID management + + refactored configuration files and start() + + fixed ; decoding in URIs + + Added HttpURI and improved UTF-8 parsing. + + refactored writers and improved UTF-8 generation. + +jetty-6.0.0beta10 25/2/2006 + + Added support for java:comp/env + + Added support for pluggable transaction manager + + Forward masks include attributes and vice versa + + Fixed default servlet handling of includes + + Additional accessors for request logging + + added getLocalPort() to connector + + Fixed content-type for range requests + + Fix for sf1435795 30sec delay from c taylor + + Fix for myfaces and include with close + + Fix sf1431936 don't chunk the chunk + + Fix http://jira.codehaus.org/browse/JETTY-6. hi byte reader + + Updates javax to MR2 release + +jetty-6.0.0beta9 9/2/2006 + + PathMap for direct context mapping. + + Refactored chat demo and upgraded prototype.js + + Continuation cleanup + + Fixed unraw decoding of query string + + Fixed dispatch of wrapped requests. + + Fixed double flush of short content. + + Added request log. + + Added CGI servlet. + + Force a tempdir to be set. + + Force jasper scratch dir. + + fixed setLocale bug sf1426940 + + Added TLD tag listener handling. + +jetty-6.0.0beta8 24/1/2006 + + fixed dispatch of new session problem. sf:1407090 + + reinstated rfc2616 test harness + + Handle pipeline requests without hangs + + Removed queue from thread pool. + + improved caching of content types + + fixed bug in overloaded write method on HttpConnection (reported against Tapestry4.0) + + hid org.apache.commons.logging and org.slf4j packages from webapp + + maven-jetty6-plugin stopped transitive inclusion of log4j and + commons-logging from commons-el for jasper + + patch to remove spurious ; in HttpFields + + improve buffer return mechanism. + + conveniance addHandler removeHandler methods + + maven-jetty6-plugin: ensure compile is done before invoking jetty + + maven-jetty6-plugin: support all types of artifact dependencies + +Jetty-6.0.0Beta7 + + Fixed infinite loop with chunk handling + + Faster header name lookup + + removed singleton Container + + reduced info verbosity + + null dispatch attributes not in names + + maven-jetty6-plugin added tmpDirectory property + + maven-jetty6-plugin stopped throwing an error if there is no target/classes directory + +Jetty-6.0.0Beta6 + + Fixed issue with blocking reads + + Fixed issue with unknown headers + + optimizations + +Jetty-6.0.0Beta5 + + Moved to SVN + + Fixed writer char[] creations + + Added management module for mbeans + +Jetty-6.0.0Beta4 + + System property support in plugin + + CVE-2006-2758 Fixed JSP visibility security issue. + + Improved jetty-web.xml access to org.eclipse classes. + + Jasper 5.5.12 + +Jetty-6.0.0Beta3 + + Fixed error in block read + + Named dispatch. + + Fixed classloader issue with server classes + +Jetty-6.0.0Beta2 + + merged util jar back into jetty jar + + Simpler continuation API + + loosely coupled with JSP servlet + + loosely coupled with SLF4J + + Improved reuse of HttpField values and cookies. + + Improved buffer return + +Jetty-6.0.0Beta1 + + Servlet 2.5 API + + SSL connector + + maven2 plugin + + shutdown hook + + refactored start/stop + + Implemented all listeners + + Error pages + + Virtual hosts + + Multiple select sets + +Jetty-6.0.0Beta0 + + Maven 2 build + + Dispatcher parameters + + UTF-8 encoding for URLs + + Fixed blocking read + +Jetty-6.0.0APLPA3 + + Added demo for Continuations + + Jasper and associated libraries. + +Jetty-6.0.0ALPHA2 + + Continuations - way cool way to suspend a request and retry later. + + Dispatchers + + Security + +Jetty-6.0.0ALPHA1 + + Filters + + web.xml handling + +Jetty-6.0.0ALPHA0 + * Totally rearchitected and rebuilt, so 10 years of cruft could be removed! + * Improved "dependancy injection" and "inversion of control" design of components + * Improved "interceptor" design of handlers + * Smart split buffer design allows large buffers to only be allocated to active connections. The + resulting memory savings allow very large buffers to be used, which increases the chance of efficient + asynchronous flushing and of avoiding chunking. + * Optional use of NIO Buffering so that efficient direct buffers and memory mapped files can be + used. + * Optional use of NIO non-blocking scheduling so that threads are not allocated per connection. + * Optional use of NIO gather writes, so that for example a HTTP header and a memory mapped + * file may be sent as sent is a single operation. + - Missing Security + - Missing Request Dispatchers + - Missing web.xml based configuration + - Missing war support + + +Jetty-5.1.11RC0 - 5 April 2006 + + stop JDBCUserRealm forcing all credentials to be String + + force close with shutdownOutput for win32 + + NPE protection if desirable client certificates + + Added provider support to SslListener + + logging improvements for servlet and runtime exceptions + + Fixed AJP handling of ;jsessionid. + + improved contentType param handling + +Jetty-5.1.10 - 5 January 2006 + + Fixed path aliasing with // on windows. + + Fix for AJP13 with multiple headers + + Fix for AJP13 with encoded path + + Remove null dispatch attributes from getAttributeNames + + Put POST content default back to iso_8859_1. GET is UTF-8 still + +Jetty-4.2.25 - 4 Jan 2006 + + Fixed aliasing of // for win32 + +Jetty-5.1.9 - 7 December 2005 + + Fixed wantClientAuth(false) overriding netClientAuth(true) + +Jetty-6.0.0betaX - + + See http://jetty.eclipse.org/jetty6 for 6.0 releases + +Jetty-5.1.8 - 7 December 2005 + + Fixed space in URL issued created in 5.1.6 + +Jetty-5.1.7 - 7 December 2005 +Jetty-5.1.7rc0 - 6 December 2005 + + improved server stats + + char encoding for MultiPartRequest + + fixed merging of POST params in dispatch query string. + + protect from NPE in dispatcher getValues + + Updated to 2.6.2 xerces + + JSP file servlet mappings copy JspServlet init params. + + Prefix servlet context logs with org.eclipse.jetty.context + + better support for URI character encodings + + use commons logging jar instead of api jar. + +Jetty-5.1.6 - 18 November 2005 + + CVE-2006-2758 Fixed JSP visibility security issue. + + Improved jetty-web.xml access to org.eclipse classes. + +Jetty-5.1.5 - 10 November 2005 + + Improved shutdown hook + + Improved URL Decoding + + Improved mapping of JSP files. + +Jetty-5.1.5rc2 - 7 October 2005 + + Reverted dispatcher params to RI rather than spec behaviour. + + ProxyHandler can handle chained proxies + + unsynchronized ContextLoader + + ReFixed merge of Dispatcher params + + public ServerMBean constructor + + UTF-8 encoding for URLs + + Response.setLocale will set locale even if getWriter called. + +Jetty-5.1.5rc1 - 23 August 2005 + + upgraded to commons logging 1.0.4 + + Release commons logging factories when stopping context. + + Fixed illegal state with chunks and 100 continue - Tony Seebregts + + Fixed PKCS12Import input string method + + Fixed merge of Dispatcher parameters + + Encoded full path in ResourceHandler directory listing + + handle extra params after charset in header + + Fixed 100-continues with chunking and early commit + +Jetty-5.1.5rc0 - 16 August 2005 + + Fixed component remove memory leak for stop/start cycles + + Facade over commons LogFactory so that discovery may be avoided. + + Applied ciphersuite patch from tonyj + + Authenticators use servlet sendError + + CGI sets SCRIPT_FILENAME + + HttpTunnel timeout + + NPE protection for double stop in ThreadedServer + + Expect continues only sent if input is read. + +Jetty-5.1.4 - 5 June 2005 + + Fixed FTP close issue. + + setup MX4J with JDK1.5 in start.config + + set classloader during webapp doStop + + NPE protection in ThreadedServer + + ModelMBean handles null signatures + + Change JAAS impl to be more flexible on finding roles + +Jetty-5.1.4rc0 - 19 April 2005 + + ServletHttpContext correctly calls super.doStop. + + HttpServer delegates component handling to Container. + + Allow ServletHandler in normal HttpContext again. + + Stop start.jar putting current directory on classpath. + + More protection from null classloaders. + + Turn off web.xml validation for JBoss. + +Jetty-5.1.3 - 7 April 2005 + + Some minor code janitorial services + +Jetty-4.2.24 - 7 April 2005 + +Jetty-5.1.3rc4 - 31 March 2005 + + Moved servlet request wrapping to enterContextScope for geronimo security + + refixed / mapping for filters + + Allow XmlConfiguration to start with no object. + + updated to mx4j 3.0.1 + + rework InitialContextFactory to use static 'default' namespace + + make java:comp/env immutable for webapps as per J2EE spec + +Jetty-5.1.3rc3 - 20 March 2005 + + removed accidental enablement of DEBUG for JettyPlus jndi in log4j.properties + + fixed "No getter or setter found" mbean errors + +Jetty-5.1.3rc2 - 16 March 2005 + + Updated JSR154Filter for ERROR dispatch + + Fixed context to _context refactory error + +Jetty-5.1.3rc1 - 13 March 2005 + + Fixed typo in context-param handling. + + update to demo site look and feel. + + Fixed principal naming in FormAuthenticator + + JettyPlus updated to JOTM 2.0.5, XAPool 1.4.2 + +Jetty-4.2.24rc1 + + Fixed principal naming in FormAuthenticator + +Jetty-5.1.3rc0 - 8 March 2005 + + Flush filter chain caches on servlet/filter change + + Fixed rollover filename format bug + + Fixed JSR154 error dispatch with explicit pass of type. + + Allow system and server classes to be configured for context loader. + + IOException if EOF read during chunk. + + Fixed HTAccess crypt salt handling. + + Added simple xpath support to XmlParser + + Added TagLibConfiguration to search for listeners in TLDs. + + Added SslListener for 1.4 JSSE API. + + Fixed moderate load preventing ThreadPool shrinking. + + Added logCookie and logLatency support to NCSARequestLog + + Added new JAAS callback to allow extra login form fields in authentication + +Jetty-4.2.24rc0 - 8 March 2005 + + Back ported Jetty 5 ThreadedServer and ThreadPool + + Added logCookie and logLatency support to NCSARequestLog + +Jetty-5.1.2 - 18 January 2005 + + Added id and ref support to XmlConfiguration + + Cleaned up AbstractSessionManager synchronization. + + Fixed potential concurrent login problem with JAAS + + Apply patch #1103953 + +Jetty-4.2.23 - 16 January 2005 + + Cleaned up AbstractSessionManager synchronization. + + Fixed potential concurrent login problem with JAAS + +Jetty-5.1.2pre0 - 22 December 2004 + + Fixed case of Cookie parameters + + Support Secure and HttpOnly in session cookies + + Modified useRequestedID handling to only use IDs from other contexts + + Added global invalidation to AbstractSessionManager + + UnavailableException handling from handle + + Fixed suffix filters + +Jetty-4.2.23RC0 - 17 December 2004 + + LineInput handles readers with small internal buffer + + Added LogStream to capture stderr and stdout to logging + + Support Secure and HttpOnly in session cookies + + Build unsealed jars + +Jetty-5.1.1 - 1 December 2004 + +Jetty-5.1.1RC1 + + Some minor findbugs code cleanups + + Made more WebApplicationHandle configuration methods public. + + Fixed ordering of filters with multiple interleaved mappings. + + Allow double // within URIs + + Applied patch for MD5 hashed credentials for MD5 + +Jetty-5.1.1RC0 - 17 November 2004 + + fix for adding recognized EventListeners + + fix commons logging imports to IbmJsseListener + + added new contributed shell start/stop script + + excluded ErrorPageHandler from standard build in extra/jdk1.2 build + +Jetty-5.1.0 - 14 November 2004 + +Jetty-5.1.RC1 - 24 October 2004 + + Allow JSSE listener to be just confidential or just integral. + + Fixed NPE for null contenttype + + improved clean targets + + when committed setHeader is a noop rather than IllegalStateException + + Partially flush writers on every write so content length can be detected. + + Build unsealed jars + + default / mapping does not apply to Filters + + many minor cleanups suggested from figbug utility + + Allow multiple accepting threads + +Jetty-5.1.RC0 - 11 October 2004 + + Fixed many minor issues from J2EE 1.4 TCK testing +See sf.net bugs 1031520 - 1032205 + + Refactored, simplified and optimized HttpOutputStream + + LineInput handles readers with small internal buffer + + Added LogStream to capture stderr and stdout to logging + + Added filter chain cache + + Added JSR77 servlet statistic support + + Refactored webapp context configurations + + Added LifeCycle events and generic container. + + Upgraded to ant-1.6 for jasper + + Fixed HTAccessHandler + + JBoss 4.0.0 support + +Jetty-5.0.0 - 10 September 2004 + +Jetty-5.0.RC4 - 5 September 2004 + + Fixed configuration of URL alias checking + + JettyJBoss: Use realm-name from web.xml if present, otherwise use security-domain from jboss-web.xml + +Jetty-5.0.RC3 - 28 August 2004 + + DIGEST auth handles qop, stale and maxNonceAge. + + Less verbose warning for non validating xml parser. + + fixed jaas logout for jetty-jboss + + fixed deployment of ejb-link elements in web.xml with jboss + + Update to jasper 5.0.27 + + Added parameters for acceptQueueSize and lowResources level. + + Changed default URI encoding to UTF-8 + + Fixes to work with java 1.5 + + JettyPlus upgrade to XAPool 1.3.3. and HSQLDB 1.7.2 + + JettyPlus addition of pluggable DataSources + + Always say close for HTTP/1.0 non keep alive. + +Jetty-4.2.22 - 23 August + + fixed jaas logout for jetty-jboss integration + + fixed deployment of ejb-link elements in web.xml for jboss + + Added parameters for acceptQueueSize and lowResources level. + +Jetty-5.0.RC2 - 2 July 2004 + + Fixed DIGEST challenge delimiters + + HTAccess calls UnixCrypt correctly + + integrated jetty-jboss with jboss-3.2.4 + + Error dispatchers are always GET requests. + + OPTIONS works for all URLs on default servlet + + add JMX support for JettyPlus + + add listing of java:comp/env for webapp with JMX + + make choice of override of JNDI ENC entries: config.xml or web.xml + + Default servlet may use only pathInfo for resource + + Fixed session leak in j2ee + + Fixed no-role security constraint combination. + + Fix to use runas roles during servlet init and destroy + + Fixed JAAS logout + + HttpContext sendError for authentication errors + +Jetty-4.2.21 - 2 July 2004 + + integrated jetty-jboss with jboss-3.2.4 + + add JMX support for JettyPlus + + add listing of java:comp/env for webapp with JMX + + make choice of override of JNDI ENC entries: config.xml or web.xml + + Fixed JAAS logout + +Jetty-5.0.RC1 - 24 May 2004 + + Changed to apache 2.0 license + + added extra/etc/start-plus.config to set up main.class for jettyplus + + maxFormContentLength may be unlimited with <0 value + + Fixed HTTP tunnel timeout setting. + + Improved handling of exception from servlet init. + + FORM auth redirects to context on a re-auth + + Handle multiple virutal hosts from JBoss 3.2.4RC2 + +Jetty-4.2.20 - 22 May 2004 + + maxFormContentLength may be unlimited with <0 value + + Fixed HTTP tunnel timeout setting. + + Improved handling of exception from servlet init. + + FORM auth redirects to context on a re-auth + +Jetty-5.0.0RC0 - 7 April 2004 + + Updated JettyPlus to JOTM 1.4.3 (carol-1.5.2, xapool-1.3.1) + + ServletContext attributes wrap HttpContext attributes. + + Factored out XML based config from WebApplicationContext + + Improved RequestLog performance + + Fixed j2se 1.3 problem with HttpFields + + Default servlet respectes servlet path + + Fixed setCharacterEncoding for parameters. + + Fixed DOS problem + + Worked around bad jboss URL handler in XMLParser + + Forced close of connections over stop/start + + ProxiedFor field support added to NCSARequestLog + + Fixed Default servlet for non empty servlet paths + + Updated mx4j to V2 + + Updated jasper to 5.0.19 + + Changed dist naming convention to lowercase + +Jetty-4.2.20RC0 - 7 April 2004 + + Worked around bad jboss URL handler in XMLParser + + Forced close of connections over stop/start + + HttpFields protected headers + + ProxiedFor field support added to NCSARequestLog + + Fixed Default servlet for non empty servlet paths + + Changed dist naming convention to lowercase + +Jetty-4.2.19 - 19 Mar 2004 + + Fixed DOS attack problem + +Jetty-5.0.beta2 - 12 Feb 2004 + + Added skeleton JMX MBean for jetty plus + + Fixed HEAD with empty chunk bug. + + Fixed jetty.home/work handling + + Fixed setDate thread safety + + Fixed SessionManager init + + Improved low thread handling + + FileResource better handles non sun JVM + + Monitor closes socket before exit + + Updated to Japser 5.0.16 + + RequestDispatcher uses request encoding for query params + + Fixed busy loop in threadpool run + + Reorganized ServletHolder init + + Added log4j context repository to jettyplus + + NPE guard for no-listener junit deployment + + Added experimental NIO listeners again. + + fixed filter dispatch configuration. + + fixed lazy authentication with FORMs + +Jetty-4.2.18 - 1 Mar 2004 + + Added log4j context repository to jettyplus + + NPE guard for no-listener junit deployment + + Improved log performance + + Fixed j2se 1.3 problem with HttpFields + + Suppress some more IOExceptions + + Default servlet respectes servlet path + +Jetty-4.2.17 - 1 Feb 2004 + + Fixed busy loop in threadpool run + + Reorganized ServletHolder init + +Jetty-4.2.16 - 30 Jan 2004 + + Fixed setDate multi-cpu race + + Improved low thread handling + + FileResource better handles non sun JVM + + Fixed HttpTunnel for JDK 1.2 + + Monitor closes socket before exit + + RequestDispatcher uses request encoding for query params + + Update jasper to 4.1.29 + +Jetty-5.0.beta1 - 24 December 2003 + + SecurityConstraints not reset by stop() on custom context + + Fixed UnixCrypt handling in HTAccessHandler + + Added patch for JBoss realm single sign on + + Reorganized FAQ + + Env variables for CGI + + Removed support for old JBoss clustering + +Jetty-4.2.15 - 24 December 2003 + + SecurityConstraints not reset by stop() on custom context + + Fixed UnixCrypt handling in HTAccessHandler + + Added patch for JBoss realm single sign on + + Environment variables for CGI + + Removed support for old JBoss clustering + +Jetty-5.0.beta0 - 22 November 2003 + + Removed support for HTTP trailers + + PathMap uses own Map.Entry impl for IBM JVMs + + Use ${jetty.home}/work or WEB-INF/work for temp directories if present + + Protect ThreadPool.run() from interrupted exceptions + + Added org.eclipse.http.ErrorHandler for error pages. + + Fixed init race in HttpFields cache + + Allow per listener handlers + + Added MsieSslHandler to handle browsers that don't grok persistent SSL (msie 5) + + Respect content length when decoding form content. + + JBoss integration uses writer rather than stream for XML config handling + + Expire pages that contain set-cookie as per RFC2109 recommendation + + Updated jasper to 5.0.14beta + + Removed the CMR/CMP distributed session implementation + +Jetty-4.2.15rc0 - 22 November 2003 + + PathMap uses own Map.Entry impl for IBM JVMs + + Race in HttpFields cache + + Use ${jetty.home}/work or WEB-INF/work for temp directories if present + + Protect ThreadPool.run() from interrupted exceptions + + Added org.eclipse.http.ErrorHandler for error pages. + + JsseListener checks UserAgent for browsers that can't grok persistent SSL (msie5) + + Removed the CMR/CMP distributed session implementation + +Jetty-4.2.14 - 04 November 2003 + + respect content length when decoding form content. + + JBoss integration uses writer rather than stream for XML config handling + + Fixed NPE in SSO + + Expire pages that contain set-cookie as per RFC2109 recommendation + +Jetty-5.0.alpha3 - 19 October 2003 + + Reworked Dispatcher to better support cross context sessions. + + Use File.toURI().toURL() when jdk 1.2 alternative is available. + + Priority added to ThreadPool + + replaced win32 service with http://wrapper.tanukisoftware.org + + FileClassPath derived from walk of classloader hierarchy. + + Implemented security constraint combinations + + Set TransactionManager on JettyPlus datasources and pools + + Fixed null pointer if no sevices configured for JettyPlus + + Updated jasper and examples to 5.0.12 + + Lazy authentication if no auth constraint. + + Restore servlet handler after dispatch + + Allow customization of HttpConnections + + Failed requests excluded from duration stats + +Jetty-4.2.14RC1 - 19 October 2003 + + Reworked Dispatcher to better support cross context sessions. + + Added UserRealm.logout and arrange for form auth + + Allow customization of HttpConnections + + Failed requests excluded from + +Jetty-4.2.14RC0 - 7 October 2003 + + Correctly setup context classloader in cross context dispatch. + + Put a semi busy loop into proxy tunnels for IE problems + + Fixed handling of error pages for IO and Servlet exceptions + + updated extra/j2ee to jboss 3.2.1+ + + Use File.toURI().toURL() when jdk 1.2 alternative is available. + + cookie timestamps are in GMT + + Priority on ThreadedServer + + replaced win32 service with http://wrapper.tanukisoftware.org + + Build fileclasspath from a walk of the classloaders + + Set TransactionManager on JettyPlus datasources and pools + + Fixed null pointer if no sevices configured for JettyPlus + + Fixed comments with embedded double dashes on jettyplus.xml file + +Jetty-5.0.alpha2 - 19 September 2003 + + Use commons logging. + + Use log4j if extra is present. + + Improved JMX start. + + Update jakarta examples + + Correctly setup context classloader in cross context dispatch. + + Turn off validation without non-xerces errors + + minor doco updates. + + moved mailing lists to sourceforge. + + Put a semi busy loop into proxy tunnels for IE problems + + MultipartRequest supports multi value headers. + + XML entity resolution uses URLs not Resources + + Implemented ServletRequestListeners as optional filter. + + Moved error page mechanism to be webapp only. + + Fixed error page handling of IO and Servlet exceptions. + +Jetty-5.0.alpha1 - 12 August 2003 + + Switched to mx4j + + Improve combinations of Security Constraints + + Implemented locale encoding mapping. + + Synced with 4.2.12 + + Updated to Jasper 5.0.7 + + Server javadoc from war + +Jetty-5.0.alpha0 - 16 Jul 2003 + + Compiled against 2.4 servlet spec. + + Implemented remote/local addr/port methods + + Updated authentication so that a normal Principal is used. + + updated to jasper 5.0.3 + + Implemented setCharaterEncoding + + Implemented filter-mapping element + + Implemented Dispatcher forward attributes. + +Jetty-4.2.12 - 12 August 2003 + + Restore max inactive interval for session manager + + Removed protection of org.eclipse.http attributes + + Fixed parameter ordering for a forward request. + + Fixed up HTAccessHandler + + Improved error messages from ProxyHandler + + Added missing S to some OPTIONS strings + + Added open method to threaded server. + + FORMAuthenticator does 403 with empty error page. + + Fixed MIME types for chemicals + + Padding for IE in RootNotFoundHandler + +Jetty-4.2.11 - 12 July 2003 + + Fixed race in servlet initialization code. + + Cookie params all in lower case. + + Simplified AJP13 connection handling. + + Prevent AJP13 from reordering query. + + Support separate Monitor class for start + + Branched for Jetty 5 development. + +Jetty-4.2.10 - 7 July 2003 + + Updates to JettyPlus documentation + + Updates to Jetty tutorial for start.jar, jmx etc + +Jetty-4.2.10pre2 - 4 July 2003 + + Improvement to JettyPlus config of datasources and connection pools + + Addition of mail service for JettyPlus + + Move to Service-based architecture for JettyPlus features + + Re-implementation of JNDI + + Many improvements in JettyPlus java:comp handling + + Allow multiple security-role-ref elements per servlet. + + Handle Proxy-Connection better + + Cleaned up alias handling. + + Confidential redirection includes query + + handle multiple security role references + + Fixed cookie handling for old cookies and safari + + Restricted ports in ProxyHandler. + + URI always encodes % + + Session statistics + + XmlConfiguration can get/set fields. + +Jetty-4.2.10pre1 - 2 June 2003 + + Fixed JSP code visibility problem introduced in Jetty-4.2.10pre0 + + Added stop.jar + + Added SSO implementation for FORM authentication. + + WebApplicationContext does not reassign defaults descriptor value. + + Fixed AJP13 protocol so that request/response header enums are correct. + + Fixed form auth success redirect after retry, introduced in 4.2.9rc1 + + Trace support is now optional (in AbstractHttpHandler). + + Deprecated forced chunking. + + Form authentication remembers URL over 403 + + ProxyHandler has improved test for request content + + Removed support of org.eclipse.http.User role. + + Fixed problem with shared session for inter context dispatching. + +Jetty-4.2.10pre0 - 5 May 2003 + + Moved Log4JLogSink into JettyPlus + + Added ability to override jetty startup class by using -Djetty.server on runline + + Incorporate JettyPlus jotm etc into build. + + Massive reorg of the CVS tree. + + Incorporate jetty extra and plus into build + + Integrate with JAAS + + Apply the append flag of RolloverFileOutputStream constructor. + + RolloverFileOutputStream manages Rollover thread. + + New look and feel for www site. + + Fixed table refs in JDBCUserRealm. + + Allow params in form auth URLs + + Updated to jasper jars from tomcat 4.1.24 + + Allow query params in error page URL. + + ProxyHandler checks black and white lists for Connect. + + Merge multivalued parameters in dispatcher. + + Fixed CRLF bug in MultiPartRequest + + Warn if max form content size is reached. + + getAuthType returns CLIENT_CERT instead of CLIENT-CERT. + + getAuthType maps the HttpServletRequest final strings. + + FORM Authentication is serializable for session distribution. + +Jetty-4.2.9 - 19 March 2003 + + Conditional headers check after /dir to /dir/ redirection. + +Jetty-4.2.9rc2 - 16 March 2003 + + Fixed build.xml for source release + + Made rfc2068 PUT/POST Continues support optional. + + Defaults descriptor has context classloader set. + + Allow dispatch to j_security_check + + Added X-Forwarded-For header in ProxyHandler + + Updated included jmx jars + +Jetty-4.2.9rc1 - 6 March 2003 + + Work around URLClassloader not handling leading / + + Dump servlet can load resources for testing now. + + Added trust manager support to SunJsseListener. + + Added support for client certs to AJP13. + + Cleaned up includes + + Removed checking for single valued headers. + + Optional 2.4 behaviour for sessionDestroyed notification. + + Stop proxy url from doing user interaction. + + Turn request log buffering off by default. + + Reduced default context cache sizes (Total 1MB file 100KB). + + ProxyHandler has black and white host list. + + Added requestlog to HttpContext. + + Allow delegated creation of WebApplication derivations. + + Check Data contraints before Auth constraints + +Jetty-4.2.8_01 - 18 February 2003 + + Patched first release of 4.2.8 with correct version number + + Fixed CGI servlet to handle multiple headers. + + Added a SetResponseHeadersHandler, can set P3P headers etc. + + ProxyHandler can handle multiple cookies. + + Fixed AdminServlet to handle changed getServletPath better. + + Default servlet can have own resourceBase. + + Rolled back SocketChannelListener to 4.2.5 version + + Added option to resolve remote hostnames. Defaults to off. + + Added MBeans for Servlets and Filters + + Moved ProxyHandler to the src1.4 tree + +Jetty-4.2.7 - 4 February 2003 + + Upgraded to JSSE 1.0.3_01 to fix security problem. + + Fixed proxy tunnel for non persistent connections. + + Relative sendRedirect handles trailing / correctly. + + Changed PathMap to conform to / getServletPath handling. + +Jetty-4.2.6 - 24 January 2003 + + Improved synchronization on AbstractSessionManager. + + Allow AJP13 buffers to be resized. + + Fixed LineInput problem with expanded buffers. + + ClientCertAuthentication updates request. + + Fixed rel sendRedirects for root context. + + Added HttpContext.setHosts to restrict context by real interface. + + Added MBeans for session managers + + Improved SocketChannelListener contributed. + + Added version to HttpServerMBean. + +Jetty-4.2.5 - 14 January 2003 + + Fixed pathParam bug for ;jsessionid + + Don't process conditional headers and ranges for includes + + Added Log4jSink in the contrib directory. + + Fixed requestedSessionId null bug. + +Jetty-4.2.4 - 4 January 2003 + + Fixed stop/start handling of servlet context + + Reuse empty LogSink slots. + + HTAccessHandler checks realm as well as htpassword. + + Clear context listeners after stop. + + Clear context attributes after stop. + + Use requestedSessionId as default session ID. + + Added MBeans for handlers + + Upgraded jasper to 4.1.18 + +Jetty-4.2.4rc0 - 12 December 2002 + + Simplified ThreadedServer + + Use ThreadLocals for ByteArrayPool to avoid synchronization. + + Use Version to reset HttpFields + + Cheap clear for HttpFields + + Fixed setBufferSize NPE. + + Cleaned up some unused listener throws. + + Handle chunked form data. + + Allow empty host header. + + Avoid optional 100 continues. + + Limit form content size. + + Handle = in param values. + + Added HttpContext.flushCache + + Configurable root context. + + RootNotFoundHandler to help when no context found. + + Update jasper to 4.1.16beta + + Fixed dir listing from jars. + + Dir listings in UTF8 + + Character encoding handling for GET requests. + + Removed container transfer encoding handling. + + Improved setBufferSize handling + + Code logs objects rather than strings. + + Better access to session manager. + + Fixed isSecure and getScheme for SSL over AJP13 + + Improved ProxyHandler to the point is works well for non SSL. + + Implemented RFC2817 CONNECT in ProxyHandler + + Added gzip content encoding support to Default and ResourceHandler + +Jetty-4.2.3 - 2 December 2002 + + Removed aggressive threadpool shrinkage to avoid deadlock on SMP machines. + + Fixed some typos + + Added links to Jetty Powered page + + Clean up of ThreadedServer.stop() + + Updated bat scripts + + Added PKCS12Import class to import PKCS12 key directly + + removed old HttpContext.setDirAllowed() + + added main() to org.eclipse.http.Version + + Check form authentication config for leading / + + Cleaner servlet stop to avoid extra synchronization on handle + + org.eclipse.http.HttpContext.FileClassPathAttribute + +Jetty-4.2.2 - 20 November 2002 + + Fixed sendRedirect for non http URLS + + Fixed URI query recycling for persistent connections + + Fixed handling of empty headers + + Added EOFException to reduce log verbosity on closed connections. + + Avoided bad buffer status after closed connection. + +Jetty-4.2.1 - 18 November 2002 + + Fixed bad optimization in UrlEncoding + + Re-enabled UrlEncoding test harnesses + +Jetty-4.2.0 - 16 November 2002 + + Fixed AJP13 buffer size. + + Fixed remove listener bug. + + Fixed include of Invoker servlet. + + Restrict 304 responses to seconds time resolution. + + Use IE date formatting for speed. + + Removed jasper source and just include jars from 4.1.12 + + Worked around JVM1.3 bug for JSPs + + Lowercase jsessionid for URLs only. + + Made NCSARequestLog easier to extend. + + Added definitions for RFC2518 WebDav response codes. + + Removed remaining non portable getBytes() calls + + Added upload demo to dump servlet. + + Many more optimizations. + +Jetty-4.1.4 - 16 November + + Fixed ContextLoader parent delegation bug + + Fixed remove SocketListener bug. + + Fixed Invoker servlet for RD.include + + Use IE date formatting for last-modified efficiency + + Last modified handling uses second resolution. + + Made NCSARequestLog simpler to extend. + +Jetty-4.2.0rc1 - 2 November 2002 + + Support default mime mapping defined by * + + Recycling of HttpFields class. + + Renamed Filter application methods. + + Fixed firstWrite after commit. + + Fixed ContextLoader parent delegation bug. + + Fixed problem setting the size of chunked buffers. + + Removed unused Servlet and Servlet-Engine headers. + + Fixed servletpath on invoker for named servlets. + + Fixed directory resource bug in JarFileResource. + + Improved handling of 2 byte encoded characters within forms. + +Jetty-4.2.0rc0 - 24 October 2002 + + Greg's birthday release! + + Added embedded iso8859 writer to HttpOutputStream. + + Removed duplicate classes from jar + + Fixed RolloverFileOutputStream without date. + + Fixed SessionManager initialization + + Added authenticator to admin.xml + + Fixed Session timeout NPE. + + Jetty-4.1.3 - 24 October 2002 + + Fixed RolloverFileOutputStream without date. + + Fixed SessionManager initialization + + Added authenticator to admin.xml + + Fixed Session timeout NPE. + +Jetty-4.0.6 - 24 October 2002 + + Clear interrupted status in ThreadPool + + Fixed forward query string handling + + fixed forward attribute handling for jsp-file servlets + + Fixed setCharacterEncoding to work with getReader + + Fixed handling of relative sendRedirect after forward. + + Fixed virtual hosts temp directories. + +Jetty-4.2.0beta0 - 13 October 2002 + + New ThreadPool implementation. + + New Buffering implementation. + + New AJP13 implementation. + + Removed Dispatcher dependancy on ServletHttpContext + + getNamedDispatcher(null) returns containers default servlet. + + unquote charset in content type + + Stop/Start filters in declaration order. + + Use "standard" names for default,jsp & invoker servlets. + + Fixed caching of directories to avoid shared buffers. + + Fixed bad log dir detection + + Fix Session invalidation bug + + Build without jmx + + 404 instead of 403 for WEB-INF requests + + FORM authentication sets 403 error page + + Allow %3B encoded ; in URLs + + Allow anonymous realm + + Update jasper to 4.1.12 tag + +Jetty-4.1.2 - 13 October 2002 + + Some AJP13 optimizations. + + getNamedDispatcher(null) returns containers default servlet. + + unquote charset in content type + + Stop/Start filters in declaration order. + + Use "standard" names for default,jsp & invoker servlets. + + Fixed caching of directories to avoid shared buffers. + + Fixed bad log dir detection + + Fix Session invalidation bug + + Build without jmx + + 404 instead of 403 for WEB-INF requests + + FORM authentication sets 403 error page + + Allow %3B encoded ; in URLs + + Allow anonymous realm + + Update jasper to 4.1.12 tag + +Jetty-4.1.1 - 30 September 2002 + + Fixed client scripting vulnerability with jasper2. + + Merged LimitedNCSARequestLog into NCSARequestLog + + Fixed space in resource name handling for jdk1.4 + + Moved launcher/src to src/org/eclipse/start + + Fixed infinite recursion in JDBCUserRealm + + Avoid setting sotimeout for optimization. + + String comparison of If-Modified-Since headers. + + Touch files when expanding jars + + Deprecated maxReadTime. + + Cache directory listings. + +Jetty-4.1.0 - 22 September 2002 + + Fixed CGI+windows security hole. + + Fixed AJP13 handling of mod_jk loadbalancing. + + Stop servlets in opposite order to start. + + NCSARequest log buffered default + + WEB-INF/classes before WEB-INF/lib + + Sorted directory listings. + + Handle unremovable tempdir. + + Context Initparams to control session cookie domain, path and age. + + ClientCertAuthenticator protected from null subjectDN + + Added LimitedNCSARequestLog + + Use javac -target 1.2 for normal classes + +Jetty-4.1.0RC6 - 14 September 2002 + + Don't URL encode FileURLS. + + Improved HashUserRealm doco + + FormAuthenticator uses normal redirections now. + + Encode URLs of Authentication redirections. + + Added logon.jsp for no cookie form authentication. + + Extended Session API to pass request for jvmRoute handling + + Fixed problem with AJP 304 responses. + + Improved look and feel of demo + + Cleaned up old debug. + + Added redirect to welcome file option. + +Jetty-4.1.0RC5 - 8 September 2002 + + AJP13Listener caught up with HttpConnection changes. + + Added commandPrefix init param to CGI + + More cleanup in ThreadPool for idle death. + + Improved errors for misconfigured realms. + + Implemented security-role-ref for isUserInRole. + +Jetty-4.1.0RC4 - 30 August 2002 + + Included IbmJsseListener in the contrib directory. + + Updated jasper2 to 4.1.10 tag. + + Reverted to 302 for all redirections as all clients do not understand 303 + + Created statsLock sync objects to avoid deadlock when stopping. + +Jetty-4.1.0RC3 - 28 August 2002 + + Fixed security problem for suffix matching with trailing "/" + + addWebApplications encodes paths to allow for spaces in file names. + + Improved handling of PUT,DELETE & MOVE. + + Improved handling of path encoding in Resources for bad JVMs + + Added buffering to request log + + Created and integrated the Jetty Launcher + + Made Resource canonicalize it's base path for directories + + Allow WebApplicationHandler to be used with other handlers. + + Added defaults descriptor to addWebApplications. + + Allow FORM auth pages to be within security constraint. + +Jetty-4.1.0RC2 - 20 August 2002 + + Conveninace setClassLoaderJava2Compliant method. + + Clear interrupted status in ThreadPool + + Fixed HttpFields cache overflow + + Improved ByteArrayPool to handle multiple sizes. + + Added HttpListener.bufferReserve + + Use system line separator for log files. + + Updated to Jasper2 (4_1_9 tag) + + Build ant, src and zip versions with the release + +Jetty-4.1.0RC1 - 11 August 2002 + + Fixed forward query string handling + + Fixed setCharacterEncoding to work with getReader + + Fixed getContext to use canonical contextPathSpec + + Improved the return codes for PUT + + Made HttpServer serializable + + Updated international URI doco + + Updated jasper to CVS snapshot 200208011920 + + Fixed forward to jsp-file servlet + + Fixed handling of relative sendRedirect after forward. + +Jetty-4.1.0RC0 - 31 July 2002 + + Fixed getRealPath for packed war files. + + Changed URI default charset back to ISO_8859_1 + + Restructured Password into Password and Credentials + + Added DigestAuthenticator + + Added link to a Jetty page in Korean. + + Added ExpiryHandler which can set a default Expires header. + +Jetty-4.0.5 - 31 July 2002 + + Fixed getRealPath for packed war files. + + Reversed order of ServletContextListener.contextDestroyed calls + + Fixed getRequestURI for RD.forward to return new URI. + +Jetty-4.1.B1 - 19 July 2002 + + Updated mini.http.jar target + + CGI Servlet, pass all HTTP headers through. + + CGI Servlet, catch and report program invocation failure status. + + CGI Servlet, fixed suffix mapping problem. + + CGI Servlet, set working directory for exec + + Support HTTP/0.9 requests again + + Reversed order of ServletContextListener.contextDestroyed calls + + Moved dynamic servlet handling to Invoker servlet. + + Moved webapp resource handling to Default servlet. + + Sessions create attribute map lazily. + + Added PUT,DELETE,MOVE support to webapps. + + Added 2.4 Filter dispatching support. + +Jetty-3.1.9 - 15 July 2002 + + Allow doHead requests to be forwarded. + + Fixed race in ThreadPool for minThreads <= CPUs + +Jetty-4.1.B0 - 13 July 2002 + + Added work around of JDK1.4 bug with NIO listener + + Moved 3rd party jars to $JETTY_HOME/ext + + Fixed ThreadPool bug when minThreads <= CPUs + + close rather than disable stream after forward + + Allow filter init to access servlet context methods. + + Keep notFoundContext out of context mapping lists. + + mod_jk FAQ + + Fixed close problem with load balancer. + + Stopped RD.includes closing response. + + RD.forward changes getRequestURI. + + NCSARequestLog can log to stderr + +Jetty-4.1.D2 - 24 June 2002 + + Support trusted external authenticators. + + Moved jmx classes from JettyExtra to here. + + Set contextloader during webapplicationcontext.start + + Added AJP13 listener for apache integration. + + Fixed ChunkableOutputStream close propagation + + Better recycling of HttpRequests. + + Protect session.getAttributeNames from concurrent modifications. + + Allow comma separated cookies and headers + + Back out Don't chunk 30x empty responses. + + Conditional header tested against welcome file not directory. + + Improved ThreadedServer stopping on bad networks + + Use ThreadLocals to avoid unwrapping in Dispatcher. + +Jetty-4.0.4 - 23 June 2002 + + Back out change: Don't chunk 30x empty responses. + + Conditional header tested against welcome file not directory. + + Improved ThreadedServer stopping on bad networks + +Jetty-4.0.3 - 20 June 2002 + + WebapplicationContext.start sets context loader + + Fixed close propagation of on-chunked output streams + + Force security disassociation. + + Better recycling of HttpRequests. + + Protect session.getAttributeNames from concurrent modifications. + + Allow session manager to be initialized when set. + + Fixed japanese locale + + Allow comma separated cookies and headers + +Jetty-4.1.D1 - 8 June 2002 + + Recycle servlet requests and responses + + Added simple buffer pool. + + Reworked output buffering to keep constant sized buffers. + + Don't chunk 30x empty responses. + + Fixed "" contextPaths in Dispatcher. + + Removed race for the starting of session scavaging + + Fixed /foo/../bar// bug in canonical path. + + Merged ResourceBase and SecurityBase into HttpContext + +Jetty-4.0.2 - 6 June 2002 + + Fixed web.dtd references. + + Fixed handler/context start order. + + Added OptimizeIt plug + + Fixed /foo/../bar// bug in canonical path. + + Don't chunk 30x empty responses. + + Fixed "" contextPaths in Dispatcher. + + Removed race for the starting of session scavaging + +Jetty-3.1.8 - 6 June 2002 + + Made SecurityConstraint.addRole() require authentication. + + Fixed singled threaded dynamic servlets + + Fixed no slash context redirection. + + Fixed /foo/../bar// bug in canonical path. + +Jetty-4.1.D0 - 5 June 2002 + + The 4.1 Series started looking for even more performance +within the 2.3 specification. + + Removed the HttpMessage facade mechanism + + BRAND NEW WebApplicationHandler & WebApplicationContext + + Added TypeUtil to reduce Integer creation. + + General clean up of the API for for MBean getters/setters. + + Experimental CLIENT-CERT Authenticator + + Restructured ResourceHandler into ResourceBase + + Fixed web.dtd references. + + Fixed handler/context start order. + + Added OptimizeIt plug. + +Jetty-4.0.1 - 22 May 2002 + + Fixed contextclassloader on ServletContextEvents. + + Support graceful stopping of context and server. + + Fixed "null" return from getRealPath + + OutputStreamLogSink config improvements + + Updated jasper to 16 May snapshot + +Jetty-4.0.1RC2 - 14 May 2002 + + Better error for jre1.3 with 1.4 classes + + Cleaned up RD query string regeneration. + + 3DES Keylength was being reported as 0. Now reports 168 bits. + + Implemented the run-as servlet tag. + + Added confidential and integral redirections to HttpListener + + Fixed ServletResponse.reset() to resetBuffer. + +Jetty-4.0.1RC1 - 29 April 2002 + + Improved flushing of chunked responses + + Better handling if no realm configured. + + Expand ByteBuffer full limit with capacity. + + Fixed double filtering of welcome files. + + Fixed FORM authentication auth of login page bug. + + Fixed setTempDirectory creation bug + + Avoid flushes during RequestDispatcher.includes + +Jetty-4.0.1RC0 - 18 April 2002 + + Updated Jasper to CVS snapshot from Apr 18 18:50:59 BST 2002 + + Pass pathParams via welcome file forward for jsessionid + + Extended facade interfaces to HttpResponse.sendError + + Moved basic auth handling to HttpRequest + + AbstractSessionManager sets contextClassLoader for scavanging + + Set thread context classloader for webapp load-on-startup inits + + Added extract arg to addWebApplications + + Fixed delayed response bug: +Stopped HttpConnection consuming input from timedout connection. + + DTD allows static "Get" and "Set" methods to be invoked. + +Jetty-4.0.0 - 22 March 2002 + + Updated tutorial configure version + + Added IPAddressHandler for IP restrictions + + Updated contributors. + + Minor documentation updates. + + Jetty.sh cygwin support + +Jetty-4.0.RC3 - 20 March 2002 + + Fixed ZZZ offset format to +/-HHMM + + Updated history + + JDBCUserRealm instantiates JDBC driver + + ContextInitialized notified before load-on-startup servlets. + + Suppress WriterOutputStream warning. + + Changed html attribute order for mozilla quirk. + +Jetty-4.0.RC2 - 12 March 2002 + + Fixed security constraint problem with // + + Fixed version for String XmlConfigurations + + Fixed empty referrer in NCSA log. + + Dont try to extract directories + + Added experimental nio SocketChannelListener + + Added skeleton load balancer + + Fixed column name in JDBCUserRealm + + Remove last of the Class.forName calls. + + Removed redundant sessionID check. + + Security FAQ + + Disabled the Password EXEC mechanism by default + +Jetty-3.1.7 - 12 Mar 2002 + + Fixed security problem with constraints being bypassed with // +in URLs + +Jetty-4.0.RC1 - 06 March 2002 + + Added ContentEncodingHandler for compression. + + Fixed filter vs forward bug. + + Improved efficiency of quality list handling + + Simplified filter API to chunkable streams + + XmlParser is validating by default. use o.m.x.XmlParser.NotValidating property to change. + + contextDestroyed event sent before destruction. + + Minor changes to make HttpServer work on J2ME CVM + + Warn if jdk 1.4 classes used on JVM <1.4 + + WebApplication will use ContextLoader even without WEB-INF directory. + + FileResource depends less on FilePermissions. + + Call response.flushBuffer after service to flush wrappers. + + Empty suffix for temp directory. + + Contributors list as an image to prevent SPAM! + + Fixed recursive DEBUG loop in Logging. + + Updated jetty.sh to always respect arguments. + +Jetty-3.1.6 - 28 Feb 2002 + + Implemented 2.3 clarifications to security constraint semantics +PLEASE REVIEW YOUR SECURITY CONSTRAINTS (see README). + + Empty suffix for temp directory. + + Fixed HttpFields remove bug + + Set Listeners default scheme + + LineInput can handle any sized marks + + HttpResponse.sendError makes a better attempt at finding an error page. + + Dispatcher.forward dispatches directly to ServletHolder to avoid +premature exception handling. + +Jetty-4.0.B2 - 25 Feb 2002 + + Minor Jasper updates + + Improve handling of unknown URL protocols. + + Improved default jetty.xml + + Adjust servlet facades for welcome redirection + + User / mapping rather than /* for servlet requests to static content + + Accept jetty-web.xml or web-jetty.xml in WEB-INF + + Added optional JDK 1.4 src tree + + o.m.u.Frame uses JDK1.4 stack frame handling + + Added LoggerLogSink to direct Jetty Logs to JDK1.4 Log. + + Start ServletHandler as part of the FilterHandler start. + + Simplified addWebApplication + + Added String constructor to XmlConfiguration. + + Added org.eclipse.http.JDBCUserRealm + + Init classloader for JspServlet + + Slightly more agressive eating unused input from non persistent connection. + +Jetty-4.0.B1 - 13 Feb 2002 + + WriterOutputStream so JSPs can include static resources. + + Suppress error only for IOExceptions not derivitives. + + HttpConnection always eats unused bodies + + Merged HttpMessage and Message + + LineInput waits for LF after CF if seen CRLF before. + + Added setClassLoader and moved getFileClassPath to HttpContext + + Updated examples webapp from tomcat + + getRequestURI returns encoded path + + Servlet request destined for static content returns paths as default servlet + +Jetty-4.0.B0 - 4 Feb 2002 + + Implemented 2.3 security constraint semantics +PLEASE REVIEW YOUR SECURITY CONSTRAINTS (see README). + + Stop and remove NotFound context for HttpServer + + HttpContext destroy + + Release process builds JettyExtra + + Welcome files may be relative + + Fixed HttpFields remove bug + + Added Array element to XMLConfiguration + + Allow listener schemes to be set. + + Added index links to tutorial + + Renamed getHttpServers and added setAnonymous + + Updated crimson to 1.1.3 + + Added hack for compat tests in watchdog for old tomcat stuff + + Added AbstractSessionManager + + Support Random Session IDs in HashSessionManager. + + Common handling of TRACE + + Updated tutorial and FAQ + + Reduce object count and add hash width to StringMap + + Factor out RolloverFileOutputStream from OutputStreamLogSink + + Remove request logSink and replace with RequestLog using +RolloverFileOutputStream + + Handle special characters in resource file names better. + + Welcome file dispatch sets requestURI. + + Removed triggers from Code. + +Jetty-4.0.D4 - 14 Jan 2002 + + Prevent output after forward + + Handle ServletRequestWrappers for Generic Servlets + + Improved handling of UnavailableException + + Extract WAR files to standard temp directory + + URI uses UTF8 for % encodings. + + Added BlueRibbon campaign. + + RequestDispatcher uses cached resources for include + + Improved HttpResponsse.sendError error page matching. + + Fixed noaccess auth demo. + + FORM auth caches UserPrincipal + + Added isAuthenticated to UserPrincipal + +Jetty-4.0.D3 - 31 Dec 2001 + + Fixed cached filter wrapping. + + Fixed getLocale again + + Patch jasper to 20011229101000 + + Removed limits on mark in LineInput. + + Corrected name to HTTP_REFERER in CGI Servlet. + + Fixed UrlEncoding for % + combination. + + Generalized temp file handling + + Fixed ContextLoader lib handling. + + DateCache handles misses better. + + HttpFields uses DateCache more. + + Moved admin port to 8081 to avoid JBuilder + + Made Frame members private and fixed test harness + + cookies with maxAge==0 expire on 1 jan 1970 + + setCookie always has equals + +Jetty-3.1.5 - 11 Dec 2001 + + setCookie always has equals for cookie value + + cookies with maxage==0 expired 1 jan 1970 + + Fixed formatting of redirectURLs for NS4.08 + + Fixed ChunableInputStream.resetStream bug. + + Ignore IO errors when trying to persist connections. + + Allow POSTs to static resources. + + stopJob/killStop in ThreadPool to improve stopping +ThreadedServer on some platforms. + + Branched at Jetty_3_1 + +Jetty-4.0.D2 - 2 Dec 2001 + + Removed most of the old doco, which needs to be +rewritten and added again. + + Restructured for demo and test hierarchies + + Fixed formatting of redirect URLs. + + Removed ForwardHandler. + + Removed Demo.java (until updated). + + Made the root context a webapplication. + + Moved demo docroot/servlets to demo directory + + added addWebApplications auto discovery + + Disabled last forwarding by setPath() + + Removed Request set methods (will be replaced) + + New event model to decouple from beans container. + + Better handling of charset in form encoding. + + Allow POSTs to static resources. + + Fixed ChunableInputStream.resetStream bug. + + Ignore IO errors when trying to persist connections. + + Allow POSTs to static resources. + + stopJob/killStop in ThreadPool to improve stopping +ThreadedServer on some platforms. + +Jetty-4.0.D1 - 14 Nov 2001 + + Fixed ServletHandler with no servlets + + Fixed bug with request dispatcher parameters + + New ContextLoader implementation. + + New Dispatcher implementation + + Added Context and Session Event Handling + + Added FilterHolder + + Added FilterHandler + + Changed HandlerContext to HttpContext + + Simplified ServletHandler + + Removed destroy methods + + Simplified MultiMap + +Jetty-4.0.D0 - 06 Nov 2001 + + Branched from Jetty_3_1 == Jetty_3_1_4 + + 2.3 Servlet API + + 1.2 JSP API + + Jasper from tomcat4 + + Start SessionManager abstraction. + + Added examples webapp from tomcat4 + + Branched at Jetty_3_1 + +Jetty-3.1.4 - 06 Nov 2001 + + Added RequestLogFormat to allow extensible request logs. + + Support the ZZZ timezone offset format in DateCache + + HTAccessHandler made stricter on misconfiguration + + Generate session unbind events on a context.stop() + + Default PathMap separator changed to ":," + + PathMap now ignores paths after ; or ? characters. + + Remove old stuff from contrib that had been moved to extra + + getRealPath accepts \ URI separator on platforms using \ file separator. + +Jetty-3.1.3 - 26 Oct 2001 + + Fix security problem with trailing special characters. +Trailing %00 enabled JSP source to be viewed or other +servlets to be bypassed. + + Fixed several problems with external role authentication. +Role authentication in JBoss was not working correctly and +there were possible object leaks. The fix required an API +change to UserPrinciple and UserRealm. + + Allow a per context UserRealm instance. + + Upgraded JSSE to 1.0.2 + + Improved FORM auth handling of role failure. + + Improved Jasper debug output. + + Improved ThreadedServer timeout defaults + + Fixed binary files in CVS + + Fixed Virtual hosts to case insensitive. + + PathMap spec separator changed from ',' to ':'. May be set with +org.eclipse.http.PathMap.separators system property. + + Correct dispatch to error pages with javax attributes set. + +Jetty-3.1.2 - 13 Oct 2001 + + Fixed double entry on PathMap.getMatches + + Fixed servlet handling of non session url params. + + Fixed attr handling in XmlParser.toString + + Fixed request log date formatting + + Fixed NotFoundHandler handling of unknown methods + + Fixed FORM Authentication username. + + Fixed authentication role handling in FORM auth. + + FORM authentication passes query params. + + Added short delay to shutdown hook for JVM bug. + + Added ServletHandler.sessionCount() + + Added run target to ant + + Changed 304 responses for Opera browser. + + Changed JSESSIONID to jsessionid + + Log OK state after thread low warnings. + + Changed unsatisfiable range warnings to debug. + + Further improvements in handling of shutdown. + +Jetty-3.1.1 - 27 Sep 2001 + + Fixed jar manifest format - patched 28 Sep 2001 + + Removed JDK 1.3 dependancy + + Fixed ServletRequest.getLocale(). + + Removed incorrect warning for WEB-INF/lib jar files. + + Handle requestdispatcher during init. + + Use lowercase tags in html package to be XHTML-like. + + Correctly ignore auth-constraint descriptions. + + Reduced verbosity of bad URL errors from IIS virus attacks + +Jetty-3.1.0 - 21 Sep 2001 + + Added long overdue Tutorial documentation. + + Improved some other documentation. + + Fix ResourceHandler cache invalidate. + + Fix ServletResponse.setLocale() + + Fix reuse of Resource + + Fix Jetty.bat for spaces. + + Fix .. handling in URI + + Fix REFFERER in CGI + + Fix FORM authentication on exact patterns + + Fix flush on stop bug in logs. + + Fix param reading on CGI servlet + + New simplified jetty.bat + + Improved closing of listeners. + + Optimized List creation + + Removed win32 service.exe + + Added HandlerContext.registerHost + +Jetty-3.1.rc9 - 02 Sep 2001 + + Added bin/orgPackage.sh script to change package names. + + Changed to org.eclipse domain names. + + Form auth login and error pages relative to context path. + + Fixed handling of rel form authentication URLs + + Added support for Nonblocking listener. + + Added lowResourcePersistTimeMs for more graceful degradation when +we run out of threads. + + Patched Jasper to 3.2.3. + + Added handlerContext.setClassPaths + + Fixed bug with non cookie sessions. + + Format cookies in HttpFields. + +Jetty-3.1.rc8 - 22 Aug 2001 + + Support WEB-INF/web-jetty.xml configuration extension for webapps + + Allow per context log files. + + Updated sponsors page + + Added HttpServer statistics + + Don't add notfound context. + + Many major and minor optimizations: +* ISO8859 conversion +* Buffer allocation +* URI pathAdd +* StringMap +* URI canonicalPath +* OutputStreamLogSink replaces WriterLogSink + + Separation of URL params in HttpHandler API. + + Fixed handling of default mime types + + Allow contextpaths without leading / + + Removed race from dynamic servlet initialization. + +Jetty-3.1.rc7 - 9 Aug 2001 + + Fix bug in sendRedirect for HTTP/1.1 + + Added doco for Linux port redirection. + + Don't persist connections if low on threads. + + Added shutdown hooks to Jetty.Server to trap Ctl-C + + Fixed bug with session ID generation. + + Added FORM authentication. + + Remove old context path specs + + Added UML diagrams to Jetty architecture documentation. + + Use Enumerations to reduce conversions for servlet API. + + Optimized HttpField handling to reduce object creatiyon. + + ServletRequest SSL attributes in line with 2.2 and 2.3 specs. + + Dump Servlet displays cert chains + + Fixed redirect handling by the CGI Servlet. + + Fixed request.getPort for redirections from 80 + + Added utility methods to ServletHandler for wrapping req/res pairs. + + Added method handling to HTAccessHandler. + + ServletResponse.sendRedirect puts URLs into absolute format. + +Jetty-3.1.rc6 - 10 Jul 2001 + + Avoid script vulnerability in error pages. + + Close persistent HTTP/1.0 connections on missing Content-Length + + Use exec for jetty.sh run + + Improved SSL debugging information. + + KeyPairTool can now load cert chains. + + KeyPairTool is more robust to provider setup. + + Fixed bug in B64Code. Optimised B64Code. + + Added Client authentication to the JsseListener + + Fixed a problem with Netscape and the acrobat plugin. + + Improved debug output for IOExceptions. + + Updated to JSSE-1.0.2, giving full strength crypto. + + Win32 Service uses Jetty.Server instead of HttpServer. + + Added getResource to HandleContext. + + WebApps initialize resourceBase before start. + + Fixed XmlParser to handle xerces1.3 OK + + Added Get element to the XmlConfiguration class. + + Added Static calls to the XmlConfiguration class. + + Added debug and logging config example to demo.xml + + Moved mime types and encodings to property bundles. + + RequestDispatch.forward() uses normal HandlerContext.handle() +path if possible. + + Cleaned up destroy handling of listeners and contexts. + + Removed getConfiguration from LifeCycleThread to avoid JMX clash. + + Cleaned up Win32 Service server creation. + + Moved gimp image files to Jetty3Extra + +Jetty-3.1.rc5 - 1 May 2001 + + Added build target for mini.jetty.jar - see README. + + Major restructing of packages to separate servlet dependancies. + c.m.XML - moved XML dependant classes from c.m.Util + c.m.HTTP - No servlet or XML dependant classes: + c.m.Jetty.Servlet - moved from c.m.HTTP.Handler.Servlet + c.m.Servlet - received some servlet dependant classes from HTTP. + + Added UnixCrypt support to c.m.U.Password + + Added HTaccessHandler to authenitcate against apache .htaccess files. + + Added query param handling to ForwardHandler + + Added ServletHandler().setUsingCookies(). + + Optimized canonical path calculations. + + Warn and close connections if content-length is incorrectly set. + + Request log contains bytes actually returned. + + Fixed handling of empty responses at header commit. + + Fixed ResourceHandler handling of ;JSESSIONID + + Fixed forwarding to null pathInfo requests. + + Fixed handling of multiple cookies. + + Fixed EOF handling in MultiPartRequest. + + Fixed sync of ThreadPool idleSet. + + Fixed jetty.bat classpath problems. + +Jetty-3.0.6 - 26 Apr 2001 + + Fixed handling of empty responses at header commit. + + Fixed ResourceHandler handling of ;JSESSIONID + + Fixed forwarding to null pathInfo requests. + + Fixed EOF handlding in MultiPartRequest. + + Fixed sync of ThreadPool idleSet. + + Load-on-startup the JspServlet so that precompiled servlets work. + +Jetty-3.1.rc4 - 14 April 2001 + + Include full versions of JAXP and Crimson + + Added idle thread getter to ThreadPool. + + Load-on-startup the JspServlet so that precompiled servlets work. + + Removed stray debug println from the Frame class. + +Jetty-3.0.5 - 14 Apr 2001 + + Branched from 3.1 trunk to fix major errors + + Fixed LineInput bug EOF + + Improved flush ordering for forwarded requests. + + Turned off range handling by default until bugs resolved + + Don't chunk if content length is known. + + fixed getLocales handling of quality params + + Created better random session ID + + Resource handler strips URL params like JSESSION. + + Fixed session invalidation unbind notification to conform with spec + + Load-on-startup the JspServlet so that precompiled servlets work. + +Jetty-3.1.rc3 - 9 April 2001 + + Implemented multi-part ranges so that acrobat is happy. + + Simplified multipart response class. + + Improved flush ordering for forwarded requests. + + Improved ThreadPool stop handling + + Frame handles more JIT stacks. + + Cleaned up handling of exceptions thrown by servlets. + + Handle zero length POSTs + + Start session scavenger if needed. + + Added ContentHandler Observer to XmlParser. + + Allow webapp XmlParser to be observed for ejb-ref tags etc. + + Created better random session ID + +Jetty-3.1.rc2 - 30 Mar 2001 + + Lifecycle.start() may throw Exception + + Added MultiException to throw multiple nested exceptions. + + Improved logging of nested exceptions. + + Only one instance of default MIME map. + + Use reference JAXP1.1 for XML parsing.y + + Version 1.1 of configuration dtd supports New objects. + + Improved handling of Primitive classes in XmlConfig + + Renamed getConnection to getHttpConnection + + fixed getLocales handling of quality params + + fixed getParameter(name) handling for multiple values. + + added options to turn off ranges and chunking to support acrobat requests. + +Jetty-3.1.rc1 - 18 Mar 2001 + + Moved JMX and SASL handling to Jetty3Extra release + + Fixed problem with ServletContext.getContext(uri) + + Added Jetty documentation pages from JettyWiki + + Cleaned up build.xml script + + Minimal handling of Servlet.log before initialization. + + Various SSL cleanups + + Resource handler strips URL params like JSESSION. + +Jetty-3.1.rc0 - 23 Feb 2001 + + Added JMX management framework. + + Use Thread context classloader as default context loader parent. + + Fixed init order for unnamed servlets. + + Fixed session invalidation unbind notification to conform with spec + + Improved handling of primitives in utilities. + + Socket made available via HttpConnection. + + Improved InetAddrPort and ThreadedServer to reduce DNS lookups. + + Dynamic servlets may be restricted to Context classloader. + + Reoganized packages to allowed sealed Jars + + Changed getter and setter methods that did not conform to beans API. + +Jetty-3.0.4 - 23 Feb 2001 + + Fixed LineInput bug with split CRLF. + +Jetty-3.0.3 - 3 Feb 2001 + + Fixed pipelined request buffer bug. + + Handle empty form content without exception. + + Allow Log to be disabled before initialization. + + Included new Jetty Logo + + Implemented web.xml servlet mapping to a JSP + + Fixed handling of directories without trailing / + +Jetty-3.0.2 - 13 Jan 2001 + + Replaced ResourceHandler FIFO cache with LRU cache. + + Greatly improved buffering in ChunkableOutputStream + + Padded error bodies for IE bug. + + Improved HTML.Block efficiency + + Improved jetty.bat + + Improved jetty.sh + + Handle unknown status reasons in HttpResponse + + Ignore included response updates rather than IllegalStateException + + Removed classloading stats which were causing circular class loading problems. + + Allow '+' in path portion of a URL. + + Try ISO8859_1 encoding if can't find ISO-8859-1 + + Restructured demo site pages. + + Context specific security permissions. + + Added etc/jetty.policy as example policy file. + +Jetty-3.0.1 - 20 Dec 2000 + + Fixed value unbind notification for session invalidation. + + Removed double null check possibility from ServletHolder + +Jetty-3.0.0 - 17 Dec 2000 + + Improved jetty.sh logging + + Improved dtd resolution in XML parser. + + Fixed taglib parsing + + Fixed rel path handling in default configurations. + + Optional extract war files. + + Fixed WriterLogSink init bug + + Use inner class to avoid double null check sync problems + + Fixed rollover bug in WriterLogSink + +Jetty-3.0.0.rc8 - 13 Dec 2000 + + Optional alias checking added to FileResource. Turned on by default +on all platforms without the "/" file separator. + + Mapped *.jsp,*.jsP,*.jSp,*.jSP,*.Jsp,*.JsP,*.JSp,*.JSP + + Tidied handling of ".", ".." and "//" in resource paths + + Protected META-INF as well as WEB-INF in web applications. + + Jetty.Server catches init exceptions per server + + getSecurityHandler creates handler at position 0. + + SysV unix init script + + Improved exit admin handling + + Change PathMap handling of /* to give precedence over suffix mapping. + + Forward to welcome pages rather than redirect. + + Removed special characters from source. + + Default log options changed if in debug mode. + + Removed some unused variables. + + Added ForwardHandler + + Removed security constraint on demo admin server. + + Patched jasper to tomcat 3.2.1 + +Jetty-3.0.0.rc7 - 02 Dec 2000 + + Fixed security problem with lowercase WEB-INF uris on windows. + + Extended security constraints (see README and WebApp Demo). + + Set thread context classloader during handler start/stop calls. + + Don't set MIME-Version in response. + + Allow dynamic servlets to be served from / + + Handle multiple inits of same servlet class. + + Auto add a NotFoundHandler if needed. + + Added NotFoundServlet + + Added range handling to ResourceHandler. + + CGI servlet handles not found better. + + WEB-INF protected by NotFoundServlet rather than security constraint. + + PUT, MOVE disabled in WebApplication unless defaults file is passed. + + Conditionals apply to puts, dels and moves in ResourceHandler. + + URIs accept all characters < 0xff. + + Set the AcceptRanges header. + + Depreciated RollOverLogSink and moved functionality to an +improved WriterLogSink. + + Changed log options to less verbose defaults. + + ThreadedServer.forceStop() now makes a connection to itself to handle non-premptive close. + + Double null lock checks use ThreadPool.__nullLockChecks. + + Split Debug servlet out of Admin Servlet. + + Added Com.eclipse.HTTP.Handler.Servlet.Context.LogSink attribute +to Servlet Context. If set, it is used in preference to the system log. + +Jetty-3.0.0.rc6 - 20 Nov 2000 + + RequestDispatcher.forward() only resets buffer, not headers. + + Added ServletWriter that can be disabled. + + Resource gets systemresources from it's own classloader. + + don't include classes in release. + + Allow load-on-startup with no content. + + Fixed RollOverFileLogSink bug with extra log files. + + Improved Log defaults + + Don't start HttpServer log sink on add. + + Admin servlet uses unique links for IE. + + Added Win32 service support + + Reduced risk of double null check sync problem. + + Don't set connection:close for normal HTTP/1.0 responses. + + RequestDispatcher new queries params replace old. + + Servlet init order may be negative. + + Corrected a few of the many spelling mistakes. + + Javadoc improvements. + + Webapps serve dynamics servlets by default. + + Warn for missing WEB-INF or web.xml + + Sessions try version 1 cookies in set-cookie2 header. + + Session cookies are given context path + + Put extra server and servlet info in header. + + Version details in header can be suppressed with System property +java.com.eclipse.HTTP.Version.paranoid + + Prevent reloading dynamic servlets at different paths. + + Implemented resource aliases in HandlerContext - used by Servlet Context + + Map tablib configuration to resource aliases. + + Implemented customizable error pages. + + Simple stats in ContextLoader. + + Allow HttpMessage state to be manipulated. + + Allow multiple set cookies. + +Jetty-3.0.0.rc5 - 12 Nov 2000 + + Default writer encoding set by mime type if not explicitly set. + + Relax webapp rules, accept no web.xml or no WEB-INF + + Pass flush through ServletOut + + Avoid jprobe race warnings in DateCache + + Allow null cookie values + + Servlet exceptions cause 503 unavailable rather than 500 server error + + RequestDispatcher can dispatch static resources. + + Merged DynamicHandler into ServletHandler. + + Added debug form to Admin servlet. + + Implemented servlet load ordering. + + Moved JSP classpath hack to ServletHolder + + Removed Makefile build system. + + Many javadoc cleanups. + +Jetty-2.4.9 - 12 Nov 2000 + + HttpListener ignore InterruptedIOExceptions + + HttpListener default max idle time = 20s + + HtmlFilter handles non default encodings + + Writing HttpRequests encodes path + + HttpRequest.write uses ISO8859_1 encoding. + +Jetty-3.0.0.rc4 - 6 Nov 2000 + + Provide default JettyIndex.properties + + Fixed mis-synchronization in ThreadPool.stop() + + Fixed mime type mapping bug introduced in RC3 + + Ignore more IOExceptions (still visible with debug). + +Jetty-3.0.0.rc3 - 5 Nov 2000 + + Changed ThreadPool.stop for IBM 1.3 JVM + + Added bin/jetty.sh run script. + + upgraded build.xml to ant v1.2 + + Set MaxReadTimeMs in all examples + + Further clean up of the connection close actions + + Moved unused classes from com.eclipse.Util to com.eclipse.Tools in +new distribution package. + + Handle mime suffixes containing dots. + + Added gz tgz tar.gz .z mime mappings. + + Fixed default mimemap initialization bug + + Optimized persistent connections by recycling objects + + Added HandlerContext.setHttpServerAccess for trusted contexts. + + Set the thread context class loader in HandlerContext.handle + + Prevent servlet setAttribute calls to protected context attributes. + + Removed redundant context attributes. + + Implemented mime mapping in webapplications. + + Strip ./ from relative resources. + + Added context class path dynamic servlet demo + +Jetty-3.0.0.rc2 - 29 Oct 2000 + + Replaced ISO-8859-1 literals with StringUtil static + + Pass file based classpath to JspServlet (see README). + + Prevented multiple init of ServletHolder + + ErlEncoding treats params without values as empty rather than null. + + Accept public DTD for XmlConfiguration (old style still supported). + + Cleaned up non persistent connection close. + + Accept HTTP/1. as HTTP/1.0 (for netscape bug). + + Fixed thread name problem in ThreadPool + +Jetty-3.0.0.rc1 - 22 Oct 2000 + + Added simple admin servlet. + + Added CGI to demo + + Added HashUserRealm and cleaned up security constraints + + Added Multipart request and response classes from Jetty2 + + Moved and simplified ServletLoader to ContextLoader. + + Initialize JSP with classloader. + + All attributes in javax. java. and com.eclipse. name spaces to be set. + + Partial handling of 0.9 requests. + + removed Thread.destroy() calls. + + Cleaned up exception handling. + +Jetty-2.4.8 23 Oct 2000 + + Fixed bug with 304 replies with bodies. + + Improved win32 make files. + + Fixed closing socket problem + +Jetty-3.0.B05 - 18 Oct 2000 + + Improved null returns to get almost clean watchdog test. + + Cleaned up response committing and flushing + + Handler RFC2109 cookies (like any browser handles them!) + + Added default webapp servlet mapping /servlet/name/* + + Improved path spec interpretation by looking at 2.3 spec + + Implemented security-role-ref for servlets + + Protected servletConfig from downcast security problems + + Made test harnesses work with ant. + + improved ant documentation. + + Removed most deprecation warnings + + Fixed JarFileResource to handle jar files without directories. + + Implemented war file support + + Java2 style classloading + + Improved default log format for clarity. + + Separated context attributes and initParams. + +Jetty-3.0.B04 - 12 Oct 2000 + + Restricted context mapping to simple model for servlets. + + Fixed problem with session ID in paths + + Added modified version of JasperB3.2 for JSP + + Moved FileBase to docroot + + Merged and renamed third party jars. + + Do not try multiple servlets for a request. + + Implemented Context.getContext(uri) + + Added webdefault.xml for web applications. + + Redirect to index files, so index.jsp works. + + Filthy hack to teach jasper JspServer Jetty classpath + +Jetty-3.0.B03 - 9th Oct 2000 + + Expanded import package.*; lines + + Expanded leading tabs to spaces + + Improved Context to Handler contract. + + Parse but not handler startup ordering in web applications. + + Send request log via a LogSink + + Added append mode in RolloverFileLogSink + + Made LogSink a Lifecycle interface + + Improved handler toString + + Redirect context only paths. + + Pass object to LogSink + + Implemented request dispatching. + + Redo dynamic servlets handling + + Improved Log rollover. + + Simplified path translation and real path calculation. + + Catch stop and destroy exceptions in HttpServer.stop() + + Handle ignorable spaces in XmlConfiguration + + Handle ignorable spaces in WebApplication + + Warn about explicit sets of WebApplication + + Remove 411 checks as IE breaks this rule after redirect. + + Removed last remnants JDK 1.1 support + + Added release script + +Jetty-2.4.7 - 6th Oct 2000 + + Allow Objects to be passed to LogSink + + Set content length on errors for keep alive. + + Added encode methods to URI + + Improved win32 build + + fixes to SSL doco + + Support key and keystore passwords + + Various improvements to ServletDispatch, PropertyTree and +associated classes. + +Jetty-3.0.B02 - 24st Aug 2000 + + Fixed LineInput bug with SSL giving CR pause LF. + + Fixed HTTP/1.0 input close bug + + Fixed bug in TestRFC2616 + + Improved ThreadedServer stop and destroy + + Use resources in WebApplication + + Added CGI servlet + +Jetty-3.0.B01 - 21st Aug 2000 + + SSL implemented with JsseListener + + Partial implementation of webapp securitycontraints + + Implemented more webapp configuration + + Switched to the aelfred XML parser from microstar, which is +only partially validating, but small and lightweight + +Jetty-2.4.6 - 16th Aug 2000 + + Turn Linger off before closing sockets, to allow restart. + + JsseListener & SunJsseListener added and documented + + com.eclipse.Util.KeyPairTool added to handle openSSL SSL keys. + + Minor changes to compile with jikes. + + Added passive mode methods to FTP + +Jetty-3.0.A99 - 10 Aug 2000 + + Implemented jetty.xml configuration + + Added Xmlconfiguration utility + + ServletLoader simplied and uses ResourcePath + + Replaced FileHandler with ResourceHandler + + Use SAX XML parsing instead of DOM for space saving. + + Removed FileBase. Now use ResourceBase instead + + Added Resource abstraction + + Make it compile cleanly with jikes. + + Re-added commented out imports for JDK-1.1 compile + +Jetty-3.0.A98 - 20 July 2000 + + Implemented Jetty demos and Site as Web Application. + + Implemented WebApplicationContext + + Switched to JDK1.2 only + + ServletRequest.getServerPort() returns 80 rather than 0 + + Fixed constructor to RolloverFileLogSink + + Improved synchronization on LogSink + + Allow HttpRequest.toString() handles bad requests. + +Jetty-3.0.A97 - 13 July 2000 + + Tempory request log implementation + + Less verbose debug + + Better tuned SocketListener parameters + + Started RequestDispatcher implementation. + + Added WML mappings + + Fixed makefiles for BSD ls + + Fixed persistent commits with no content (eg redirect+keep-alive). + + Implemented servlet isSecure(). + + Implemented servlet getLocale(s). + + Formatted version in server info string. + + Protect setContentLength from a late set in default servlet +HEAD handling. + + Added error handling to LifeCycleThread + + implemented removeAttribute on requests + +Jetty-2.4.5 - 9th July 2000 + + Don't mark a session invalid until after values unbound. + + Formatted version in server info. + + Added HtmlExpireFilter and removed response cache +revention from HtmlFilter. + + Fixed transaction handling in JDBC wrappers + +Jetty-3.0.A96 - 27 June 2000 + + Fixed bug with HTTP/1.1 Head reqests to servlets. + + Supressed un-needed chunking EOF indicators. + +Jetty-3.0.A95 - 24 June 2000 + + Fixed getServletPath for default "/" + + Handle spaces in file names in FileHandler. + +Jetty-3.0.A94 - 19 June 2000 + + Implemented Sessions. + + PathMap exact matches can terminate with ; or # for +URL sessions and targets. + + Added HandlerContext to allow grouping of handlers into +units with the same file, resource and class configurations. + + Cleaned up commit() and added complete() to HttpResponse + + Updated license to clarify that commercial usage IS OK! + +Jetty-3.0.A93 - 14 June 2000 + + Major rethink! Moved to 2.2 servlet API + + Lots of changes and probably unstable + +Jetty-3.0.A92 - 7 June 2000 + + Added HTML classes to jar + + Fixed redirection bug in FileHandler + +Jetty-2.4.4 - 3rd June 2000 + + Many debug call optimizations + + Added RolloverFileLogSink + + Improved LogSink configuration + + Support System.property expansions in PropertyTrees. + + Added uk.org.gosnell.Servlets.CgiServlet to contrib + + HttpRequest.setRequestPath does not null pathInfo. + + BasicAuthHandler uses getResourcePath so it can be used +behind request dispatching + + Added HTML.Composite.replace + + FileHandler implements IfModifiedSince on index files. + + Added build-win32.mak + +Jetty-3.0.A91 - 3 June 2000 + + Improved LogSink mechanism + + Implemented realPath and getResource methods for servlets. + + Abstracted ServletHandler + + Simplified HttpServer configuration methods and arguments + + Simplified class loading + + Added HTML classes from Jetty2 + +Jetty-3.0.A9 - 7 May 2000 + + Improvided finally handling of output end game. + + Fixed double chunking bug in SocketListener. + + File handler checks modified headers on directory indexes. + + ServletLoader tries unix then platform separator for zip separator. + +Jetty-3.0.A8 4th May 2000 + + Servlet2_1 class loading re-acrchitected. See README. + + Moved Sevlet2_1 handler to com.eclipse.Servlet2_1 + + addCookie takes an int maxAge rather than a expires date. + + Added LogSink extensible log architecture. + + Code.ignore only outputs when debug is verbose. + + Added Tenlet class for reverse telnet. + +Jetty-2.4.3 - 4th May 2000 STABLE + + Pass Cookies with 0 max age to browser. + + Allow CRLF in UrlEncoded + +Jetty-2.4.2 - 23rd April 2000 + + Added LogSink and FileLogSink classes to allow extensible +Log handling. + + Handle nested RequestDispatcher includes. + + Modified GNUJSP to prevent close in nested requests. + + Added GNUJSP to JettyServer.prp file. + +Jetty-3.0.A7 - 15 Apr 2000 + + Include java 1.2 source hierarchy + + removed excess ';' from source + + fixed flush problem with chunked output for IE5 + + Added InetGateway to help debug IE5 problems + + added removeValue method to MultiMap + +Jetty-2.4.1 - 9th April 2000 + + Removed debug println from ServletHolder. + + Set encoding before exception in FileHandler. + + Fixed bug in HtmlFilter for tags split between writes. + +Jetty-3.0.A6 - 9 Apr 2000 + + Integrated skeleton 2.1 Servlet container + + Improved portability of Frame and Debug. + + Dates forced to use US locale + + Removed Converter utilities and InetGateway. + + added bin/useJava2Collections to convert to JDK1.2 + +Jetty-2.4.0 - 24th March 2000 + + Upgraded to gnujsp 1.0.0 + + Added per servlet resourceBase configuration. + + Absolute URIs are returned by getRequestURI (if sent by browser). + + Improved parsing of stack trace in debug mode. + + Implemented full handling of cookie max age. + + Moved SetUID native code to contrib hierarchy + + Form parameters only decoded for POSTs + + RequestDispatcher handles URI parameters + + Fixed bug with RequestDispatcher.include() + + Fixed caste problem in UrlEncoded + + Fixed null pointer in ThreadedServer with stopAll + + Added VirtualHostHandler for virtual host handling + + Added doc directory with a small start + +Jetty-2.3.5 - 25th January 2000 + + Fixed nasty bug with HTTP/1.1 redirects. + + ProxyHandler sends content for POSTs etc. + + Force locale of date formats to US. + + Fixed expires bug in Cookies + + Added configuration option to turn off Keep-Alive in HTTP/1.0 + + Allow configured servlets to be auto reloaded. + + Allow properties to be configured for dynamic servlets. + + Added contrib/com/kiwiconsulting/jetty JSSE SSL adaptor to release. + +Jetty-2.3.4 - 18th January 2000 + + include from linux rather than genunix for native builds + + Fixed IllegalStateException handling in DefaultExceptionHandler + + MethodTag.invoke() is now public. + + Improved HtmlFilter.activate header modifications. + + Cookie map keyed on domain as well as name and path. + + DictionaryConverter handles null values. + + URI decodes applies URL decoding to the path. + + Servlet properties allow objects to be stored. + + Fixed interaction with resourcePaths and proxy demo. + +Jetty-3.0.A5 - 19 Oct 1999 + + Use ISO8859_1 instead of UTF8 for headers etc. + + Use char array in UrlEncoded.decode + + Do our own URL string encoding with 8859-1 + + Replaced LF wait in LineInput with state boolean. + +Jetty-2.3.3 - 19th October 1999 STABLE + + Replaced UTF8 encoding with ISO-8859-1 for headers. + + Use UrlEncoded for form parameters. + + Do our own URL encoding with ISO-8859-1 + + HTTP.HTML.EmbedUrl uses contents encoding. + +Jetty-2.3.2 - 17th October 1999 + + Fixed getReader bug with HttpRequest. + + Updated UrlEncoded with Jetty3 version. + +Jetty-3.0.A4 - 16 Oct 1999 + + Request attributes + + Basic Authentication Handler. + + Added LF wait after CR to LineInput. + + UTF8 in UrlDecoded.decodeString. + +Jetty-2.3.1 - 14th October 1999 + + Force UTF8 for FTP commands + + Force UTF8 for HTML + + Changed demo servlets to use writers in preference to outputstreams + + NullHandler/Server default name.name.PROPERTIES to load +prefix/name.name.properties + + Use UTF8 in HTTP headers + + Added Oracle DB adapter + + Added assert with no message to Code + + ThreadedServer calls setSoTimeout(_maxThreadIdleMs) on +accepted sockets. Idle reads will timeout. + + Prevented thread churn on idle server. + + HTTP/1.0 Keep-Alive (about time!). + + Fixed GNUJSP 1.0 resource bug. + +Jetty-3.0.A3 - 14 Oct 1999 + + Added LifeCycle interface to Utils implemented by +ThreadPool, ThreadedServer, HttpListener & HttpHandler + + StartAll, stopAll and destroyAll methods added to HttpServer. + + MaxReadTimeMs added to ThreadedServer. + + Added service method to HttpConnection for specialization. + +Jetty-3.0.A2 - 13 Oct 1999 + + UTF8 handling on raw output stream. + + Reduced flushing on writing response. + + Fixed LineInput problem with repeated CRs + + Cleaned up Util TestHarness. + + Prevent entity content for responses 100-199,203,304 + + Added cookie support and demo. + + HTTP/1.0 Keep-alive (about time!) + + Virtual Hosts. + + NotFound Handler + + OPTION * Handling. + + TRACE handling. + + HEAD handling. + +Jetty-3.0.A1 - 12 Oct 1999 + + LineInput uses own buffering and uses character encodings. + + Added MultiMap for common handling of multiple valued parameters. + + Added parameters to HttpRequest + + Quick port of FileHandler + + Setup demo pages. + + Added PathMap implementing mapping as defined in the 2.2 API +specification (ie. /exact, /prefix/*, *.extention & default ). + + Added HttpHandler interface with start/stop/destroy lifecycle + + Updated HttpListener is start/stop/destroy lifecycle. + + Implemented simple extension architecture in HttpServer. + +Jetty-3.0.A0 - 9 Oct 1999 + + Started fresh repository in CVS + + Moved com.eclipse.Base classes to com.eclipse.Util + + Cleanup of UrlEncoded, using 1.2 Collections. + + Cleanup of URI, using 1.2 Collections. + + Extended URI to handle absolute URLs + + Cleanup of LineInput, using 1.2 Collections. + + Moved HttpInput/OutputStream to ChunkableInput/OutputStream. + + Cleaned up chunking code to use LineInput and reduce buffering. + + Added support for transfer and content encoding filters. + + Added support for servlet 2.2 outbut buffer control. + + Generalized notification of outputStream events. + + Split HttpHeader into HttpFields and HttpMessage. + + HttpMessage supports chunked trailers. + + HttpMessage supports message states. + + Added generalized HTTP Connection. + + Cleanup of HttpRequest and decoupled from Servlet API + + Cleanup and abstraction of ThreadPool. + + ThreadedServer based on ThreadPool. + + Cleanup of HttpResponse and decoupled from Servlet API + + Created RFC2616 test harness. + + gzip and deflate request transfer encodings + + TE field coding and trailer handler + + HttpExceptions now produce error pages with specific detail +of the exception. + +Jetty-2.3.0 - 5th October 1999 + + Added SetUID class with native Unix call to set the +effective User ID. + + FTP closes files after put/get. + + FTP uses InetAddress of command socket for data socket. + +Jetty-2.3.0A - 22 Sep 1999 + + Added GNUJSP 1.0 for the JSP 1.0 API. + + Use javax.servlet classes from JWSDK1.0 + + Added "Powered by Jetty" button. + + ServerContext available to HtmlFilters via context param + + Made session IDs less predictable and removed race. + + Added BuildJetty.java file. + + Expanded tabs to spaces in source. + +Jetty-2.2.8 - 15 Sep 1999 + + Fixed bug in Element.attribute with empty string values. + + Made translation of getRequestURI() optional. + + Removed recursion from TranslationHandler + + Added disableLog() to turn off logging. + + Allow default table attributes to be overriden. + + Improved quoting in HTML element values + +Jetty-2.2.7 - 9 Sep 1999 + + Reverted semantics of getRequestURI() to return untranslated URI. + + Added GzipFilter for content encoding. + + Added default row, head and cell elements to Table. + + FileHandler passes POST request through if the file does not exist. + +Jetty-2.2.6 - 5 Sep 1999 + + New implementation of ThreadPool, avoids a thread leak problem. + + Fixed Cookie max age order of magnitude bug. + + Cookies always available from getCookies. + + Cookies parameter renamed to CookiesAsParameters + + HttpRequest.getSession() always returns a session as per +the latest API spec. + + Added destroy() method on all HttpHandlers. + + ServletHandler.destroy destroys all servlets. + + FileHandler does not server files ending in '/' + + Ignore duplicate single valued headers, rather than +reply with bad request, as IE4 breaks the rules. + + Allow the handling of getPathTranslated to +be configured in ServletHandler. + + Removed JRUN options from ServletHandler configuration. + + Added ServletRunnerHandler to the contrib directories. + + Updated HTML package to better support CSS: +- cssClass, cssID and style methods added to element. +- SPAN added to Block +- media added to Style +- class StyleLink added. + +Jetty-2.2.5 - 19 Aug 1999 + + Fixed bug with closing connections in ThreadedServer + + Made start and stop non final in ThreadedServer + + Better default handling of ServletExceptions + + Always close connection after a bad request. + + Set Expires header in HtmlFilter. + + Don't override the cookie as parameter option. + + Limited growth in MultiPartResponse boundary. + + Improved error messages from Jetty.Server. + + Close loaded class files so Win32 can overwrite +them before GC (what a silly file system!). + +Jetty-2.2.4 - 2 Aug 1999 + + ThreadedServer can use subclasses of Thread. + + Better help on Jetty.Server + + HttpRequests may be passed to HttpFilter constructors. + + HtmlFilter blanks IfModifiedSince headers on construction + + Fixed bugs in HtmlFilter parser and added TestHarness. + + Improved cfg RCS script. + +Jetty-2.2.3 - 27 July 1999 + + Fixed parser bug in HtmlFilter + + Made setInitialize public in ServletHolder + + Improved performance of com.eclipse.HTML.Heading + + Added stop call to HttpServer, used by Exit Servlet. + + Simplified JDBC connection handling so that it works +with Java1.2 - albeit less efficiently. + + FileHandler defaults to allowing directory access. + + JDBC tests modified to use cloudscape as DB. + +Jetty-2.2.2 - 22 July 1999 + + Fixed bug in HtmlFilter that prevented single char buffers +from being written. + + Implemented getResourceAsStream in FileJarServletLoader + + Fixed bug with CLASSPATH in FileJarServletLoader after attempt +to load from a jar. + + Fixed bug in com.eclipse.Util.IO with thread routines. + + Moved more test harnesses out of classes. + + File handler passes through not allowed options for +non existant files. + + NotFoundHandler can repond with SC_METHOD_NOT_ALLOWED. + + Improved com.eclipse.Base.Log handling of different JVMs + + Minor fixes to README + +Jetty-2.2.1 - 18 July 1999 + + Comma separate header fields. + + Protect against duplicate single valued headers. + + Less verbose debug in PropertyTree + + Ignore IOException in ThreadedServer.run() when closing. + + Limit maximum line length in HttpInputStream. + + Response with SC_BAD_REQUEST rather than close in more +circumstances + + Handle continuation lines in HttpHeader. + + HtmlFilter resets last-modified and content-length headers. + + Implemented com.eclipse.Util.IO as a ThreadPool + + Decoupled ExceptionHandler configuration from Handler stacks. +Old config style will produce warning and Default behavior. +See new config file format for changes. + + Added TerseExceptionHandler + + Added optional resourceBase property to HttpConfiguration. This +is used as a URL prefix in the getResource API and was suggested +by the JSERV and Tomcat implementors. + +Jetty-2.2.0 - 1 July 1999 + + Improved feature description page. + + Added Protekt SSL HttpListener + + Moved GNUJSP and Protekt listener to a contrib hierarchy. + + ThreadedServer.stop() closes socket before interrupting threads. + + Exit servlet improved (a little). + + Fixed some of the javadoc formatting. + +Jetty-2.2.Beta4 - 29 June 1999 + + FileHandler flushes files from cache in DELETE method. + + ThreadedServer.stop() now waits until all threads are stopped. + + Options "allowDir" added to FileHandler. + + Added getGlobalProperty to Jetty.Server and used this to +configure default page type. + + Updated README.txt + + Restructured com.eclipse.Jetty.Server for better clarity and +documentation. + + Added comments to configuration files. + + Made ServerSocket and accept call generic in ThreadedServer for +SSL listeners. + + Altered meaning of * in PropertyTree to assist in abbreviated +configuration files. + + Added JettyMinimalDemo.prp as an example of an abbreviated +configuration. + + Expanded Mime.prp file + + Added property handling to ServletHandler to read JRUN +servlet configuration files. + +Jetty-2.2.Beta3 - 22 June 1999 + + Re-implemented ThreadedServer to improve and balance performance. + + Added file cache to FileHandler + + Implemented efficient version of +ServletContext.getResourceAsStream() that does not open a +new socket connection (as does getResource()). + + LookAndFeelServlet uses getResourceAsStream to get the file +to wrap. This allows it to benefit from any caching done and +to wrap arbitrary content (not just files). + + Restructure demo so that LookAndFeel content comes from simple +handler stack. + + Fixed file and socket leaks in Include and Embed tags. + + Ran dos2unix on all text files + + Applied contributed patch of spelling and typo corrections + + Added alternate constructors to HTML.Include for InputStream. + + Server.shutdown() clears configuration so that server may +be restarted in same virtual machine. + + Improved Block.write. + + Fixed bug in HttpResponse flush. + +Jetty-2.2.Beta2 - 12 June 1999 + + Added all write methods to HttpOutputStream$SwitchOutputStream + + Added com.eclipse.Jetty.Server.shutdown() for gentler shutdown +of server. Called from Exit servlet + + HttpRequest.getParameterNames() no longer alters the order +returned by getQueryString(). + + Handle path info of a dynamic loaded servlets and +correctly set the servlet path. + + Standardized date format in persistent cookies. + +Jetty-2.2.Beta1 - 7 June 1999 + + Defined abstract ServletLoader, derivations of which can be +specified in HttpConfiguration properties. + + Implemented all HttpServer attribute methods by mapping to the +HttpConfiguration properties. Dynamic reconfiguration is NOT +supported by these methods (but we are thinking about it). + + Close files after use to avoid "file leak" under heavy load. + + Fixed missing copyright messages from some contributions + + Fixed incorrect version numbers in a few places. + + Improved ThreadPool synchronization and added minThreads. + + Allow configuration of MinListenerThreads, MaxListenerThreads, +MaxListenerThreadIdleMs + + HtmlFilter optimized for being called by a buffered writer. + + Don't warn about IOExceptions unless Debug is on. + + Limit the job queue only grow to the max number of threads. + + Included GNUJSP 0.9.9 + + Optional use of DateCache in log file format + + Fixed cache in FileJarServletLoader + + Destroy requests and responses to help garbage collector. + + Restructure ThreadedServer to reduce object creation. + +Jetty-2.2.Beta0 - 31 May 1999 + + Servlet loader handles jar files with different files separator. + + ThreadedServer gently shuts down. + + Handle malformed % characters in URLs. + + Included and improved version of ThreadPool for significant +performance improvement under high load. + + HttpRequest.getCookies returns empty array rather than null for no +cookies. + + Added HttpResponse.requestHandled() method to avoid bug with +servlet doHead method. + + Added Page.rewind() method to allow a page to be written multiple +times + + Added "Initialize" attribute to servlet configuration to allow +servlet to be initialized when loaded. + + LogHandler changed to support only a single outfile and optional +append. + + Included contributed com.eclipse.Jetty.StressTester class + + Token effort to keep test files out of the jar + + Removed support for STF + +Jetty-2.2.Alpha1 - 7 May 1999 + + ServletHolder can auto reload servlets + + Dynamic servlets can have autoReload configured + + Wait for requests to complete before reloading. + + Call destroy on old servlets when reloading. + + Made capitalization of config file more consistent(ish) + + Fixed bug in SessionDump + +Jetty-2.2.Alpha0 - 6 May 1999 + + Improved PropertyTree implementation + + Old Jetty.Server class renamed to Jetty.Server21 + + New Server class using PropertyTree for configuration + + HttpHandlers given setProperties method to configure via Properties. + + HttpListener class can be configured + + Mime suffix mapping can be configured. + + Removed historic API from sessions + + Improved SessionDump servlet + + Fixed date overflow in Cookies + + HttpResponse.sendError avoids IllegalStateException + + Added ServletLoader implementation if ClassLoader. + + Dynamic loading of servlets. + + Added reload method to ServletHolder, but no way to call it yet. + + Changed options for FileServer + + Implemented ServletServer + + Removed SimpleServletServer + +Jetty-2.1.7 - 22 April 1999 + + Fixed showstopper bug with getReader and getWriter in +requests and responses. + + HttpFilter uses package interface to get HttpOutputStream + +Jetty-2.1.6 - 21 April 1999 + + Reduced initial size of most hashtables to reduce +default memory overheads. + + Throw IllegalStateException as required from gets of +input/output/reader/writer in requests/responses. + + New simpler version of PropertyTree + + Updated PropertyTreeEditor + + Return EOF from HttpInputStream that has a content length. + + Added additional date formats for HttpHeader.getDateHeader + +Jetty-2.1.5 - 15 April 1999 + + Session URL encoding fixed for relative URLs. + + Reduced session memory overhead of sessions + + Form parameters protected against multiple decodes when redirected. + + Added setType methods to com.eclipse.FTP.Ftp + + Fixed bugs with invalid sessions + + Page factory requires response for session encoding + + Moved SessionHandler to front of stacks + + HtmlFilter now expands to the URL encoded session if +required. + + Instrumented most of the demo to support URL session encoding. + + Implemented HttpRequest.getReader() + + Servlet log has been diverted to com.eclipse.Base.Log.event() +Thus debug does not need to be turned on to see servlet logs. + + Fixed alignment bug in TableForm + + Removed RFCs from package + + Fixed bug in ServletDispatch for null pathInfo + +Jetty-2.1.4 - 26 March 1999 + + Fixed problem compiling PathMap under some JDKs. + + Reduced HTML dependence in HTTP package to allow minimal configuration + + Tightened license agreement so that binary distributions are required +to include the license file. + + HttpRequest attributes implemented. + + Session max idle time implemented. + + pathInfo returns null for zero length pathInfo (as per spec). +Sorry if this breaks your servlets - it is a pain! + + fixed bug in getRealPath + + getPathTranslated now call getRealPath with pathInfo (as per spec). + +Jetty-2.1.3 - 19 March 1999 + + Added support for suffixes to PathMap + + Included GNUJSP implementation of Java Server Pages + + Use Java2 javadoc + +Jetty-2.1.2 - 9 March 1999 + + JSDK 2.1.1 + + API documentation for JSDK 2.1.1 + + Cascading style sheet HTML element added. + + Fixed trailing / bug in FileHandler (again!). + + Converted most servlets to HttpServlets using do Methods. + +Jetty-2.1.1 - 5 March 1999 + + Reduced number of calls to getRemoteHost for optimization + + Faster version of HttpInputStream.readLine(). + + com.eclipse.Base.DateCache class added and used to speed date handling. + + Handle '.' in configured paths (temp fix until PropertyTrees) + + Fast char buffer handling in HttpInputStream + + Faster version of HttpHeader.read() + + Faster version of HttpRequest + + Size all StringBuffers + +Jetty-2.1.0 - 22 February 1999 + + Session URL Encoding + + PropertyTrees (see new Demo page) + + ServletDispatch (see new Demo page) + + image/jpg -> image/jpeg + + Deprecated com.eclipse.Util.STF + + getServlet methods return null. + +Jetty-2.1.B1 - 13 February 1999 + + Fixed bug with if-modified-since in FileHandler + + Added video/quicktime to default MIME types. + + Fixed bug with MultipartRequest. + + Updated DefaultExceptionHandler. + + Updated InetAddrPort. + + Updated URI. + + Implemented Handler translations and getRealPath. + + Improved handling of File.separator in FileHandler. + + Implemented RequestDispatcher (NOT Tested!). + + Implemented getResource and getResourceAsStream (NOT Tested!). + + Replace package com.eclipse.Util.Gateway with +class com.eclipse.Util.InetGateway + +Jetty-2.1.B0 - 30 January 1999 + + Uses JSDK2.1 API, but not all methods implemented. + + Added support for PUT, MOVE, DELETE in FileHandler + + FileHandler now sets content length. + + Added plug gateway classes com.eclipse.Util.Gateway + + Fixed command line bug with SimpleServletConfig + + Minor changes to support MS J++ and its non standard +language extensions - MMMmmm should have left it unchanged! + +Jetty-2.0.5 - 15 December 1998 + + Temp fix to getCharacterEncoding + + added getHeaderNoParams + +Jetty-2.0.4 - 10 December 1998 + + Use real release of JSDK2.0 (rather than beta). + + Portability issues solved for Apple's + + Improved error code returns + + Removed MORTBAY_HOME support from Makefiles + + Improved default Makefile behaviour + + Implement getCharacterEncoding + +Jetty-2.0.3 - 13 November 1998 + + Limit threads in ThreadedServer and low priority listener option +greatly improve performance under worse case loads. + + Fix bug with index files for Jetty.Server. Previously servers +configured with com.eclipse.Jetty.Server would not handle +index.html files. Need to make this configurable in the prp file. + + Fixed errors in README file: com.eclipse.Jetty.Server was called +com.eclipse.HTTP.Server + +Jetty-2.0.2 - 1 November 1998 + + Use JETTY_HOME rather than MORTBAY_HOME for build environment + + Add thread pool to threaded server for significant +performance improvement. + + Buffer files during configuration + + Buffer HTTP Response headers. + +Jetty-2.0.1 - 27 October 1998 + + Released under an Open Source license. + +Jetty-2.0.0 - 25 October 1998 + + Removed exceptional case from FileHandler redirect. + + Removed Chat demo (too many netscape dependencies). + + Fixed Code.formatObject handling of null objects. + + Added multipart/form-data demo. + +Jetty-2.0.Beta3 - 29 Sep 1998 + + Send 301 for directories without trailing / in FileHandler + + Ignore exception from HttpListener + + Properly implemented multiple listening addresses + + Added com.eclipse.Jetty.Server (see README.Jetty) + + Demo converted to an instance of com.eclipse.Jetty.Server + + Fixed Log Handler again. + + Added com.eclipse.HTTP.MultiPartRequest to handle file uploads + +Jetty-2.0Beta2 - July 1998 + + Fixed Log Handler for HTTP/1.1 + + Slight improvement in READMEEs + +Jetty-2.0Beta1 - June 1998 + + Improved performance of Code.debug() calls, significantly +in the case of non matching debug patterns. + + Fixed bug with calls to service during initialization of servlet + + Provided addSection on com.eclipse.HTML.Page + + Provided reset on com.eclipse.HTML.Composite. + + Proxy demo in different server instance + + Handle full URLs in HTTP requests (to some extent) + + Improved performance with special asciiToLowerCase + + Warn if MSIE used for multi part MIME. + +Jetty-2.0Alpha2 - May 1998 + + JDK1.2 javax.servlet API + + Added date format to Log + + Added timezone to Log + + Handle params in getIntHeader and getDateHeader + + Removed HttpRequest.getByteContent + + Use javax.servlet.http.HttpUtils.parsePostData + + Use javax.servlet.http.Cookie + + Use javax.servlet.http.HttpSession + + Handle Single Threaded servlets with servlet pool + +Jetty-1.3.5 May 1998 + + Fixed socket inet bug in FTP + + Debug triggers added to com.eclipse.Base.Code + + Added date format to Log + + Correct handling of multiple parameters + +Jetty-2.0Alpha1 Wed 8 April 1998 + + Fixed forward bug with no port number + + Removed HttpRequestHeader class + + Debug triggers added to com.eclipse.Base.Code + + Handle HTTP/1.1 Host: header + + Correct formatting of Date HTTP headers + + HttpTests test harness + + Add HTTP/1.1 Date: header + + Handle file requests with If-Modified-Since: or If-Unmodified-Since: + + Handle HEAD properly + + Send Connection: close + + Requires Host: header for 1.1 requests + + Sends chunked data for 1.1 responses of unknown length. + + handle extra spaces in HTTP headers + + Really fixed handling of multiple parameters + + accept chunked data + + Send 100 Continue for HTTP/1.1 requests (concerned about push???) + + persistent connections + +Jetty-1.3.4 - Sun 15 Mar 1998 + + Fixed handling of multiple parameters in query and form content. +"?A=1%2C2&A=C%2CD" now returns two values ("1,2" & "C,D") rather +than 4. + + ServletHandler now takes an optional file base directory +name which is used to set the translated path for pathInfo in +servlet requests. + + Dump servlet enhanced to exercise these changes. + +Jetty-1.3.3 + + Fixed TableForm.addButtonArea bug. + + TableForm.extendRow() uses existing cell + + Closed exception window in HttpListener.java + +Jetty-1.3.2 + + Fixed proxy bug with no port number + + Added per Table cell composite factories + +Jetty-1.3.1 + + Minor fixes in SmtpMail + + ForwardHandler only forwards as http/1.0 (from Tobias.Miller) + + Improved parsing of stack traces + + Better handling of InvocationTargetException in debug + + Minor release adjustments for Tracker + +Jetty-1.3.0 + + Added DbAdaptor to JDBC wrappers + + Beta release of Tracker + +Jetty-1.2.0 + + Reintroduced STF + + Fixed install bug for nested classes + + Better Debug configuration + + DebugServlet + + Alternate look and feel for Jetty + +Jetty-1.1.1 + + Improved documentation + +Jetty-1.1 + + Improved connection caching in java.eclipse.JDBC + + Moved HttpCode to com.eclipse.Util + +Jetty-1.0.1 + + Bug fixes + +Jetty-1.0 + + First release in com.eclipse package structure + + Included Util, JDBC, HTML, HTTP, Jetty + + + + + + + diff --git a/jetty-ajp/pom.xml b/jetty-ajp/pom.xml new file mode 100644 index 00000000000..903a836e90a --- /dev/null +++ b/jetty-ajp/pom.xml @@ -0,0 +1,76 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-ajp + Jetty :: AJP + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.ajp + J2SE-1.5 + http://jetty.eclipse.org + org.eclipse.jetty.ajp;version=${project.version} + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + config.xml + + + + + + + + + + org.eclipse.jetty + jetty-server + ${project.version} + + + junit + junit + test + + + diff --git a/jetty-ajp/src/main/config/etc/jetty-ajp.xml b/jetty-ajp/src/main/config/etc/jetty-ajp.xml new file mode 100644 index 00000000000..191401c7de4 --- /dev/null +++ b/jetty-ajp/src/main/config/etc/jetty-ajp.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + 8009 + + + + + + diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java new file mode 100644 index 00000000000..249c81301d0 --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java @@ -0,0 +1,252 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; + +/** + * Connection implementation of the Ajp13 protocol.

XXX Refactor to remove + * duplication of HttpConnection + * + * + * + */ +public class Ajp13Connection extends HttpConnection +{ + public Ajp13Connection(Connector connector, EndPoint endPoint, Server server) + { + super(connector, endPoint, server, + new Ajp13Parser(connector, endPoint), + new Ajp13Generator(connector, endPoint, connector.getHeaderBufferSize(), connector.getResponseBufferSize()), + new Ajp13Request() + ); + + ((Ajp13Parser)_parser).setEventHandler(new RequestHandler()); + ((Ajp13Parser)_parser).setGenerator((Ajp13Generator)_generator); + ((Ajp13Request)_request).setConnection(this); + } + + public boolean isConfidential(Request request) + { + return ((Ajp13Request) request).isSslSecure(); + } + + public boolean isIntegral(Request request) + { + return ((Ajp13Request) request).isSslSecure(); + } + + public ServletInputStream getInputStream() + { + if (_in == null) + _in = new Ajp13Parser.Input((Ajp13Parser) _parser, _connector.getMaxIdleTime()); + return _in; + } + + private class RequestHandler implements Ajp13Parser.EventHandler + { + boolean _delayedHandling = false; + + public void startForwardRequest() throws IOException + { + _delayedHandling = false; + _uri.clear(); + + ((Ajp13Request) _request).setSslSecure(false); + _request.setTimeStamp(System.currentTimeMillis()); + _request.setUri(_uri); + + } + + public void parsedAuthorizationType(Buffer authType) throws IOException + { + //TODO JASPI this doesn't appear to make sense yet... how does ajp auth fit into jetty auth? +// _request.setAuthType(authType.toString()); + } + + public void parsedRemoteUser(Buffer remoteUser) throws IOException + { + ((Ajp13Request)_request).setRemoteUser(remoteUser.toString()); + } + + public void parsedServletPath(Buffer servletPath) throws IOException + { + _request.setServletPath(servletPath.toString()); + } + + public void parsedContextPath(Buffer context) throws IOException + { + _request.setContextPath(context.toString()); + } + + public void parsedSslCert(Buffer sslCert) throws IOException + { + try + { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + ByteArrayInputStream bis = new ByteArrayInputStream(sslCert.toString().getBytes()); + + Collection certCollection = cf.generateCertificates(bis); + X509Certificate[] certificates = new X509Certificate[certCollection.size()]; + + int i=0; + for (Object aCertCollection : certCollection) + { + certificates[i++] = (X509Certificate) aCertCollection; + } + + _request.setAttribute("javax.servlet.request.X509Certificate", certificates); + } + catch (Exception e) + { + org.eclipse.jetty.util.log.Log.warn(e.toString()); + org.eclipse.jetty.util.log.Log.ignore(e); + if (sslCert!=null) + _request.setAttribute("javax.servlet.request.X509Certificate", sslCert.toString()); + } + } + + public void parsedSslCipher(Buffer sslCipher) throws IOException + { + _request.setAttribute("javax.servlet.request.cipher_suite", sslCipher.toString()); + } + + public void parsedSslSession(Buffer sslSession) throws IOException + { + _request.setAttribute("javax.servlet.request.ssl_session", sslSession.toString()); + } + + public void parsedSslKeySize(int keySize) throws IOException + { + _request.setAttribute("javax.servlet.request.key_size", new Integer(keySize)); + } + + public void parsedMethod(Buffer method) throws IOException + { + if (method == null) + throw new HttpException(HttpServletResponse.SC_BAD_REQUEST); + _request.setMethod(method.toString()); + } + + public void parsedUri(Buffer uri) throws IOException + { + _uri.parse(uri.toString()); + } + + public void parsedProtocol(Buffer protocol) throws IOException + { + if (protocol != null && protocol.length()>0) + { + _request.setProtocol(protocol.toString()); + } + } + + public void parsedRemoteAddr(Buffer addr) throws IOException + { + if (addr != null && addr.length()>0) + { + _request.setRemoteAddr(addr.toString()); + } + } + + public void parsedRemoteHost(Buffer name) throws IOException + { + if (name != null && name.length()>0) + { + _request.setRemoteHost(name.toString()); + } + } + + public void parsedServerName(Buffer name) throws IOException + { + if (name != null && name.length()>0) + { + _request.setServerName(name.toString()); + } + } + + public void parsedServerPort(int port) throws IOException + { + _request.setServerPort(port); + } + + public void parsedSslSecure(boolean secure) throws IOException + { + ((Ajp13Request) _request).setSslSecure(secure); + } + + public void parsedQueryString(Buffer value) throws IOException + { + String u = _uri + "?" + value; + _uri.parse(u); + } + + public void parsedHeader(Buffer name, Buffer value) throws IOException + { + _requestFields.add(name, value); + } + + public void parsedRequestAttribute(String key, Buffer value) throws IOException + { + _request.setAttribute(key, value.toString()); + } + + public void parsedRequestAttribute(String key, int value) throws IOException + { + _request.setAttribute(key, Integer.toString(value)); + } + + public void headerComplete() throws IOException + { + if (((Ajp13Parser) _parser).getContentLength() <= 0) + { + handleRequest(); + } + else + { + _delayedHandling = true; + } + } + + public void messageComplete(long contextLength) throws IOException + { + } + + public void content(Buffer ref) throws IOException + { + if (_delayedHandling) + { + _delayedHandling = false; + handleRequest(); + } + } + + } + +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java new file mode 100644 index 00000000000..aca8bb100e7 --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java @@ -0,0 +1,803 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import java.io.IOException; +import java.util.HashMap; + +import org.eclipse.jetty.http.AbstractGenerator; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpTokens; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * + * + */ +public class Ajp13Generator extends AbstractGenerator +{ + private static HashMap __headerHash = new HashMap(); + + static + { + byte[] xA001 = + { (byte) 0xA0, (byte) 0x01 }; + byte[] xA002 = + { (byte) 0xA0, (byte) 0x02 }; + byte[] xA003 = + { (byte) 0xA0, (byte) 0x03 }; + byte[] xA004 = + { (byte) 0xA0, (byte) 0x04 }; + byte[] xA005 = + { (byte) 0xA0, (byte) 0x05 }; + byte[] xA006 = + { (byte) 0xA0, (byte) 0x06 }; + byte[] xA007 = + { (byte) 0xA0, (byte) 0x07 }; + byte[] xA008 = + { (byte) 0xA0, (byte) 0x08 }; + byte[] xA009 = + { (byte) 0xA0, (byte) 0x09 }; + byte[] xA00A = + { (byte) 0xA0, (byte) 0x0A }; + byte[] xA00B = + { (byte) 0xA0, (byte) 0x0B }; + __headerHash.put("Content-Type", xA001); + __headerHash.put("Content-Language", xA002); + __headerHash.put("Content-Length", xA003); + __headerHash.put("Date", xA004); + __headerHash.put("Last-Modified", xA005); + __headerHash.put("Location", xA006); + __headerHash.put("Set-Cookie", xA007); + __headerHash.put("Set-Cookie2", xA008); + __headerHash.put("Servlet-Engine", xA009); + __headerHash.put("Status", xA00A); + __headerHash.put("WWW-Authenticate", xA00B); + + } + + // A, B ajp response header + // 0, 1 ajp int 1 packet length + // 9 CPONG response Code + private static final byte[] AJP13_CPONG_RESPONSE = + { 'A', 'B', 0, 1, 9}; + + private static final byte[] AJP13_END_RESPONSE = + { 'A', 'B', 0, 2, 5, 1 }; + + // AB ajp respose + // 0, 3 int = 3 packets in length + // 6, send signal to get more data + // 31, -7 byte values for int 8185 = (8 * 1024) - 7 MAX_DATA + private static final byte[] AJP13_MORE_CONTENT = + { 'A', 'B', 0, 3, 6, 31, -7 }; + + private static String SERVER = "Server: Jetty(6.0.x)"; + + public static void setServerVersion(String version) + { + SERVER = "Jetty(" + version + ")"; + } + + /* ------------------------------------------------------------ */ + private boolean _expectMore = false; + + private boolean _needMore = false; + + private boolean _needEOC = false; + + private boolean _bufferPrepared = false; + + /* ------------------------------------------------------------ */ + public Ajp13Generator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize) + { + super(buffers, io, headerBufferSize, contentBufferSize); + } + + /* ------------------------------------------------------------ */ + public void reset(boolean returnBuffers) + { + super.reset(returnBuffers); + + _needEOC = false; + _needMore = false; + _expectMore = false; + _bufferPrepared = false; + _last=false; + + + + _state = STATE_HEADER; + + _status = 0; + _version = HttpVersions.HTTP_1_1_ORDINAL; + _reason = null; + _method = null; + _uri = null; + + _contentWritten = 0; + _contentLength = HttpTokens.UNKNOWN_CONTENT; + _last = false; + _head = false; + _noContent = false; + _close = false; + + + + + _header = null; // Buffer for HTTP header (and maybe small _content) + _buffer = null; // Buffer for copy of passed _content + _content = null; // Buffer passed to addContent + + + } + + /* ------------------------------------------------------------ */ + /** + * Add content. + * + * @param content + * @param last + * @throws IllegalArgumentException + * if content is + * {@link Buffer#isImmutable immutable}. + * @throws IllegalStateException + * If the request is not expecting any more content, or if the + * buffers are full and cannot be flushed. + * @throws IOException + * if there is a problem flushing the buffers. + */ + public void addContent(Buffer content, boolean last) throws IOException + { + if (_noContent) + { + content.clear(); + return; + } + + if (content.isImmutable()) + throw new IllegalArgumentException("immutable"); + + if (_last || _state == STATE_END) + { + Log.debug("Ignoring extra content {}", content); + content.clear(); + return; + } + _last = last; + + if(!_endp.isOpen()) + { + _state = STATE_END; + return; + } + + // Handle any unfinished business? + if (_content != null && _content.length() > 0) + { + + flushBuffer(); + if (_content != null && _content.length() > 0) + throw new IllegalStateException("FULL"); + } + + _content = content; + + _contentWritten += content.length(); + + // Handle the _content + if (_head) + { + + content.clear(); + _content = null; + } + else + { + // Yes - so we better check we have a buffer + initContent(); + // Copy _content to buffer; + int len = 0; + len = _buffer.put(_content); + + // make sure there is space for a trailing null + if (len > 0 && _buffer.space() == 0) + { + len--; + _buffer.setPutIndex(_buffer.putIndex() - 1); + } + + _content.skip(len); + + if (_content.length() == 0) + _content = null; + } + } + + /* ------------------------------------------------------------ */ + /** + * Add content. + * + * @param b + * byte + * @return true if the buffers are full + * @throws IOException + */ + public boolean addContent(byte b) throws IOException + { + + if (_noContent) + return false; + + if (_last || _state == STATE_END) + throw new IllegalStateException("Closed"); + + + if(!_endp.isOpen()) + { + _state = STATE_END; + return false; + } + + // Handle any unfinished business? + if (_content != null && _content.length() > 0) + { + flushBuffer(); + if (_content != null && _content.length() > 0) + throw new IllegalStateException("FULL"); + } + + _contentWritten++; + + // Handle the _content + if (_head) + return false; + + // we better check we have a buffer + initContent(); + + // Copy _content to buffer; + + _buffer.put(b); + + return _buffer.space() <= 1; + } + + /* ------------------------------------------------------------ */ + /** + * Prepare buffer for unchecked writes. Prepare the generator buffer to + * receive unchecked writes + * + * @return the available space in the buffer. + * @throws IOException + */ + public int prepareUncheckedAddContent() throws IOException + { + if (_noContent) + return -1; + + if (_last || _state == STATE_END) + throw new IllegalStateException("Closed"); + + + if(!_endp.isOpen()) + { + _state = STATE_END; + return -1; + } + + // Handle any unfinished business? + Buffer content = _content; + if (content != null && content.length() > 0) + { + flushBuffer(); + if (content != null && content.length() > 0) + throw new IllegalStateException("FULL"); + } + + // we better check we have a buffer + initContent(); + + _contentWritten -= _buffer.length(); + + // Handle the _content + if (_head) + return Integer.MAX_VALUE; + + return _buffer.space() - 1; + } + + /* ------------------------------------------------------------ */ + public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException + { + if (_state != STATE_HEADER) + return; + + if (_last && !allContentAdded) + throw new IllegalStateException("last?"); + _last = _last | allContentAdded; + + boolean has_server = false; + if (_version == HttpVersions.HTTP_1_0_ORDINAL) + _close = true; + + // get a header buffer + if (_header == null) + _header = _buffers.getBuffer(_headerBufferSize); + + Buffer tmpbuf = _buffer; + _buffer = _header; + + try + { + // start the header + _buffer.put((byte) 'A'); + _buffer.put((byte) 'B'); + addInt(0); + _buffer.put((byte) 0x4); + addInt(_status); + if (_reason == null) + _reason=HttpGenerator.getReasonBuffer(_status); + if (_reason == null) + _reason = new ByteArrayBuffer(TypeUtil.toString(_status)); + addBuffer(_reason); + + if (_status == 100 || _status == 204 || _status == 304) + { + _noContent = true; + _content = null; + } + + + // allocate 2 bytes for number of headers + int field_index = _buffer.putIndex(); + addInt(0); + + int num_fields = 0; + + if (fields != null) + { + // Add headers + int s=fields.size(); + for (int f=0;f 100 && getSendServerVersion()) + { + num_fields++; + addString("Server"); + addString(SERVER); + } + + // TODO Add content length if last content known. + + // insert the number of headers + int tmp = _buffer.putIndex(); + _buffer.setPutIndex(field_index); + addInt(num_fields); + _buffer.setPutIndex(tmp); + + // get the payload size ( - 4 bytes for the ajp header) + // excluding the + // ajp header + int payloadSize = _buffer.length() - 4; + // insert the total packet size on 2nd and 3rd byte that + // was previously + // allocated + addInt(2, payloadSize); + } + finally + { + _buffer = tmpbuf; + } + + + _state = STATE_CONTENT; + + } + + /* ------------------------------------------------------------ */ + /** + * Complete the message. + * + * @throws IOException + */ + public void complete() throws IOException + { + if (_state == STATE_END) + return; + + super.complete(); + + if (_state < STATE_FLUSHING) + { + _state = STATE_FLUSHING; + _needEOC = true; + } + + flushBuffer(); + } + + /* ------------------------------------------------------------ */ + public long flushBuffer() throws IOException + { + try + { + if (_state == STATE_HEADER && !_expectMore) + throw new IllegalStateException("State==HEADER"); + prepareBuffers(); + + if (_endp == null) + { + // TODO - probably still needed! + // if (_rneedMore && _buffe != null) + // { + // if(!_hasSentEOC) + // _buffer.put(AJP13_MORE_CONTENT); + // } + if (!_expectMore && _needEOC && _buffer != null) + { + _buffer.put(AJP13_END_RESPONSE); + } + _needEOC = false; + return 0; + } + + // Keep flushing while there is something to flush + // (except break below) + int total = 0; + long last_len = -1; + Flushing: while (true) + { + int len = -1; + int to_flush = ((_header != null && _header.length() > 0) ? 4 : 0) | ((_buffer != null && _buffer.length() > 0) ? 2 : 0); + + + switch (to_flush) + { + case 7: + throw new IllegalStateException(); // should + // never + // happen! + case 6: + len = _endp.flush(_header, _buffer, null); + + break; + case 5: + throw new IllegalStateException(); // should + // never + // happen! + case 4: + len = _endp.flush(_header); + break; + case 3: + throw new IllegalStateException(); // should + // never + // happen! + case 2: + len = _endp.flush(_buffer); + + break; + case 1: + throw new IllegalStateException(); // should + // never + // happen! + case 0: + { + // Nothing more we can write now. + if (_header != null) + _header.clear(); + + _bufferPrepared = false; + + if (_buffer != null) + { + _buffer.clear(); + + // reserve some space for the + // header + _buffer.setPutIndex(7); + _buffer.setGetIndex(7); + + // Special case handling for + // small left over buffer from + // an addContent that caused a + // buffer flush. + if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + { + + _buffer.put(_content); + _content.clear(); + _content = null; + break Flushing; + } + + } + + + + // Are we completely finished for now? + if (!_expectMore && !_needEOC && (_content == null || _content.length() == 0)) + { + if (_state == STATE_FLUSHING) + _state = STATE_END; + +// if (_state == STATE_END) +// { +// _endp.close(); +// } +// + + break Flushing; + } + + // Try to prepare more to write. + prepareBuffers(); + } + } + + // If we failed to flush anything twice in a row + // break + if (len <= 0) + { + if (last_len <= 0) + break Flushing; + break; + } + last_len = len; + total += len; + } + + + + return total; + } + catch (IOException e) + { + Log.ignore(e); + throw (e instanceof EofException) ? e : new EofException(e); + } + + } + + /* ------------------------------------------------------------ */ + private void prepareBuffers() + { + if (!_bufferPrepared) + { + + // Refill buffer if possible + if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0) + { + + int len = _buffer.put(_content); + + // Make sure there is space for a trailing null + if (len > 0 && _buffer.space() == 0) + { + len--; + _buffer.setPutIndex(_buffer.putIndex() - 1); + } + _content.skip(len); + + if (_content.length() == 0) + _content = null; + + if (_buffer.length() == 0) + { + _content = null; + } + } + + // add header if needed + if (_buffer != null) + { + + int payloadSize = _buffer.length(); + + // 4 bytes for the ajp header + // 1 byte for response type + // 2 bytes for the response size + // 1 byte because we count from zero?? + + if (payloadSize > 0) + { + _bufferPrepared = true; + + _buffer.put((byte) 0); + int put = _buffer.putIndex(); + _buffer.setGetIndex(0); + _buffer.setPutIndex(0); + _buffer.put((byte) 'A'); + _buffer.put((byte) 'B'); + addInt(payloadSize + 4); + _buffer.put((byte) 3); + addInt(payloadSize); + _buffer.setPutIndex(put); + } + } + + if (_needMore) + { + + if (_header == null) + { + _header = _buffers.getBuffer(_headerBufferSize); + } + + if (_buffer == null && _header != null && _header.space() >= AJP13_MORE_CONTENT.length) + { + _header.put(AJP13_MORE_CONTENT); + _needMore = false; + } + else if (_buffer != null && _buffer.space() >= AJP13_MORE_CONTENT.length) + { + // send closing packet if all contents + // are added + _buffer.put(AJP13_MORE_CONTENT); + _needMore = false; + _bufferPrepared = true; + } + + } + + if (!_expectMore && _needEOC) + { + if (_buffer == null && _header.space() >= AJP13_END_RESPONSE.length) + { + + _header.put(AJP13_END_RESPONSE); + _needEOC = false; + } + else if (_buffer != null && _buffer.space() >= AJP13_END_RESPONSE.length) + { + // send closing packet if all contents + // are added + + _buffer.put(AJP13_END_RESPONSE); + _needEOC = false; + _bufferPrepared = true; + } + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isComplete() + { + return !_expectMore && _state == STATE_END; + } + + /* ------------------------------------------------------------ */ + private void initContent() throws IOException + { + if (_buffer == null) + { + _buffer = _buffers.getBuffer(_contentBufferSize); + _buffer.setPutIndex(7); + _buffer.setGetIndex(7); + } + } + + /* ------------------------------------------------------------ */ + private void addInt(int i) + { + _buffer.put((byte) ((i >> 8) & 0xFF)); + _buffer.put((byte) (i & 0xFF)); + } + + /* ------------------------------------------------------------ */ + private void addInt(int startIndex, int i) + { + _buffer.poke(startIndex, (byte) ((i >> 8) & 0xFF)); + _buffer.poke((startIndex + 1), (byte) (i & 0xFF)); + } + + /* ------------------------------------------------------------ */ + private void addString(String str) + { + if (str == null) + { + addInt(0xFFFF); + return; + } + + // TODO - need to use a writer to convert, to avoid this hacky + // conversion and temp buffer + byte[] b = str.getBytes(); + + addInt(b.length); + + _buffer.put(b); + _buffer.put((byte) 0); + } + + /* ------------------------------------------------------------ */ + private void addBuffer(Buffer b) + { + if (b == null) + { + addInt(0xFFFF); + return; + } + + addInt(b.length()); + _buffer.put(b); + _buffer.put((byte) 0); + } + + /* ------------------------------------------------------------ */ + public void getBodyChunk() throws IOException + { + _needMore = true; + _expectMore = true; + flushBuffer(); + } + + /* ------------------------------------------------------------ */ + public void gotBody() + { + _needMore = false; + _expectMore = false; + } + + + /* ------------------------------------------------------------ */ + public void sendCPong() throws IOException + { + + Buffer buff = _buffers.getBuffer(AJP13_CPONG_RESPONSE.length); + buff.put(AJP13_CPONG_RESPONSE); + + // flushing cpong response + do + { + _endp.flush(buff); + + } + while(buff.length() >0); + _buffers.returnBuffer(buff); + + reset(true); + + } + + + +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Packet.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Packet.java new file mode 100644 index 00000000000..cfaca117774 --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Packet.java @@ -0,0 +1,63 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import org.eclipse.jetty.io.BufferCache; + +/** + * + */ +public class Ajp13Packet +{ + + public final static int MAX_PACKET_SIZE=(8*1024); + public final static int HDR_SIZE=4; + + // Used in writing response... + public final static int DATA_HDR_SIZE=7; + public final static int MAX_DATA_SIZE=MAX_PACKET_SIZE-DATA_HDR_SIZE; + + public final static String + // Server -> Container + FORWARD_REQUEST="FORWARD REQUEST", + SHUTDOWN="SHUTDOWN", + PING_REQUEST="PING REQUEST", // Obsolete + CPING_REQUEST="CPING REQUEST", + + // Server <- Container + SEND_BODY_CHUNK="SEND BODY CHUNK", SEND_HEADERS="SEND HEADERS", END_RESPONSE="END RESPONSE", + GET_BODY_CHUNK="GET BODY CHUNK", + CPONG_REPLY="CPONG REPLY"; + + public final static int FORWARD_REQUEST_ORDINAL=2, SHUTDOWN_ORDINAL=7, + PING_REQUEST_ORDINAL=8, // Obsolete + CPING_REQUEST_ORDINAL=10, SEND_BODY_CHUNK_ORDINAL=3, SEND_HEADERS_ORDINAL=4, END_RESPONSE_ORDINAL=5, GET_BODY_CHUNK_ORDINAL=6, + CPONG_REPLY_ORDINAL=9; + + public final static BufferCache CACHE=new BufferCache(); + + static + { + CACHE.add(FORWARD_REQUEST,FORWARD_REQUEST_ORDINAL); + CACHE.add(SHUTDOWN,SHUTDOWN_ORDINAL); + CACHE.add(PING_REQUEST,PING_REQUEST_ORDINAL); // Obsolete + CACHE.add(CPING_REQUEST,CPING_REQUEST_ORDINAL); + CACHE.add(SEND_BODY_CHUNK,SEND_BODY_CHUNK_ORDINAL); + CACHE.add(SEND_HEADERS,SEND_HEADERS_ORDINAL); + CACHE.add(END_RESPONSE,END_RESPONSE_ORDINAL); + CACHE.add(GET_BODY_CHUNK,GET_BODY_CHUNK_ORDINAL); + CACHE.add(CPONG_REPLY,CPONG_REPLY_ORDINAL); + } + +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13PacketMethods.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13PacketMethods.java new file mode 100644 index 00000000000..8e064644863 --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13PacketMethods.java @@ -0,0 +1,69 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/** + * + */ +public class Ajp13PacketMethods +{ + + // TODO - this can probably be replaced by HttpMethods or at least an + // extension of it. + // It is probably most efficient if "GET" ends up as the same instance + + public final static String OPTIONS="OPTIONS", GET="GET", HEAD="HEAD", POST="POST", PUT="PUT", DELETE="DELETE", TRACE="TRACE", PROPFIND="PROPFIND", + PROPPATCH="PROPPATCH", MKCOL="MKCOL", COPY="COPY", MOVE="MOVE", LOCK="LOCK", UNLOCK="UNLOCK", ACL="ACL", REPORT="REPORT", + VERSION_CONTROL="VERSION-CONTROL", CHECKIN="CHECKIN", CHECKOUT="CHECKOUT", UNCHCKOUT="UNCHECKOUT", SEARCH="SEARCH", MKWORKSPACE="MKWORKSPACE", + UPDATE="UPDATE", LABEL="LABEL", MERGE="MERGE", BASELINE_CONTROL="BASELINE-CONTROL", MKACTIVITY="MKACTIVITY"; + + public final static int OPTIONS_ORDINAL=1, GET_ORDINAL=2, HEAD_ORDINAL=3, POST__ORDINAL=4, PUT_ORDINAL=5, DELETE_ORDINAL=6, TRACE_ORDINAL=7, + PROPFIND_ORDINAL=8, PROPPATCH_ORDINAL=9, MKCOL_ORDINAL=10, COPY_ORDINAL=11, MOVE_ORDINAL=12, LOCK_ORDINAL=13, UNLOCK_ORDINAL=14, ACL_ORDINAL=15, + REPORT_ORDINAL=16, VERSION_CONTROL_ORDINAL=17, CHECKIN_ORDINAL=18, CHECKOUT_ORDINAL=19, UNCHCKOUT_ORDINAL=20, SEARCH_ORDINAL=21, + MKWORKSPACE_ORDINAL=22, UPDATE_ORDINAL=23, LABEL_ORDINAL=24, MERGE_ORDINAL=25, BASELINE_CONTROL_ORDINAL=26, MKACTIVITY_ORDINAL=27; + + public final static BufferCache CACHE=new BufferCache(); + + public final static Buffer + OPTIONS_BUFFER=CACHE.add(OPTIONS,OPTIONS_ORDINAL), + GET_BUFFER=CACHE.add(GET,GET_ORDINAL), + HEAD_BUFFER=CACHE.add(HEAD, HEAD_ORDINAL), + POST__BUFFER=CACHE.add(POST,POST__ORDINAL), + PUT_BUFFER=CACHE.add(PUT,PUT_ORDINAL), + DELETE_BUFFER=CACHE.add(DELETE,DELETE_ORDINAL), + TRACE_BUFFER=CACHE.add(TRACE,TRACE_ORDINAL), + PROPFIND_BUFFER=CACHE.add(PROPFIND,PROPFIND_ORDINAL), + PROPPATCH_BUFFER=CACHE.add(PROPPATCH, PROPPATCH_ORDINAL), + MKCOL_BUFFER=CACHE.add(MKCOL,MKCOL_ORDINAL), + COPY_BUFFER=CACHE.add(COPY,COPY_ORDINAL), + MOVE_BUFFER=CACHE.add(MOVE,MOVE_ORDINAL), + LOCK_BUFFER=CACHE.add(LOCK,LOCK_ORDINAL), + UNLOCK_BUFFER=CACHE.add(UNLOCK,UNLOCK_ORDINAL), + ACL_BUFFER=CACHE.add(ACL,ACL_ORDINAL), + REPORT_BUFFER=CACHE.add(REPORT,REPORT_ORDINAL), + VERSION_CONTROL_BUFFER=CACHE.add(VERSION_CONTROL,VERSION_CONTROL_ORDINAL), + CHECKIN_BUFFER=CACHE.add(CHECKIN,CHECKIN_ORDINAL), + CHECKOUT_BUFFER=CACHE.add(CHECKOUT,CHECKOUT_ORDINAL), + UNCHCKOUT_BUFFER=CACHE.add(UNCHCKOUT,UNCHCKOUT_ORDINAL), + SEARCH_BUFFER=CACHE.add(SEARCH,SEARCH_ORDINAL), + MKWORKSPACE_BUFFER=CACHE.add(MKWORKSPACE,MKWORKSPACE_ORDINAL), + UPDATE_BUFFER=CACHE.add(UPDATE,UPDATE_ORDINAL), + LABEL_BUFFER=CACHE.add(LABEL,LABEL_ORDINAL), + MERGE_BUFFER=CACHE.add(MERGE,MERGE_ORDINAL), + BASELINE_CONTROL_BUFFER=CACHE.add(BASELINE_CONTROL,BASELINE_CONTROL_ORDINAL), + MKACTIVITY_BUFFER=CACHE.add(MKACTIVITY,MKACTIVITY_ORDINAL); +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java new file mode 100644 index 00000000000..81ade2c722b --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java @@ -0,0 +1,876 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import java.io.IOException; +import java.io.InterruptedIOException; + +import javax.servlet.ServletInputStream; + +import org.eclipse.jetty.http.HttpTokens; +import org.eclipse.jetty.http.Parser; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.util.log.Log; + +/** + * + */ +public class Ajp13Parser implements Parser +{ + private final static int STATE_START = -1; + private final static int STATE_END = 0; + private final static int STATE_AJP13CHUNK_START = 1; + private final static int STATE_AJP13CHUNK = 2; + + private int _state = STATE_START; + private long _contentLength; + private long _contentPosition; + private int _chunkLength; + private int _chunkPosition; + private int _headers; + private Buffers _buffers; + private EndPoint _endp; + private Buffer _buffer; + private Buffer _header; // Buffer for header data (and small _content) + private Buffer _body; // Buffer for large content + private View _contentView = new View(); + private EventHandler _handler; + private Ajp13Generator _generator; + private View _tok0; // Saved token: header name, request method or response version + private View _tok1; // Saved token: header value, request URI orresponse code + protected int _length; + protected int _packetLength; + + + /* ------------------------------------------------------------------------------- */ + public Ajp13Parser(Buffers buffers, EndPoint endPoint) + { + _buffers = buffers; + _endp = endPoint; + } + + /* ------------------------------------------------------------------------------- */ + public void setEventHandler(EventHandler handler) + { + _handler=handler; + } + + /* ------------------------------------------------------------------------------- */ + public void setGenerator(Ajp13Generator generator) + { + _generator=generator; + } + + /* ------------------------------------------------------------------------------- */ + public long getContentLength() + { + return _contentLength; + } + + /* ------------------------------------------------------------------------------- */ + public int getState() + { + return _state; + } + + /* ------------------------------------------------------------------------------- */ + public boolean inContentState() + { + return _state > 0; + } + + /* ------------------------------------------------------------------------------- */ + public boolean inHeaderState() + { + return _state < 0; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isIdle() + { + return _state == STATE_START; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isComplete() + { + return _state == STATE_END; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isMoreInBuffer() + { + + if (_header != null && _header.hasContent() || _body != null && _body.hasContent()) + return true; + + return false; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isState(int state) + { + return _state == state; + } + + /* ------------------------------------------------------------------------------- */ + public void parse() throws IOException + { + if (_state == STATE_END) + reset(false); + if (_state != STATE_START) + throw new IllegalStateException("!START"); + + // continue parsing + while (!isComplete()) + { + parseNext(); + } + } + + /* ------------------------------------------------------------------------------- */ + public long parseAvailable() throws IOException + { + long len = parseNext(); + long total = len > 0 ? len : 0; + + // continue parsing + while (!isComplete() && _buffer != null && _buffer.length() > 0) + { + len = parseNext(); + if (len > 0) + total += len; + else + break; + } + return total; + } + + /* ------------------------------------------------------------------------------- */ + private int fill() throws IOException + { + int filled = -1; + if (_body != null && _buffer != _body) + { + // mod_jk implementations may have some partial data from header + // check if there are partial contents in the header + // copy it to the body if there are any + if(_header.length() > 0) + { + // copy the patial data from the header to the body + _body.put(_header); + } + + _buffer = _body; + + if (_buffer.length()>0) + { + filled = _buffer.length(); + return filled; + } + } + + if (_buffer.markIndex() == 0 && _buffer.putIndex() == _buffer.capacity()) + throw new IOException("FULL"); + if (_endp != null && filled <= 0) + { + // Compress buffer if handling _content buffer + // TODO check this is not moving data too much + if (_buffer == _body) + _buffer.compact(); + + if (_buffer.space() == 0) + throw new IOException("FULL"); + + try + { + filled = _endp.fill(_buffer); + } + catch (IOException e) + { + // This is normal in AJP since the socket closes on timeout only + Log.debug(e); + reset(true); + throw (e instanceof EofException) ? e : new EofException(e); + } + } + + if (filled < 0) + { + if (_state > STATE_END) + { + _state = STATE_END; + _handler.messageComplete(_contentPosition); + return filled; + } + reset(true); + throw new EofException(); + } + + return filled; + } + + /* ------------------------------------------------------------------------------- */ + public long parseNext() throws IOException + { + long total_filled = -1; + + if (_buffer == null) + { + if (_header == null) + { + _header = _buffers.getBuffer(Ajp13Packet.MAX_PACKET_SIZE); + _header.clear(); + } + _buffer = _header; + _tok0 = new View(_header); + _tok1 = new View(_header); + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + } + + if (_state == STATE_END) + throw new IllegalStateException("STATE_END"); + if (_state > STATE_END && _contentPosition == _contentLength) + { + _state = STATE_END; + _handler.messageComplete(_contentPosition); + return total_filled; + } + + if (_state < 0) + { + // have we seen a packet? + if (_packetLength<=0) + { + if (_buffer.length()<4) + { + if (total_filled<0) + total_filled=0; + total_filled+=fill(); + if (_buffer.length()<4) + return total_filled; + } + + _contentLength = HttpTokens.UNKNOWN_CONTENT; + int _magic = Ajp13RequestPacket.getInt(_buffer); + if (_magic != Ajp13RequestHeaders.MAGIC) + throw new IOException("Bad AJP13 rcv packet: " + "0x" + Integer.toHexString(_magic) + " expected " + "0x" + Integer.toHexString(Ajp13RequestHeaders.MAGIC) + " " + this); + + + _packetLength = Ajp13RequestPacket.getInt(_buffer); + if (_packetLength > Ajp13Packet.MAX_PACKET_SIZE) + throw new IOException("AJP13 packet (" + _packetLength + "bytes) too large for buffer"); + + } + + if (_buffer.length() < _packetLength) + { + if (total_filled<0) + total_filled=0; + total_filled+=fill(); + if (_buffer.length() < _packetLength) + return total_filled; + } + + // Parse Header + Buffer bufHeaderName = null; + Buffer bufHeaderValue = null; + int attr_type = 0; + + byte packetType = Ajp13RequestPacket.getByte(_buffer); + + switch (packetType) + { + case Ajp13Packet.FORWARD_REQUEST_ORDINAL: + _handler.startForwardRequest(); + break; + case Ajp13Packet.CPING_REQUEST_ORDINAL: + ((Ajp13Generator) _generator).sendCPong(); + + if(_header != null) + { + _buffers.returnBuffer(_header); + _header = null; + } + + if(_body != null) + { + _buffers.returnBuffer(_body); + _body = null; + } + + _buffer= null; + + reset(true); + + return -1; + case Ajp13Packet.SHUTDOWN_ORDINAL: + shutdownRequest(); + + return -1; + + default: + // XXX Throw an Exception here?? Close + // connection! + Log.warn("AJP13 message type ({PING}: "+packetType+" ) not supported/recognized as an AJP request"); + throw new IllegalStateException("PING is not implemented"); + } + + + _handler.parsedMethod(Ajp13RequestPacket.getMethod(_buffer)); + _handler.parsedProtocol(Ajp13RequestPacket.getString(_buffer, _tok0)); + _handler.parsedUri(Ajp13RequestPacket.getString(_buffer, _tok1)); + _handler.parsedRemoteAddr(Ajp13RequestPacket.getString(_buffer, _tok1)); + _handler.parsedRemoteHost(Ajp13RequestPacket.getString(_buffer, _tok1)); + _handler.parsedServerName(Ajp13RequestPacket.getString(_buffer, _tok1)); + _handler.parsedServerPort(Ajp13RequestPacket.getInt(_buffer)); + _handler.parsedSslSecure(Ajp13RequestPacket.getBool(_buffer)); + + + _headers = Ajp13RequestPacket.getInt(_buffer); + + for (int h=0;h<_headers;h++) + { + bufHeaderName = Ajp13RequestPacket.getHeaderName(_buffer, _tok0); + bufHeaderValue = Ajp13RequestPacket.getString(_buffer, _tok1); + + if (bufHeaderName != null && bufHeaderName.toString().equals(Ajp13RequestHeaders.CONTENT_LENGTH)) + { + _contentLength = BufferUtil.toLong(bufHeaderValue); + if (_contentLength == 0) + _contentLength = HttpTokens.NO_CONTENT; + } + + _handler.parsedHeader(bufHeaderName, bufHeaderValue); + } + + + + attr_type = Ajp13RequestPacket.getByte(_buffer) & 0xff; + while (attr_type != 0xFF) + { + + switch (attr_type) + { + // XXX How does this plug into the web + // containers + // authentication? + + case Ajp13RequestHeaders.REMOTE_USER_ATTR: + _handler.parsedRemoteUser(Ajp13RequestPacket.getString(_buffer, _tok1)); + break; + case Ajp13RequestHeaders.AUTH_TYPE_ATTR: + //XXX JASPI how does this make sense? + _handler.parsedAuthorizationType(Ajp13RequestPacket.getString(_buffer, _tok1)); + break; + + case Ajp13RequestHeaders.QUERY_STRING_ATTR: + _handler.parsedQueryString(Ajp13RequestPacket.getString(_buffer, _tok1)); + break; + + case Ajp13RequestHeaders.JVM_ROUTE_ATTR: + // XXX Using old Jetty 5 key, + // should change! + // Note used in + // org.eclipse.jetty.servlet.HashSessionIdManager + _handler.parsedRequestAttribute("org.eclipse.http.ajp.JVMRoute", Ajp13RequestPacket.getString(_buffer, _tok1)); + break; + + case Ajp13RequestHeaders.SSL_CERT_ATTR: + _handler.parsedSslCert(Ajp13RequestPacket.getString(_buffer, _tok1)); + break; + + case Ajp13RequestHeaders.SSL_CIPHER_ATTR: + _handler.parsedSslCipher(Ajp13RequestPacket.getString(_buffer, _tok1)); + // SslSocketConnector.customize() + break; + + case Ajp13RequestHeaders.SSL_SESSION_ATTR: + _handler.parsedSslSession(Ajp13RequestPacket.getString(_buffer, _tok1)); + break; + + case Ajp13RequestHeaders.REQUEST_ATTR: + _handler.parsedRequestAttribute(Ajp13RequestPacket.getString(_buffer, _tok0).toString(), Ajp13RequestPacket.getString(_buffer, _tok1)); + break; + + // New Jk API? + // Check if experimental or can they + // assumed to be + // supported + + case Ajp13RequestHeaders.SSL_KEYSIZE_ATTR: + + // This has been implemented in AJP13 as either a string or a integer. + // Servlet specs say javax.servlet.request.key_size must be an Integer + + // Does it look like a string containing digits? + int length = Ajp13RequestPacket.getInt(_buffer); + + if (length>0 && length<16) + { + // this must be a string length rather than a key length + _buffer.skip(-2); + _handler.parsedSslKeySize(Integer.parseInt(Ajp13RequestPacket.getString(_buffer, _tok1).toString())); + } + else + _handler.parsedSslKeySize(length); + + break; + + + // Used to lock down jk requests with a + // secreate + // key. + + case Ajp13RequestHeaders.SECRET_ATTR: + // XXX Investigate safest way to + // deal with + // this... + // should this tie into shutdown + // packet? + break; + + case Ajp13RequestHeaders.STORED_METHOD_ATTR: + // XXX Confirm this should + // really overide + // previously parsed method? + // _handler.parsedMethod(Ajp13PacketMethods.CACHE.get(Ajp13RequestPacket.getString())); + break; + + + case Ajp13RequestHeaders.CONTEXT_ATTR: + _handler.parsedContextPath(Ajp13RequestPacket.getString(_buffer, _tok1)); + break; + case Ajp13RequestHeaders.SERVLET_PATH_ATTR: + _handler.parsedServletPath(Ajp13RequestPacket.getString(_buffer, _tok1)); + + break; + default: + Log.warn("Unsupported Ajp13 Request Attribute {}", new Integer(attr_type)); + break; + } + + attr_type = Ajp13RequestPacket.getByte(_buffer) & 0xff; + } + + + + + + + _contentPosition = 0; + switch ((int) _contentLength) + { + + case HttpTokens.NO_CONTENT: + _state = STATE_END; + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + + break; + + case HttpTokens.UNKNOWN_CONTENT: + + _generator.getBodyChunk(); + if (_buffers != null && _body == null && _buffer == _header && _header.length() <= 0) + { + _body = _buffers.getBuffer(Ajp13Packet.MAX_PACKET_SIZE); + _body.clear(); + } + _state = STATE_AJP13CHUNK_START; + _handler.headerComplete(); // May recurse here! + + return total_filled; + + default: + + if (_buffers != null && _body == null && _buffer == _header && _contentLength > (_header.capacity() - _header.getIndex())) + { + _body = _buffers.getBuffer(Ajp13Packet.MAX_PACKET_SIZE); + _body.clear(); + + } + _state = STATE_AJP13CHUNK_START; + _handler.headerComplete(); // May recurse here! + return total_filled; + } + } + + + Buffer chunk; + + while (_state>STATE_END) + { + switch (_state) + { + case STATE_AJP13CHUNK_START: + if (_buffer.length()<6) + { + if (total_filled<0) + total_filled=0; + total_filled+=fill(); + if (_buffer.length()<6) + return total_filled; + } + int _magic=Ajp13RequestPacket.getInt(_buffer); + if (_magic!=Ajp13RequestHeaders.MAGIC) + { + throw new IOException("Bad AJP13 rcv packet: "+"0x"+Integer.toHexString(_magic)+" expected "+"0x" + +Integer.toHexString(Ajp13RequestHeaders.MAGIC)+" "+this); + } + _chunkPosition=0; + _chunkLength=Ajp13RequestPacket.getInt(_buffer)-2; + Ajp13RequestPacket.getInt(_buffer); + if (_chunkLength==0) + { + _state=STATE_END; + _generator.gotBody(); + _handler.messageComplete(_contentPosition); + return total_filled; + } + _state=STATE_AJP13CHUNK; + + case STATE_AJP13CHUNK: + if (_buffer.length()<_chunkLength) + { + if (total_filled<0) + total_filled=0; + total_filled+=fill(); + if (_buffer.length()<_chunkLength) + return total_filled; + } + + int remaining=_chunkLength-_chunkPosition; + + if (remaining==0) + { + _state=STATE_AJP13CHUNK_START; + if (_contentPosition<_contentLength) + { + _generator.getBodyChunk(); + } + else + { + _generator.gotBody(); + } + + return total_filled; + } + + if (_buffer.length() 0) + return true; + if (_parser.isState(Ajp13Parser.STATE_END)) + return false; + + // Handle simple end points. + if (_endp == null) + _parser.parseNext(); + + // Handle blocking end points + else if (_endp.isBlocking()) + { + _parser.parseNext(); + + // parse until some progress is made (or IOException thrown for timeout) + while (_content.length() == 0 && !_parser.isState(Ajp13Parser.STATE_END)) + { + // Try to get more _parser._content + _parser.parseNext(); + } + } + else // Handle non-blocking end point + { + long filled = _parser.parseNext(); + boolean blocked = false; + + // parse until some progress is made (or + // IOException thrown for timeout) + while (_content.length() == 0 && !_parser.isState(Ajp13Parser.STATE_END)) + { + // if fill called, but no bytes read, + // then block + if (filled > 0) + blocked = false; + else if (filled == 0) + { + if (blocked) + throw new InterruptedIOException("timeout"); + + blocked = true; + _endp.blockReadable(_maxIdleTime); + } + + // Try to get more _parser._content + filled = _parser.parseNext(); + } + } + + return _content.length() > 0; + } + + } +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java new file mode 100644 index 00000000000..d1ae515f6f6 --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java @@ -0,0 +1,113 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; + +public class Ajp13Request extends Request +{ + protected String _remoteAddr; + protected String _remoteHost; + protected String _remoteUser; + protected boolean _sslSecure; + + /* ------------------------------------------------------------ */ + public Ajp13Request(HttpConnection connection) + { + super(connection); + } + + /* ------------------------------------------------------------ */ + public Ajp13Request() + { + } + + /* ------------------------------------------------------------ */ + void setConnection(Ajp13Connection connection) + { + super.setConnection(connection); + } + + /* ------------------------------------------------------------ */ + public void setRemoteUser(String remoteUser) + { + _remoteUser = remoteUser; + } + + /* ------------------------------------------------------------ */ + public String getRemoteUser() + { + if(_remoteUser != null) + return _remoteUser; + return super.getRemoteUser(); + } + + /* ------------------------------------------------------------ */ + public String getRemoteAddr() + { + if (_remoteAddr != null) + return _remoteAddr; + if (_remoteHost != null) + return _remoteHost; + return super.getRemoteAddr(); + } + + + + /* ------------------------------------------------------------ */ + public void setRemoteAddr(String remoteAddr) + { + _remoteAddr = remoteAddr; + } + + /* ------------------------------------------------------------ */ + public String getRemoteHost() + { + if (_remoteHost != null) + return _remoteHost; + if (_remoteAddr != null) + return _remoteAddr; + return super.getRemoteHost(); + } + + /* ------------------------------------------------------------ */ + public void setRemoteHost(String remoteHost) + { + _remoteHost = remoteHost; + } + + /* ------------------------------------------------------------ */ + public boolean isSslSecure() + { + return _sslSecure; + } + + /* ------------------------------------------------------------ */ + public void setSslSecure(boolean sslSecure) + { + _sslSecure = sslSecure; + } + + /* ------------------------------------------------------------ */ + protected void recycle() + { + super.recycle(); + _remoteAddr = null; + _remoteHost = null; + _remoteUser = null; + _sslSecure = false; + } + +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestHeaders.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestHeaders.java new file mode 100644 index 00000000000..55d3d926684 --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestHeaders.java @@ -0,0 +1,62 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/** + * XXX Should this implement the Buffer interface? + * + * + */ +public class Ajp13RequestHeaders extends BufferCache +{ + + public final static int MAGIC=0x1234; + + public final static String ACCEPT="accept", ACCEPT_CHARSET="accept-charset", ACCEPT_ENCODING="accept-encoding", ACCEPT_LANGUAGE="accept-language", + AUTHORIZATION="authorization", CONNECTION="connection", CONTENT_TYPE="content-type", CONTENT_LENGTH="content-length", COOKIE="cookie", + COOKIE2="cookie2", HOST="host", PRAGMA="pragma", REFERER="referer", USER_AGENT="user-agent"; + + public final static int ACCEPT_ORDINAL=1, ACCEPT_CHARSET_ORDINAL=2, ACCEPT_ENCODING_ORDINAL=3, ACCEPT_LANGUAGE_ORDINAL=4, AUTHORIZATION_ORDINAL=5, + CONNECTION_ORDINAL=6, CONTENT_TYPE_ORDINAL=7, CONTENT_LENGTH_ORDINAL=8, COOKIE_ORDINAL=9, COOKIE2_ORDINAL=10, HOST_ORDINAL=11, PRAGMA_ORDINAL=12, + REFERER_ORDINAL=13, USER_AGENT_ORDINAL=14; + + public final static BufferCache CACHE=new BufferCache(); + + public final static Buffer ACCEPT_BUFFER=CACHE.add(ACCEPT,ACCEPT_ORDINAL), ACCEPT_CHARSET_BUFFER=CACHE.add(ACCEPT_CHARSET,ACCEPT_CHARSET_ORDINAL), + ACCEPT_ENCODING_BUFFER=CACHE.add(ACCEPT_ENCODING,ACCEPT_ENCODING_ORDINAL), ACCEPT_LANGUAGE_BUFFER=CACHE + .add(ACCEPT_LANGUAGE,ACCEPT_LANGUAGE_ORDINAL), AUTHORIZATION_BUFFER=CACHE.add(AUTHORIZATION,AUTHORIZATION_ORDINAL), CONNECTION_BUFFER=CACHE + .add(CONNECTION,CONNECTION_ORDINAL), CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL), CONTENT_LENGTH_BUFFER=CACHE.add( + CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL), COOKIE_BUFFER=CACHE.add(COOKIE,COOKIE_ORDINAL), COOKIE2_BUFFER=CACHE.add(COOKIE2,COOKIE2_ORDINAL), + HOST_BUFFER=CACHE.add(HOST,HOST_ORDINAL), PRAGMA_BUFFER=CACHE.add(PRAGMA,PRAGMA_ORDINAL), REFERER_BUFFER=CACHE.add(REFERER,REFERER_ORDINAL), + USER_AGENT_BUFFER=CACHE.add(USER_AGENT,USER_AGENT_ORDINAL); + + public final static byte + CONTEXT_ATTR=1, // Legacy + SERVLET_PATH_ATTR=2, // Legacy + REMOTE_USER_ATTR=3, + AUTH_TYPE_ATTR=4, + QUERY_STRING_ATTR=5, + JVM_ROUTE_ATTR=6, + SSL_CERT_ATTR=7, + SSL_CIPHER_ATTR=8, + SSL_SESSION_ATTR=9, + REQUEST_ATTR=10, + SSL_KEYSIZE_ATTR=11, + SECRET_ATTR=12, + STORED_METHOD_ATTR=13; + +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestPacket.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestPacket.java new file mode 100644 index 00000000000..95e4cf27c8f --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestPacket.java @@ -0,0 +1,85 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.View; + +/** + * + * + * + */ +public class Ajp13RequestPacket +{ + public static boolean isEmpty(Buffer _buffer) + { + return _buffer.length()==0; + } + + public static int getInt(Buffer _buffer) + { + return ((_buffer.get()&0xFF)<<8)|(_buffer.get()&0xFF); + } + + public static Buffer getString(Buffer _buffer, View tok) + { + int len=((_buffer.peek()&0xFF)<<8)|(_buffer.peek(_buffer.getIndex()+1)&0xFF); + if (len==0xffff) + { + _buffer.skip(2); + return null; + } + int start=_buffer.getIndex(); + tok.update(start+2,start+len+2); + _buffer.skip(len+3); + return tok; + } + + public static byte getByte(Buffer _buffer) + { + return _buffer.get(); + } + + public static boolean getBool(Buffer _buffer) + { + return _buffer.get()>0; + } + + public static Buffer getMethod(Buffer _buffer) + { + return Ajp13PacketMethods.CACHE.get(_buffer.get()); + } + + public static Buffer getHeaderName(Buffer _buffer, View tok) + { + int len=((_buffer.peek()&0xFF)<<8)|(_buffer.peek(_buffer.getIndex()+1)&0xFF); + if ((0xFF00&len)==0xA000) + { + _buffer.skip(1); + return Ajp13RequestHeaders.CACHE.get(_buffer.get()); + } + int start=_buffer.getIndex(); + tok.update(start+2,start+len+2); + _buffer.skip(len+3); + return tok; + + } + + public static Buffer get(Buffer buffer, int length) + { + return buffer.get(length); + } + +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13ResponseHeaders.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13ResponseHeaders.java new file mode 100644 index 00000000000..59030b40ba7 --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13ResponseHeaders.java @@ -0,0 +1,43 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/** + * + */ +public class Ajp13ResponseHeaders extends BufferCache +{ + + public final static int MAGIC=0xab00; + + public final static String CONTENT_TYPE="Content-Type", CONTENT_LANGUAGE="Content-Language", CONTENT_LENGTH="Content-Length", DATE="Date", + LAST_MODIFIED="Last-Modified", LOCATION="Location", SET_COOKIE="Set-Cookie", SET_COOKIE2="Set-Cookie2", SERVLET_ENGINE="Servlet-Engine", + STATUS="Status", WWW_AUTHENTICATE="WWW-Authenticate"; + + public final static int CONTENT_TYPE_ORDINAL=1, CONTENT_LANGUAGE_ORDINAL=2, CONTENT_LENGTH_ORDINAL=3, DATE_ORDINAL=4, LAST_MODIFIED_ORDINAL=5, + LOCATION_ORDINAL=6, SET_COOKIE_ORDINAL=7, SET_COOKIE2_ORDINAL=8, SERVLET_ENGINE_ORDINAL=9, STATUS_ORDINAL=10, WWW_AUTHENTICATE_ORDINAL=11; + + public final static BufferCache CACHE=new BufferCache(); + + public final static Buffer CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL), CONTENT_LANGUAGE_BUFFER=CACHE.add(CONTENT_LANGUAGE, + CONTENT_LANGUAGE_ORDINAL), CONTENT_LENGTH_BUFFER=CACHE.add(CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL), DATE_BUFFER=CACHE.add(DATE,DATE_ORDINAL), + LAST_MODIFIED_BUFFER=CACHE.add(LAST_MODIFIED,LAST_MODIFIED_ORDINAL), LOCATION_BUFFER=CACHE.add(LOCATION,LOCATION_ORDINAL), SET_COOKIE_BUFFER=CACHE + .add(SET_COOKIE,SET_COOKIE_ORDINAL), SET_COOKIE2_BUFFER=CACHE.add(SET_COOKIE2,SET_COOKIE2_ORDINAL), SERVLET_ENGINE_BUFFER=CACHE.add( + SERVLET_ENGINE,SERVLET_ENGINE_ORDINAL), STATUS_BUFFER=CACHE.add(STATUS,STATUS_ORDINAL), WWW_AUTHENTICATE_BUFFER=CACHE.add(WWW_AUTHENTICATE, + WWW_AUTHENTICATE_ORDINAL); + +} diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13SocketConnector.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13SocketConnector.java new file mode 100644 index 00000000000..eee96e5a49d --- /dev/null +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13SocketConnector.java @@ -0,0 +1,115 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import java.io.IOException; + +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.util.log.Log; + +/** + * + * + * + */ +public class Ajp13SocketConnector extends SocketConnector +{ + static String __secretWord = null; + static boolean __allowShutdown = false; + public Ajp13SocketConnector() + { + super.setHeaderBufferSize(Ajp13Packet.MAX_DATA_SIZE); + super.setRequestBufferSize(Ajp13Packet.MAX_DATA_SIZE); + super.setResponseBufferSize(Ajp13Packet.MAX_DATA_SIZE); + // IN AJP protocol the socket stay open, so + // by default the time out is set to 900 seconds + super.setMaxIdleTime(900000); + } + + protected void doStart() throws Exception + { + super.doStart(); + Log.info("AJP13 is not a secure protocol. Please protect port {}",Integer.toString(getLocalPort())); + } + + + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.server.bio.SocketConnector#customize(org.eclipse.io.EndPoint, org.eclipse.jetty.server.Request) + */ + public void customize(EndPoint endpoint, Request request) throws IOException + { + super.customize(endpoint,request); + if (request.isSecure()) + request.setScheme(HttpSchemes.HTTPS); + } + + /* ------------------------------------------------------------ */ + protected HttpConnection newHttpConnection(EndPoint endpoint) + { + return new Ajp13Connection(this,endpoint,getServer()); + } + + /* ------------------------------------------------------------ */ + // Secured on a packet by packet bases not by connection + public boolean isConfidential(Request request) + { + return ((Ajp13Request) request).isSslSecure(); + } + + /* ------------------------------------------------------------ */ + // Secured on a packet by packet bases not by connection + public boolean isIntegral(Request request) + { + return ((Ajp13Request) request).isSslSecure(); + } + + /* ------------------------------------------------------------ */ + public void setHeaderBufferSize(int headerBufferSize) + { + Log.debug(Log.IGNORED); + } + + /* ------------------------------------------------------------ */ + public void setRequestBufferSize(int requestBufferSize) + { + Log.debug(Log.IGNORED); + } + + /* ------------------------------------------------------------ */ + public void setResponseBufferSize(int responseBufferSize) + { + Log.debug(Log.IGNORED); + } + + /* ------------------------------------------------------------ */ + public void setAllowShutdown(boolean allowShutdown) + { + Log.warn("AJP13: Shutdown Request is: " + allowShutdown); + __allowShutdown = allowShutdown; + } + + /* ------------------------------------------------------------ */ + public void setSecretWord(String secretWord) + { + Log.warn("AJP13: Shutdown Request secret word is : " + secretWord); + __secretWord = secretWord; + } + +} diff --git a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java new file mode 100644 index 00000000000..0a27086dc57 --- /dev/null +++ b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java @@ -0,0 +1,313 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.TypeUtil; + +public class Ajp13ConnectionTest extends TestCase +{ + private Server _server; + private Ajp13SocketConnector _connector; + private Socket _client; + + protected void setUp() throws Exception + { + _server=new Server(); + _connector=new Ajp13SocketConnector(); + + _connector.setPort(0); + _connector.setMaxIdleTime(100); + _server.setConnectors(new Connector[] { _connector }); + _server.setHandler(new Handler()); + _server.start(); + + _client=new Socket("localhost",_connector.getLocalPort()); + + } + + protected void tearDown() throws Exception + { + _client.close(); + _connector.close(); + _server.stop(); + } + + public void testPacket1() throws Exception + { + OutputStream os=_client.getOutputStream(); + + String packet="123401070202000f77696474683d20485454502f312e300000122f636f6e74726f6c2f70726f647563742f2200000e3230382e32372e3230332e31323800ffff000c7777772e756c74612e636f6d000050000005a006000a6b6565702d616c69766500a00b000c7777772e756c74612e636f6d00a00e002b4d6f7a696c6c612f342e302028636f6d70617469626c653b20426f726465724d616e6167657220332e302900a0010043696d6167652f6769662c20696d6167652f782d786269746d61702c20696d6167652f6a7065672c20696d6167652f706a7065672c20696d6167652f706d672c202a2f2a00a008000130000600067570726f64310008000a4145533235362d53484100ff"; + os.write(TypeUtil.fromHexString(packet)); + os.flush(); + + readResponse(_client); + assertTrue(true); + } + + public void testPacket2() throws Exception + { + OutputStream os=_client.getOutputStream(); + + String packet="1234020102020008485454502f312e3100000f2f6363632d7777777777772f61616100000c38382e3838382e38382e383830ffff00116363632e6363636363636363632e636f6d0001bb010009a00b00116363632e6363636363636363632e636f6d00a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a004000e656e2d75732c656e3b713d302e3500a003000c677a69702c6465666c61746500a002001e49534f2d383835392d312c7574662d383b713d302e372c2a3b713d302e3700000a4b6565702d416c69766500000333303000a006000a6b6565702d616c69766500000c4d61782d466f7277617264730000023130000800124448452d5253412d4145533235362d5348410009004039324643303544413043444141443232303137413743443141453939353132413330443938363838423843433041454643364231363035323543433232353341000b0100ff"; + os.write(TypeUtil.fromHexString(packet)); + os.flush(); + + readResponse(_client); + assertTrue(true); + } + + public void testPacket3() throws Exception + { + OutputStream os=_client.getOutputStream(); + + String packet="1234028f02020008485454502f312e3100000d2f666f726d746573742e6a737000000d3139322e3136382e342e31383000ffff00107777772e777265636b6167652e6f726700005000000aa0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a00200075554462d382c2a00a003000c677a69702c6465666c61746500a004000e656e2d67622c656e3b713d302e3500a006000a6b6565702d616c69766500a00900f95048505345535349443d37626361383232616638333466316465373663633630336366636435313938633b20667041757468436f6f6b69653d433035383430394537393344364245434633324230353234344242303039343230383344443645443533304230454637464137414544413745453231313538333745363033454435364332364446353531383635333335423433374531423637414641343533364345304546323342333642323133374243423932333943363631433131443330393842333938414546334546334146454344423746353842443b204a53455353494f4e49443d7365366331623864663432762e6a657474793300a00b00107777772e777265636b6167652e6f726700000a6b6565702d616c69766500000333303000a00e00654d6f7a696c6c612f352e3020285831313b20553b204c696e7578207838365f36343b20656e2d55533b2072763a312e382e302e3929204765636b6f2f3230303631323035202844656269616e2d312e382e302e392d3129204570697068616e792f322e313400a008000130000600066a657474793300ff"; + os.write(TypeUtil.fromHexString(packet)); + os.flush(); + readResponse(_client); + + assertTrue(true); + } + + public void testSSLPacketWithIntegerKeySize() throws Exception + { + OutputStream os=_client.getOutputStream(); + + String packet="1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a004000e656e2d75732c656e3b713d302e3500a003000c677a69702c6465666c61746500a002001e49534f2d383835392d312c7574662d383b713d302e372c2a3b713d302e3700000a4b6565702d416c69766500000333303000a006000a6b6565702d616c69766500a00d001a68747470733a2f2f776562746964652d746573742f746573742f00a00900174a53455353494f4e49443d69326c6e307539773573387300000d43616368652d436f6e74726f6c0000096d61782d6167653d3000000c4d61782d466f7277617264730000023130000800124448452d5253412d4145533235362d5348410009004032413037364245323330433238393130383941414132303631344139384441443131314230323132343030374130363642454531363742303941464337383942000b0100ff"; + os.write(TypeUtil.fromHexString(packet)); + os.flush(); + readResponse(_client); + + assertTrue(true); + } + + public void testSSLPacketWithStringKeySize() throws Exception + { + OutputStream os=_client.getOutputStream(); + + String packet="1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a004000e656e2d75732c656e3b713d302e3500a003000c677a69702c6465666c61746500a002001e49534f2d383835392d312c7574662d383b713d302e372c2a3b713d302e3700000a4b6565702d416c69766500000333303000a006000a6b6565702d616c69766500a00d001a68747470733a2f2f776562746964652d746573742f746573742f00a00900174a53455353494f4e49443d69326c6e307539773573387300000d43616368652d436f6e74726f6c0000096d61782d6167653d3000000c4d61782d466f7277617264730000023130000800124448452d5253412d4145533235362d5348410009004032413037364245323330433238393130383941414132303631344139384441443131314230323132343030374130363642454531363742303941464337383942000b000332353600ff"; + os.write(TypeUtil.fromHexString(packet)); + os.flush(); + readResponse(_client); + + assertTrue(true); + } + + public void testPacketWithBody() throws Exception + { + OutputStream os=_client.getOutputStream(); + + os.write(TypeUtil.fromHexString(getTestHeader())); + os.write(TypeUtil.fromHexString(getTestShortBody())); + os.write(TypeUtil.fromHexString(getTestTinyBody())); + + readResponse(_client); + + assertTrue(true); + } + + public void testPacketWithChunkedBody() throws Exception + { + OutputStream os=_client.getOutputStream(); + + String packet="123400ff02040008485454502f312e3100000f2f746573742f64756d702f696e666f0000093132372e302e302e3100ffff00096c6f63616c686f7374000050000007a00e000d4a6176612f312e352e305f313100a00b00096c6f63616c686f737400a0010034746578742f68746d6c2c20696d6167652f6769662c20696d6167652f6a7065672c202a3b20713d2e322c202a2f2a3b20713d2e3200a006000a6b6565702d616c69766500a00700216170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640000115472616e736665722d456e636f64696e670000076368756e6b656400000c4d61782d466f727761726473000002313000ff"; + + os.write(TypeUtil.fromHexString(packet)); + os.flush(); + + os.write(TypeUtil.fromHexString("1234007e007c7468656e616d653d746865253230717569636b25323062726f776e253230666f782532306a756d70732532306f766572253230746f2532307468652532306c617a79253230646f67253230544845253230515549434b25323042524f574e253230464f582532304a554d50532532304f564552253230544f25323054")); + os.flush(); + + os.write(TypeUtil.fromHexString("12340042004048452532304c415a59253230444f472532302676616c75656f66323d6162636465666768696a6b6c6d6e6f707172737475767778797a31323334353637383930")); + os.flush(); + + os.write(TypeUtil.fromHexString("123400020000")); + os.flush(); + + readResponse(_client); + assertTrue(true); + } + + private String getTestHeader() + { + StringBuffer header=new StringBuffer(""); + header.append("1234026902040008485454502f31"); + header.append("2e310000162f61646d696e2f496d6167"); + header.append("6555706c6f61642e68746d00000a3130"); + header.append("2e34382e31302e3100ffff000a31302e"); + header.append("34382e31302e3200005000000da00b00"); + header.append("0a31302e34382e31302e3200a00e005a"); + header.append("4d6f7a696c6c612f352e30202857696e"); + header.append("646f77733b20553b2057696e646f7773"); + header.append("204e5420352e313b20656e2d55533b20"); + header.append("72763a312e382e312e3129204765636b"); + header.append("6f2f3230303631323034204669726566"); + header.append("6f782f322e302e302e3100a001006374"); + header.append("6578742f786d6c2c6170706c69636174"); + header.append("696f6e2f786d6c2c6170706c69636174"); + header.append("696f6e2f7868746d6c2b786d6c2c7465"); + header.append("78742f68746d6c3b713d302e392c7465"); + header.append("78742f706c61696e3b713d302e382c69"); + header.append("6d6167652f706e672c2a2f2a3b713d30"); + header.append("2e3500a004000e656e2d75732c656e3b"); + header.append("713d302e3500a003000c677a69702c64"); + header.append("65666c61746500a002001e49534f2d38"); + header.append("3835392d312c7574662d383b713d302e"); + header.append("372c2a3b713d302e3700000a4b656570"); + header.append("2d416c69766500000333303000a00600"); + header.append("0a6b6565702d616c69766500a00d003f"); + header.append("687474703a2f2f31302e34382e31302e"); + header.append("322f61646d696e2f496d61676555706c"); + header.append("6f61642e68746d3f6964303d4974656d"); + header.append("266964313d32266964323d696d673200"); + header.append("a00900174a53455353494f4e49443d75"); + header.append("383977733070696168746d00a0070046"); + header.append("6d756c7469706172742f666f726d2d64"); + header.append("6174613b20626f756e646172793d2d2d"); + header.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d"); + header.append("2d2d2d2d2d2d2d2d2d39343338333235"); + header.append("34323630383700a00800033735390000"); + header.append("0c4d61782d466f727761726473000002"); + header.append("3130000500176964303d4974656d2669"); + header.append("64313d32266964323d696d673200ff"); + + return header.toString(); + + } + + private String getTestShortBody() + { + StringBuffer body=new StringBuffer(""); + + body.append("123402f702f52d2d2d2d2d2d2d2d"); + body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d"); + body.append("2d2d2d2d2d3934333833323534323630"); + body.append("38370d0a436f6e74656e742d44697370"); + body.append("6f736974696f6e3a20666f726d2d6461"); + body.append("74613b206e616d653d227265636f7264"); + body.append("4964220d0a0d0a320d0a2d2d2d2d2d2d"); + body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d"); + body.append("2d2d2d2d2d2d2d393433383332353432"); + body.append("363038370d0a436f6e74656e742d4469"); + body.append("73706f736974696f6e3a20666f726d2d"); + body.append("646174613b206e616d653d226e616d65"); + body.append("220d0a0d0a4974656d0d0a2d2d2d2d2d"); + body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d"); + body.append("2d2d2d2d2d2d2d2d3934333833323534"); + body.append("32363038370d0a436f6e74656e742d44"); + body.append("6973706f736974696f6e3a20666f726d"); + body.append("2d646174613b206e616d653d22746e49"); + body.append("6d674964220d0a0d0a696d67320d0a2d"); + body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d"); + body.append("2d2d2d2d2d2d2d2d2d2d2d2d39343338"); + body.append("3332353432363038370d0a436f6e7465"); + body.append("6e742d446973706f736974696f6e3a20"); + body.append("666f726d2d646174613b206e616d653d"); + body.append("227468756d624e61696c496d61676546"); + body.append("696c65223b2066696c656e616d653d22"); + body.append("6161612e747874220d0a436f6e74656e"); + body.append("742d547970653a20746578742f706c61"); + body.append("696e0d0a0d0a61616161616161616161"); + body.append("61616161616161616161616161616161"); + body.append("61616161616161616161616161616161"); + body.append("61616161616161616161616161616161"); + body.append("0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d"); + body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d39"); + body.append("3433383332353432363038370d0a436f"); + body.append("6e74656e742d446973706f736974696f"); + body.append("6e3a20666f726d2d646174613b206e61"); + body.append("6d653d226c61726765496d6167654669"); + body.append("6c65223b2066696c656e616d653d2261"); + body.append("61612e747874220d0a436f6e74656e74"); + body.append("2d547970653a20746578742f706c6169"); + body.append("6e0d0a0d0a6161616161616161616161"); + body.append("61616161616161616161616161616161"); + body.append("61616161616161616161616161616161"); + body.append("6161616161616161616161616161610d"); + body.append("0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d"); + body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d3934"); + body.append("33383332353432363038372d2d"); + + return body.toString(); + + } + + private String getTestTinyBody() + { + StringBuffer body = new StringBuffer(""); + + body.append("123400042d2d0d0a"); + + return body.toString(); + + } + + // TODO: char array instead of string? + private String readResponse(Socket _client) throws IOException + { + BufferedReader br=null; + + try + { + br=new BufferedReader(new InputStreamReader(_client.getInputStream())); + + StringBuffer sb=new StringBuffer(); + String line; + while ((line=br.readLine()) != null) + { + sb.append(line); + sb.append('\n'); + } + + return sb.toString(); + } + finally + { + if (br != null) + { + br.close(); + } + } + } + + public static class Handler extends AbstractHandler + { + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Request base_request=(request instanceof Request) ? (Request) request : HttpConnection.getCurrentConnection().getRequest(); + base_request.setHandled(true); + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("text/plain"); + response.getWriter().println("success"); + } + + } + +} diff --git a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java new file mode 100644 index 00000000000..facba13082d --- /dev/null +++ b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java @@ -0,0 +1,620 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.ajp; + +import java.io.IOException; + +import junit.framework.TestCase; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.ByteArrayEndPoint; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.SimpleBuffers; +import org.eclipse.jetty.util.TypeUtil; + +public class TestAjpParser extends TestCase +{ + + public void testPacket1() throws Exception + { + String packet = "123401070202000f77696474683d20485454502f312e300000122f636f6e74726f6c2f70726f647563742f2200000e3230382e32372e3230332e31323800ffff000c7777772e756c74612e636f6d000050000005a006000a6b6565702d616c69766500a00b000c7777772e756c74612e636f6d00a00e002b4d6f7a696c6c612f342e302028636f6d70617469626c653b20426f726465724d616e6167657220332e302900a0010043696d6167652f6769662c20696d6167652f782d786269746d61702c20696d6167652f6a7065672c20696d6167652f706a7065672c20696d6167652f706d672c202a2f2a00a008000130000600067570726f64310008000a4145533235362d53484100ff"; + byte[] src = TypeUtil.fromHexString(packet); + + ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + EndPoint endp = new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE); + + Ajp13Parser parser = new Ajp13Parser(buffers,endp); + parser.setEventHandler(new EH()); + parser.setGenerator(new Ajp13Generator(buffers,endp,0,0)); + + parser.parseAvailable(); + + assertTrue(true); + } + + public void testPacket2() throws Exception + { + String packet="1234020102020008485454502f312e3100000f2f6363632d7777777777772f61616100000c38382e3838382e38382e383830ffff00116363632e6363636363636363632e636f6d0001bb010009a00b00116363632e6363636363636363632e636f6d00a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a004000e656e2d75732c656e3b713d302e3500a003000c677a69702c6465666c61746500a002001e49534f2d383835392d312c7574662d383b713d302e372c2a3b713d302e3700000a4b6565702d416c69766500000333303000a006000a6b6565702d616c69766500000c4d61782d466f7277617264730000023130000800124448452d5253412d4145533235362d5348410009004039324643303544413043444141443232303137413743443141453939353132413330443938363838423843433041454643364231363035323543433232353341000b0100ff"; + byte[] src=TypeUtil.fromHexString(packet); + ByteArrayBuffer buffer=new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[] + { buffer }); + EndPoint endp=new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE); + Ajp13Parser parser = new Ajp13Parser(buffers,endp); + parser.setEventHandler(new EH()); + parser.setGenerator(new Ajp13Generator(buffers,endp,0,0)); + parser.parse(); + assertTrue(true); + } + + public void testPacket3() throws Exception + { + String packet="1234028f02020008485454502f312e3100000d2f666f726d746573742e6a737000000d3139322e3136382e342e31383000ffff00107777772e777265636b6167652e6f726700005000000aa0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a00200075554462d382c2a00a003000c677a69702c6465666c61746500a004000e656e2d67622c656e3b713d302e3500a006000a6b6565702d616c69766500a00900f95048505345535349443d37626361383232616638333466316465373663633630336366636435313938633b20667041757468436f6f6b69653d433035383430394537393344364245434633324230353234344242303039343230383344443645443533304230454637464137414544413745453231313538333745363033454435364332364446353531383635333335423433374531423637414641343533364345304546323342333642323133374243423932333943363631433131443330393842333938414546334546334146454344423746353842443b204a53455353494f4e49443d7365366331623864663432762e6a657474793300a00b00107777772e777265636b6167652e6f726700000a6b6565702d616c69766500000333303000a00e00654d6f7a696c6c612f352e3020285831313b20553b204c696e7578207838365f36343b20656e2d55533b2072763a312e382e302e3929204765636b6f2f3230303631323035202844656269616e2d312e382e302e392d3129204570697068616e792f322e313400a008000130000600066a657474793300ff"; + byte[] src=TypeUtil.fromHexString(packet); + ByteArrayBuffer buffer=new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[] + { buffer }); + EndPoint endp=new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE); + Ajp13Parser parser = new Ajp13Parser(buffers,endp); + parser.setEventHandler(new EH()); + parser.setGenerator(new Ajp13Generator(buffers,endp,0,0)); + parser.parse(); + assertTrue(true); + } + + + public void testSSLPacketWithIntegerKeySize() throws Exception + { + String packet = "1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a004000e656e2d75732c656e3b713d302e3500a003000c677a69702c6465666c61746500a002001e49534f2d383835392d312c7574662d383b713d302e372c2a3b713d302e3700000a4b6565702d416c69766500000333303000a006000a6b6565702d616c69766500a00d001a68747470733a2f2f776562746964652d746573742f746573742f00a00900174a53455353494f4e49443d69326c6e307539773573387300000d43616368652d436f6e74726f6c0000096d61782d6167653d3000000c4d61782d466f7277617264730000023130000800124448452d5253412d4145533235362d5348410009004032413037364245323330433238393130383941414132303631344139384441443131314230323132343030374130363642454531363742303941464337383942000b0100ff"; + byte[] src = TypeUtil.fromHexString(packet); + + ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + EndPoint endp = new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE); + + Ajp13Parser parser = new Ajp13Parser(buffers,endp); + parser.setEventHandler(new EH()); + parser.setGenerator(new Ajp13Generator(buffers,endp,0,0)); + + parser.parseAvailable(); + + assertTrue(true); + } + + public void testSSLPacketWithStringKeySize() throws Exception + { + String packet = "1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a004000e656e2d75732c656e3b713d302e3500a003000c677a69702c6465666c61746500a002001e49534f2d383835392d312c7574662d383b713d302e372c2a3b713d302e3700000a4b6565702d416c69766500000333303000a006000a6b6565702d616c69766500a00d001a68747470733a2f2f776562746964652d746573742f746573742f00a00900174a53455353494f4e49443d69326c6e307539773573387300000d43616368652d436f6e74726f6c0000096d61782d6167653d3000000c4d61782d466f7277617264730000023130000800124448452d5253412d4145533235362d5348410009004032413037364245323330433238393130383941414132303631344139384441443131314230323132343030374130363642454531363742303941464337383942000b000332353600ff"; + byte[] src = TypeUtil.fromHexString(packet); + + ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + EndPoint endp = new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE); + + Ajp13Parser parser = new Ajp13Parser(buffers,endp); + parser.setEventHandler(new EH()); + parser.setGenerator(new Ajp13Generator(buffers,endp,0,0)); + + parser.parseAvailable(); + + assertTrue(true); + } + + public void testSSLPacketFragment() throws Exception + { + String packet = "1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a004000e656e2d75732c656e3b713d302e3500a003000c677a69702c6465666c61746500a002001e49534f2d383835392d312c7574662d383b713d302e372c2a3b713d302e3700000a4b6565702d416c69766500000333303000a006000a6b6565702d616c69766500a00d001a68747470733a2f2f776562746964652d746573742f746573742f00a00900174a53455353494f4e49443d69326c6e307539773573387300000d43616368652d436f6e74726f6c0000096d61782d6167653d3000000c4d61782d466f7277617264730000023130000800124448452d5253412d4145533235362d5348410009004032413037364245323330433238393130383941414132303631344139384441443131314230323132343030374130363642454531363742303941464337383942000b0100ff"; + byte[] src = TypeUtil.fromHexString(packet); + + for (int f=1;f0); + assertEquals(1,parser.getState()); + assertEquals(2,count[0]); + + endp.setIn(new ByteArrayBuffer(TypeUtil.fromHexString("12340042004048452532304c415a59253230444f472532302676616c75656f66323d6162636465666768696a6b6c6d6e6f707172737475767778797a31323334353637383930"))); + + while (parser.parseNext()>0); + assertEquals(1,parser.getState()); + assertEquals(3,count[0]); + + endp.setIn(new ByteArrayBuffer(TypeUtil.fromHexString("123400020000"))); + + while (parser.getState()!=0 && parser.parseNext()>0); + assertEquals(0,parser.getState()); + assertEquals(3,count[0]); + + assertTrue(true); + } + + + + + public void testPacketFragment() throws Exception + { + String packet = "123401070202000f77696474683d20485454502f312e300000122f636f6e74726f6c2f70726f647563742f2200000e3230382e32372e3230332e31323800ffff000c7777772e756c74612e636f6d000050000005a006000a6b6565702d616c69766500a00b000c7777772e756c74612e636f6d00a00e002b4d6f7a696c6c612f342e302028636f6d70617469626c653b20426f726465724d616e6167657220332e302900a0010043696d6167652f6769662c20696d6167652f782d786269746d61702c20696d6167652f6a7065672c20696d6167652f706a7065672c20696d6167652f706d672c202a2f2a00a008000130000600067570726f64310008000a4145533235362d53484100ff"; + byte[] src = TypeUtil.fromHexString(packet); + + for (int f=1;f + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-annotations + Jetty :: Servlet Annotations + Annotation support for deploying servlets in jetty. + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + J2SE-1.5 + http://jetty.eclipse.org + !org.eclipse.jetty.annotations.*,* + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + + + junit + junit + test + + + org.mortbay.jetty + servlet-annotation-spec + 3.0.pre0 + + + org.eclipse.jetty + jetty-jndi + ${project.version} + test + + + org.eclipse.jetty + jetty-plus + ${project.version} + + + org.eclipse.jetty + jetty-webapp + ${project.version} + + + org.apache.geronimo.specs + geronimo-annotation_1.0_spec + 1.0 + + + asm + asm-commons + 3.1 + + + diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationFinder.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationFinder.java new file mode 100644 index 00000000000..34b6d456aa6 --- /dev/null +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationFinder.java @@ -0,0 +1,729 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.regex.Pattern; + +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.JarScanner; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.EmptyVisitor; + + +/** + * AnnotationFinder + * + * + * Scans class sources using asm to find annotations. + * + * + */ +public class AnnotationFinder +{ + private Map parsedClasses = new HashMap(); + + + public static String normalize (String name) + { + if (name==null) + return null; + + if (name.startsWith("L") && name.endsWith(";")) + name = name.substring(1, name.length()-1); + + if (name.endsWith(".class")) + name = name.substring(0, name.length()-".class".length()); + + name = name.replace('$', '.'); + + return name.replace('/', '.'); + } + + public static Class convertType (org.objectweb.asm.Type t) + throws Exception + { + if (t == null) + return (Class)null; + + switch (t.getSort()) + { + case Type.BOOLEAN: + { + return Boolean.TYPE; + } + case Type.ARRAY: + { + Class clazz = convertType(t.getElementType()); + return Array.newInstance(clazz, 0).getClass(); + } + case Type.BYTE: + { + return Byte.TYPE; + } + case Type.CHAR: + { + return Character.TYPE; + } + case Type.DOUBLE: + { + return Double.TYPE; + } + case Type.FLOAT: + { + return Float.TYPE; + } + case Type.INT: + { + return Integer.TYPE; + } + case Type.LONG: + { + return Long.TYPE; + } + case Type.OBJECT: + { + return (Loader.loadClass(null, t.getClassName())); + } + case Type.SHORT: + { + return Short.TYPE; + } + case Type.VOID: + { + return null; + } + default: + return null; + } + + } + + public static Class[] convertTypes (Type[] types) + throws Exception + { + if (types==null) + return new Class[0]; + + Class[] classArray = new Class[types.length]; + + for (int i=0; i> annotations = new HashMap>(); + + + public AnnotationVisitor addAnnotation (final String name) + { + final HashMap annotationValues = new HashMap(); + this.annotations.put(normalize(name), annotationValues); + return new AnnotationVisitor() + { + public void visit(String name, Object value) + { + annotationValues.put(name, value); + } + + public AnnotationVisitor visitAnnotation(String name, String desc) + { + return null; //ignore nested annotations + } + + public AnnotationVisitor visitArray(String arg0) + { + return null;//ignore array valued annotations + } + + public void visitEnd() + { + } + + public void visitEnum(String name, String desc, String value) + { + } + }; + } + + public Map> getAnnotations () + { + return annotations; + } + + + public String toString() + { + StringBuffer strbuff = new StringBuffer(); + + for (Map.Entry> e: annotations.entrySet()) + { + strbuff.append(e.getKey()+"\n"); + for (Map.Entry v: e.getValue().entrySet()) + { + strbuff.append("\t"+v.getKey()+"="+v.getValue()+", "); + } + } + return strbuff.toString(); + } + } + + + /** + * ParsedClass + * + * A class that contains annotations. + */ + public static class ParsedClass extends AnnotatedStructure + { + String className; + String superClassName; + Class clazz; + List methods = new ArrayList(); + List fields = new ArrayList(); + + + public ParsedClass (String className, String superClassName) + { + this.className = normalize(className); + this.superClassName = normalize(superClassName); + } + + public String getClassName() + { + return this.className; + } + + public String getSuperClassName () + { + return this.superClassName; + } + + public Class toClass () + throws ClassNotFoundException + { + if (clazz==null) + clazz = Loader.loadClass(null, className); + return clazz; + } + + public List getMethods () + { + return methods; + } + + public ParsedMethod getMethod(String name, String paramString) + { + Iterator itor = methods.iterator(); + ParsedMethod method = null; + while (itor.hasNext() && method==null) + { + ParsedMethod m = itor.next(); + if (m.matches(name, paramString)) + method = m; + } + + return method; + } + + public void addMethod (ParsedMethod m) + { + if (getMethod(m.methodName, m.paramString)!= null) + return; + methods.add(m); + } + + public List getFields() + { + return fields; + } + + public ParsedField getField(String name) + { + Iterator itor = fields.iterator(); + ParsedField field = null; + while (itor.hasNext() && field==null) + { + ParsedField f = itor.next(); + if (f.matches(name)) + field=f; + } + return field; + } + + public void addField (ParsedField f) + { + if (getField(f.fieldName) != null) + return; + fields.add(f); + } + + public String toString () + { + StringBuffer strbuff = new StringBuffer(); + strbuff.append(this.className+"\n"); + strbuff.append("Class annotations\n"+super.toString()); + strbuff.append("\n"); + strbuff.append("Method annotations\n"); + for (ParsedMethod p:methods) + strbuff.append(p+"\n"); + strbuff.append("\n"); + strbuff.append("Field annotations\n"); + for (ParsedField f:fields) + strbuff.append(f+"\n"); + strbuff.append("\n"); + return strbuff.toString(); + } + } + + + /** + * ParsedMethod + * + * A class method that can contain annotations. + */ + public static class ParsedMethod extends AnnotatedStructure + { + ParsedClass pclass; + String methodName; + String paramString; + Method method; + + + public ParsedMethod(ParsedClass pclass, String name, String paramString) + { + this.pclass=pclass; + this.methodName=name; + this.paramString=paramString; + } + + + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) + { + this.pclass.addMethod(this); + return addAnnotation(desc); + } + + public Method toMethod () + throws Exception + { + if (method == null) + { + Type[] types = null; + if (paramString!=null) + types = Type.getArgumentTypes(paramString); + + Class[] args = convertTypes(types); + method = pclass.toClass().getDeclaredMethod(methodName, args); + } + + return method; + } + + public boolean matches (String name, String paramString) + { + if (!methodName.equals(name)) + return false; + + if (this.paramString!=null && this.paramString.equals(paramString)) + return true; + + return (this.paramString == paramString); + } + + public String toString () + { + return pclass.getClassName()+"."+methodName+"\n\t"+super.toString(); + } + } + + /** + * ParsedField + * + * A class field that can contain annotations. Also implements the + * asm visitor for Annotations. + */ + public static class ParsedField extends AnnotatedStructure + { + ParsedClass pclass; + String fieldName; + Field field; + + public ParsedField (ParsedClass pclass, String name) + { + this.pclass=pclass; + this.fieldName=name; + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) + { + this.pclass.addField(this); + return addAnnotation(desc); + } + + public Field toField () + throws Exception + { + if (field==null) + { + field=this.pclass.toClass().getDeclaredField(fieldName); + } + return field; + } + + + public boolean matches (String name) + { + if (fieldName.equals(name)) + return true; + + return false; + } + + public String toString () + { + return pclass.getClassName()+"."+fieldName+"\n\t"+super.toString(); + } + } + + + + /** + * MyClassVisitor + * + * ASM visitor for a class. + */ + public class MyClassVisitor extends EmptyVisitor + { + ParsedClass pclass; + + + public void visit (int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) + { + pclass = new ParsedClass(name, superName); + } + + public AnnotationVisitor visitAnnotation (String desc, boolean visible) + { + if (!parsedClasses.containsKey(pclass.getClassName())) + parsedClasses.put(pclass.getClassName(), pclass); + + return pclass.addAnnotation(desc); + } + + public MethodVisitor visitMethod (int access, + String name, + String desc, + String signature, + String[] exceptions) + { + if (!parsedClasses.values().contains(pclass)) + parsedClasses.put(pclass.getClassName(),pclass); + + ParsedMethod method = pclass.getMethod(name, desc); + if (method==null) + method = new ParsedMethod(pclass, name, desc); + return method; + } + + public FieldVisitor visitField (int access, + String name, + String desc, + String signature, + Object value) + { + if (!parsedClasses.values().contains(pclass)) + parsedClasses.put(pclass.getClassName(),pclass); + + ParsedField field = pclass.getField(name); + if (field==null) + field = new ParsedField(pclass, name); + return field; + } + } + + + + + + + public void find (String className, ClassNameResolver resolver) + throws Exception + { + if (className == null) + return; + + if (!resolver.isExcluded(className)) + { + if ((parsedClasses.get(className) == null) || (resolver.shouldOverride(className))) + { + parsedClasses.remove(className); + className = className.replace('.', '/')+".class"; + URL resource = Loader.getResource(this.getClass(), className, false); + if (resource!= null) + scanClass(resource.openStream()); + } + } + } + + public void find (String[] classNames, ClassNameResolver resolver) + throws Exception + { + if (classNames == null) + return; + + find(Arrays.asList(classNames), resolver); + } + + public void find (List classNames, ClassNameResolver resolver) + throws Exception + { + for (String s:classNames) + { + if (!resolver.isExcluded(s)) + { + if ((parsedClasses.get(s) == null) || (resolver.shouldOverride(s))) + { + parsedClasses.remove(s); + s = s.replace('.', '/')+".class"; + URL resource = Loader.getResource(this.getClass(), s, false); + if (resource!= null) + scanClass(resource.openStream()); + } + } + } + } + + public void find (Resource dir, ClassNameResolver resolver) + throws Exception + { + if (!dir.isDirectory() || !dir.exists()) + return; + + + String[] files=dir.list(); + for (int f=0;files!=null && f> getClassesForAnnotation(Class annotationClass) + throws Exception + { + List> classes = new ArrayList>(); + for (Map.Entry e: parsedClasses.entrySet()) + { + ParsedClass pc = e.getValue(); + Map> annotations = pc.getAnnotations(); + for (String key:annotations.keySet()) + { + if (key.equals(annotationClass.getName())) + { + classes.add(pc.toClass()); + } + } + } + return classes; + + } + + + + public List getMethodsForAnnotation (Class annotationClass) + throws Exception + { + + List methods = new ArrayList(); + + for (Map.Entry e: parsedClasses.entrySet()) + { + ParsedClass pc = e.getValue(); + + List pmethods = pc.getMethods(); + for (ParsedMethod p:pmethods) + { + for (String key:p.getAnnotations().keySet()) + { + if (key.equals(annotationClass.getName())) + { + methods.add(p.toMethod()); + } + } + } + } + return methods; + + } + + + public List getFieldsForAnnotation (Class annotation) + throws Exception + { + + List fields = new ArrayList(); + for (Map.Entry e: parsedClasses.entrySet()) + { + ParsedClass pc = e.getValue(); + + List pfields = pc.getFields(); + for (ParsedField f:pfields) + { + for (String key:f.getAnnotations().keySet()) + { + if (key.equals(annotation.getName())) + { + fields.add(f.toField()); + } + } + } + } + return fields; + } + + + public String toString () + { + StringBuffer strbuff = new StringBuffer(); + for (Map.Entry e:parsedClasses.entrySet()) + { + strbuff.append(e.getValue()); + strbuff.append("\n"); + } + return strbuff.toString(); + } + + + private void scanClass (InputStream is) + throws IOException + { + ClassReader reader = new ClassReader(is); + reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES); + } +} diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationProcessor.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationProcessor.java new file mode 100644 index 00000000000..652e69b2188 --- /dev/null +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationProcessor.java @@ -0,0 +1,782 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Resource; +import javax.annotation.Resources; +import javax.annotation.security.RunAs; +import javax.naming.InitialContext; +import javax.naming.NameNotFoundException; +import javax.naming.NamingException; +import javax.servlet.DispatcherType; +import javax.servlet.http.annotation.InitParam; +import javax.servlet.http.annotation.jaxrs.DELETE; +import javax.servlet.http.annotation.jaxrs.GET; +import javax.servlet.http.annotation.jaxrs.HEAD; +import javax.servlet.http.annotation.jaxrs.POST; +import javax.servlet.http.annotation.jaxrs.PUT; + +import org.eclipse.jetty.plus.annotation.Injection; +import org.eclipse.jetty.plus.annotation.InjectionCollection; +import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection; +import org.eclipse.jetty.plus.annotation.PojoContextListener; +import org.eclipse.jetty.plus.annotation.PojoFilter; +import org.eclipse.jetty.plus.annotation.PojoServlet; +import org.eclipse.jetty.plus.annotation.PostConstructCallback; +import org.eclipse.jetty.plus.annotation.PreDestroyCallback; +import org.eclipse.jetty.plus.annotation.RunAsCollection; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.FilterMapping; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.servlet.ServletMapping; +import org.eclipse.jetty.util.IntrospectionUtil; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.webapp.WebAppContext; + + + +/** + * AnnotationProcessor + * + * Act on the annotations discovered in the webapp. + */ +public class AnnotationProcessor +{ + AnnotationFinder _finder; + ClassLoader _loader; + RunAsCollection _runAs; + InjectionCollection _injections; + LifeCycleCallbackCollection _callbacks; + List _servlets; + List _filters; + List _listeners; + List _servletMappings; + List _filterMappings; + Map _pojoInstances = new HashMap(); + WebAppContext _webApp; + + private static Class[] __envEntryTypes = + new Class[] {String.class, Character.class, Integer.class, Boolean.class, Double.class, Byte.class, Short.class, Long.class, Float.class}; + + public AnnotationProcessor(WebAppContext webApp, AnnotationFinder finder, RunAsCollection runAs, InjectionCollection injections, LifeCycleCallbackCollection callbacks, + List servlets, List filters, List listeners, List servletMappings, List filterMappings) + { + _webApp=webApp; + _finder=finder; + _runAs=runAs; + _injections=injections; + _callbacks=callbacks; + _servlets=servlets; + _filters=filters; + _listeners=listeners; + _servletMappings=servletMappings; + _filterMappings=filterMappings; + } + + + public void process () + throws Exception + { + processServlets(); + processFilters(); + processListeners(); + processRunAsAnnotations(); + processLifeCycleCallbackAnnotations(); + processResourcesAnnotations(); + processResourceAnnotations(); + } + + public void processServlets () + throws Exception + { + //@Servlet(urlMappings=String[], description=String, icon=String, loadOnStartup=int, name=String, initParams=InitParams[]) + for (Class clazz:_finder.getClassesForAnnotation(javax.servlet.http.annotation.Servlet.class)) + { + javax.servlet.http.annotation.Servlet annotation = (javax.servlet.http.annotation.Servlet)clazz.getAnnotation(javax.servlet.http.annotation.Servlet.class); + PojoServlet servlet = new PojoServlet(getPojoInstanceFor(clazz)); + + List methods = _finder.getMethodsForAnnotation(GET.class); + if (methods.size() > 1) + throw new IllegalStateException ("More than one GET annotation on "+clazz.getName()); + else if (methods.size() == 1) + servlet.setGetMethodName(methods.get(0).getName()); + + methods = _finder.getMethodsForAnnotation(POST.class); + if (methods.size() > 1) + throw new IllegalStateException ("More than one POST annotation on "+clazz.getName()); + else if (methods.size() == 1) + servlet.setPostMethodName(methods.get(0).getName()); + + methods = _finder.getMethodsForAnnotation(PUT.class); + if (methods.size() > 1) + throw new IllegalStateException ("More than one PUT annotation on "+clazz.getName()); + else if (methods.size() == 1) + servlet.setPutMethodName(methods.get(0).getName()); + + methods = _finder.getMethodsForAnnotation(DELETE.class); + if (methods.size() > 1) + throw new IllegalStateException ("More than one DELETE annotation on "+clazz.getName()); + else if (methods.size() == 1) + servlet.setDeleteMethodName(methods.get(0).getName()); + + methods = _finder.getMethodsForAnnotation(HEAD.class); + if (methods.size() > 1) + throw new IllegalStateException ("More than one HEAD annotation on "+clazz.getName()); + else if (methods.size() == 1) + servlet.setHeadMethodName(methods.get(0).getName()); + + ServletHolder holder = new ServletHolder(servlet); + holder.setName((annotation.name().equals("")?clazz.getName():annotation.name())); + holder.setInitOrder(annotation.loadOnStartup()); + LazyList.add(_servlets, holder); + + for (InitParam ip:annotation.initParams()) + { + holder.setInitParameter(ip.name(), ip.value()); + } + + if (annotation.urlMappings().length > 0) + { + ArrayList paths = new ArrayList(); + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(holder.getName()); + for (String s:annotation.urlMappings()) + { + paths.add(normalizePattern(s)); + } + mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()])); + LazyList.add(_servletMappings,mapping); + } + } + } + + public void processFilters () + throws Exception + { + //@ServletFilter(description=String, filterName=String, displayName=String, icon=String,initParams=InitParam[], filterMapping=FilterMapping) + for (Class clazz:_finder.getClassesForAnnotation(javax.servlet.http.annotation.ServletFilter.class)) + { + javax.servlet.http.annotation.ServletFilter annotation = (javax.servlet.http.annotation.ServletFilter)clazz.getAnnotation(javax.servlet.http.annotation.ServletFilter.class); + PojoFilter filter = new PojoFilter(getPojoInstanceFor(clazz)); + + FilterHolder holder = new FilterHolder(filter); + holder.setName((annotation.filterName().equals("")?clazz.getName():annotation.filterName())); + holder.setDisplayName(annotation.displayName()); + LazyList.add(_filters, holder); + + for (InitParam ip:annotation.initParams()) + { + holder.setInitParameter(ip.name(), ip.value()); + } + + if (annotation.filterMapping() != null) + { + FilterMapping mapping = new FilterMapping(); + mapping.setFilterName(holder.getName()); + ArrayList paths = new ArrayList(); + for (String s:annotation.filterMapping().urlPattern()) + { + paths.add(normalizePattern(s)); + } + mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()])); + ArrayList names = new ArrayList(); + for (String s:annotation.filterMapping().servletNames()) + { + names.add(s); + } + mapping.setServletNames((String[])names.toArray(new String[names.size()])); + + int dispatcher=FilterMapping.DEFAULT; + for (DispatcherType d:annotation.filterMapping().dispatcherTypes()) + { + dispatcher = dispatcher|FilterMapping.dispatch(d); + } + mapping.setDispatches(dispatcher); + LazyList.add(_filterMappings,mapping); + } + } + } + + + + public void processListeners () + throws Exception + { + //@ServletContextListener(description=String) + for (Class clazz:_finder.getClassesForAnnotation(javax.servlet.http.annotation.ServletContextListener.class)) + { + PojoContextListener listener = new PojoContextListener(getPojoInstanceFor(clazz)); + LazyList.add(_listeners, listener); + } + } + + + public List getServlets () + { + return _servlets; + } + + public List getServletMappings () + { + return _servletMappings; + } + + public List getFilters () + { + return _filters; + } + + public List getFilterMappings () + { + return _filterMappings; + } + + public List getListeners() + { + return _listeners; + } + + + public void processRunAsAnnotations () + throws Exception + { + for (Class clazz:_finder.getClassesForAnnotation(RunAs.class)) + { + if (!javax.servlet.Servlet.class.isAssignableFrom(clazz) && !(_pojoInstances.containsKey(clazz))) + { + Log.debug("Ignoring runAs notation on on-servlet class "+clazz.getName()); + continue; + } + RunAs runAs = (RunAs)clazz.getAnnotation(RunAs.class); + if (runAs != null) + { + String role = runAs.value(); + if (role != null) + { + org.eclipse.jetty.plus.annotation.RunAs ra = new org.eclipse.jetty.plus.annotation.RunAs(); + ra.setTargetClass(clazz); + ra.setRoleName(role); + _runAs.add(ra); + } + } + } + } + + + public void processLifeCycleCallbackAnnotations() + throws Exception + { + processPostConstructAnnotations(); + processPreDestroyAnnotations(); + } + + private void processPostConstructAnnotations () + throws Exception + { + // TODO: check that the same class does not have more than one + for (Method m:_finder.getMethodsForAnnotation(PostConstruct.class)) + { + if (!isServletType(m.getDeclaringClass())) + { + Log.debug("Ignoring "+m.getName()+" as non-servlet type"); + continue; + } + if (m.getParameterTypes().length != 0) + throw new IllegalStateException(m+" has parameters"); + if (m.getReturnType() != Void.TYPE) + throw new IllegalStateException(m+" is not void"); + if (m.getExceptionTypes().length != 0) + throw new IllegalStateException(m+" throws checked exceptions"); + if (Modifier.isStatic(m.getModifiers())) + throw new IllegalStateException(m+" is static"); + + PostConstructCallback callback = new PostConstructCallback(); + callback.setTargetClass(m.getDeclaringClass()); + callback.setTarget(m); + _callbacks.add(callback); + } + } + + public void processPreDestroyAnnotations () + throws Exception + { + //TODO: check that the same class does not have more than one + + for (Method m: _finder.getMethodsForAnnotation(PreDestroy.class)) + { + if (!isServletType(m.getDeclaringClass())) + { + Log.debug("Ignoring "+m.getName()+" as non-servlet type"); + continue; + } + if (m.getParameterTypes().length != 0) + throw new IllegalStateException(m+" has parameters"); + if (m.getReturnType() != Void.TYPE) + throw new IllegalStateException(m+" is not void"); + if (m.getExceptionTypes().length != 0) + throw new IllegalStateException(m+" throws checked exceptions"); + if (Modifier.isStatic(m.getModifiers())) + throw new IllegalStateException(m+" is static"); + + PreDestroyCallback callback = new PreDestroyCallback(); + callback.setTargetClass(m.getDeclaringClass()); + callback.setTarget(m); + _callbacks.add(callback); + } + } + + + /** + * Process @Resources annotation on classes + */ + public void processResourcesAnnotations () + throws Exception + { + List> classes = _finder.getClassesForAnnotation(Resources.class); + for (Class clazz:classes) + { + if (!isServletType(clazz)) + { + Log.debug("Ignoring @Resources annotation on on-servlet type class "+clazz.getName()); + continue; + } + //Handle Resources annotation - add namespace entries + Resources resources = (Resources)clazz.getAnnotation(Resources.class); + if (resources == null) + continue; + + Resource[] resArray = resources.value(); + if (resArray==null||resArray.length==0) + continue; + + for (int j=0;j> classes = _finder.getClassesForAnnotation(Resource.class); + for (Class clazz:classes) + { + if (!isServletType(clazz)) + { + Log.debug("Ignoring @Resource annotation on on-servlet type class "+clazz.getName()); + continue; + } + //Handle Resource annotation - add namespace entries + Resource resource = (Resource)clazz.getAnnotation(Resource.class); + if (resource != null) + { + String name = resource.name(); + String mappedName = resource.mappedName(); + Resource.AuthenticationType auth = resource.authenticationType(); + Class type = resource.type(); + boolean shareable = resource.shareable(); + + if (name==null || name.trim().equals("")) + throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)"); + + try + { + //TODO don't ignore the shareable, auth etc etc + if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp, name,mappedName)) + if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp.getServer(), name,mappedName)) + throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName)); + } + catch (NamingException e) + { + throw new IllegalStateException(e); + } + } + } + } + + /** + * Process a Resource annotation on the Methods. + * + * This will generate a JNDI entry, and an Injection to be + * processed when an instance of the class is created. + * @param injections + */ + public void processMethodResourceAnnotations () + throws Exception + { + //Get all methods that have a Resource annotation + List methods = _finder.getMethodsForAnnotation(javax.annotation.Resource.class); + + for (Method m: methods) + { + if (!isServletType(m.getDeclaringClass())) + { + Log.debug("Ignoring @Resource annotation on on-servlet type method "+m.getName()); + continue; + } + /* + * Commons Annotations Spec 2.3 + * " The Resource annotation is used to declare a reference to a resource. + * It can be specified on a class, methods or on fields. When the + * annotation is applied on a field or method, the container will + * inject an instance of the requested resource into the application + * when the application is initialized... Even though this annotation + * is not marked Inherited, if used all superclasses MUST be examined + * to discover all uses of this annotation. All such annotation instances + * specify resources that are needed by the application. Note that this + * annotation may appear on private fields and methods of the superclasses. + * Injection of the declared resources needs to happen in these cases as + * well, even if a method with such an annotation is overridden by a subclass." + * + * Which IMHO, put more succinctly means "If you find a @Resource on any method + * or field, inject it!". + */ + Resource resource = (Resource)m.getAnnotation(Resource.class); + if (resource == null) + continue; + + //JavaEE Spec 5.2.3: Method cannot be static + if (Modifier.isStatic(m.getModifiers())) + throw new IllegalStateException(m+" cannot be static"); + + + // Check it is a valid javabean + if (!IntrospectionUtil.isJavaBeanCompliantSetter(m)) + throw new IllegalStateException(m+" is not a java bean compliant setter method"); + + //default name is the javabean property name + String name = m.getName().substring(3); + name = name.substring(0,1).toLowerCase()+name.substring(1); + name = m.getDeclaringClass().getCanonicalName()+"/"+name; + //allow default name to be overridden + name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name); + //get the mappedName if there is one + String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null); + + Class type = m.getParameterTypes()[0]; + + //get other parts that can be specified in @Resource + Resource.AuthenticationType auth = resource.authenticationType(); + boolean shareable = resource.shareable(); + + //if @Resource specifies a type, check it is compatible with setter param + if ((resource.type() != null) + && + !resource.type().equals(Object.class) + && + (!IntrospectionUtil.isTypeCompatible(type, resource.type(), false))) + throw new IllegalStateException("@Resource incompatible type="+resource.type()+ " with method param="+type+ " for "+m); + + //check if an injection has already been setup for this target by web.xml + Injection webXmlInjection = _injections.getInjection(m.getDeclaringClass(), m); + if (webXmlInjection == null) + { + try + { + //try binding name to environment + //try the webapp's environment first + boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp, name, mappedName); + + //try the server's environment + if (!bound) + bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName); + + //try the jvm's environment + if (!bound) + bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName); + + //TODO if it is an env-entry from web.xml it can be injected, in which case there will be no + //NamingEntry, just a value bound in java:comp/env + if (!bound) + { + try + { + InitialContext ic = new InitialContext(); + String nameInEnvironment = (mappedName!=null?mappedName:name); + ic.lookup("java:comp/env/"+nameInEnvironment); + bound = true; + } + catch (NameNotFoundException e) + { + bound = false; + } + } + + if (bound) + { + Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name); + // Make the Injection for it + Injection injection = new Injection(); + injection.setTargetClass(m.getDeclaringClass()); + injection.setJndiName(name); + injection.setMappingName(mappedName); + injection.setTarget(m); + _injections.add(injection); + } + else if (!isEnvEntryType(type)) + { + + //if this is an env-entry type resource and there is no value bound for it, it isn't + //an error, it just means that perhaps the code will use a default value instead + // JavaEE Spec. sec 5.4.1.3 + throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName)); + } + } + catch (NamingException e) + { + //if this is an env-entry type resource and there is no value bound for it, it isn't + //an error, it just means that perhaps the code will use a default value instead + // JavaEE Spec. sec 5.4.1.3 + if (!isEnvEntryType(type)) + throw new IllegalStateException(e); + } + } + else + { + //if an injection is already set up for this name, then the types must be compatible + //JavaEE spec sec 5.2.4 + + Object value = webXmlInjection.lookupInjectedValue(); + if (!IntrospectionUtil.isTypeCompatible(type, value.getClass(), false)) + throw new IllegalStateException("Type of field="+type+" is not compatible with Resource type="+value.getClass()); + } + } + } + + /** + * Process @Resource annotation for a Field. These will both set up a + * JNDI entry and generate an Injection. Or they can be the equivalent + * of env-entries with default values + * + * @param injections + */ + public void processFieldResourceAnnotations () + throws Exception + { + //Get all fields that have a Resource annotation + List fields = _finder.getFieldsForAnnotation(Resource.class); + for (Field f: fields) + { + if (!isServletType(f.getDeclaringClass())) + { + Log.debug("Ignoring @Resource annotation on on-servlet type field "+f.getName()); + continue; + } + Resource resource = (Resource)f.getAnnotation(Resource.class); + if (resource == null) + continue; + + //JavaEE Spec 5.2.3: Field cannot be static + if (Modifier.isStatic(f.getModifiers())) + throw new IllegalStateException(f+" cannot be static"); + + //JavaEE Spec 5.2.3: Field cannot be final + if (Modifier.isFinal(f.getModifiers())) + throw new IllegalStateException(f+" cannot be final"); + + //work out default name + String name = f.getDeclaringClass().getCanonicalName()+"/"+f.getName(); + //allow @Resource name= to override the field name + name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name); + + //get the type of the Field + Class type = f.getType(); + //if @Resource specifies a type, check it is compatible with field type + if ((resource.type() != null) + && + !resource.type().equals(Object.class) + && + (!IntrospectionUtil.isTypeCompatible(type, resource.type(), false))) + throw new IllegalStateException("@Resource incompatible type="+resource.type()+ " with field type ="+f.getType()); + + //get the mappedName if there is one + String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null); + //get other parts that can be specified in @Resource + Resource.AuthenticationType auth = resource.authenticationType(); + boolean shareable = resource.shareable(); + //check if an injection has already been setup for this target by web.xml + Injection webXmlInjection = _injections.getInjection(f.getDeclaringClass(), f); + if (webXmlInjection == null) + { + try + { + boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp, name, mappedName); + if (!bound) + bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName); + if (!bound) + bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName); + if (!bound) + { + //see if there is an env-entry value been bound from web.xml + try + { + InitialContext ic = new InitialContext(); + String nameInEnvironment = (mappedName!=null?mappedName:name); + ic.lookup("java:comp/env/"+nameInEnvironment); + bound = true; + } + catch (NameNotFoundException e) + { + bound = false; + } + } + //Check there is a JNDI entry for this annotation + if (bound) + { + Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name); + // Make the Injection for it if the binding succeeded + Injection injection = new Injection(); + injection.setTargetClass(f.getDeclaringClass()); + injection.setJndiName(name); + injection.setMappingName(mappedName); + injection.setTarget(f); + _injections.add(injection); + } + else if (!isEnvEntryType(type)) + { + //if this is an env-entry type resource and there is no value bound for it, it isn't + //an error, it just means that perhaps the code will use a default value instead + // JavaEE Spec. sec 5.4.1.3 + + throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName)); + } + } + catch (NamingException e) + { + //if this is an env-entry type resource and there is no value bound for it, it isn't + //an error, it just means that perhaps the code will use a default value instead + // JavaEE Spec. sec 5.4.1.3 + if (!isEnvEntryType(type)) + throw new IllegalStateException(e); + } + } + else + { + //if an injection is already set up for this name, then the types must be compatible + //JavaEE spec sec 5.2.4 + Object value = webXmlInjection.lookupInjectedValue(); + if (!IntrospectionUtil.isTypeCompatible(type, value.getClass(), false)) + throw new IllegalStateException("Type of field="+type+" is not compatible with Resource type="+value.getClass()); + } + } + } + + + /** + * Check if the presented method belongs to a class that is one + * of the classes with which a servlet container should be concerned. + * @param m + * @return + */ + private boolean isServletType (Class c) + { + boolean isServlet = false; + if (javax.servlet.Servlet.class.isAssignableFrom(c) || + javax.servlet.Filter.class.isAssignableFrom(c) || + javax.servlet.ServletContextListener.class.isAssignableFrom(c) || + javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) || + javax.servlet.ServletRequestListener.class.isAssignableFrom(c) || + javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) || + javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) || + javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) || + (_pojoInstances.get(c) != null)) + + isServlet=true; + + return isServlet; + } + + + /** + * Get an already-created instance of a pojo, or create one + * otherwise. + * @param clazz + * @return + * @throws InstantiationException + * @throws IllegalAccessException + */ + private Object getPojoInstanceFor (Class clazz) + throws InstantiationException, IllegalAccessException + { + Object instance = _pojoInstances.get(clazz); + if (instance == null) + { + instance = clazz.newInstance(); + _pojoInstances.put(clazz, instance); + } + return instance; + } + + private static boolean isEnvEntryType (Class type) + { + boolean result = false; + for (int i=0;i<__envEntryTypes.length && !result;i++) + { + result = (type.equals(__envEntryTypes[i])); + } + return result; + } + + protected static String normalizePattern(String p) + { + if (p!=null && p.length()>0 && !p.startsWith("/") && !p.startsWith("*")) + return "/"+p; + return p; + } +} diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassNameResolver.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassNameResolver.java new file mode 100644 index 00000000000..a76544306dd --- /dev/null +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassNameResolver.java @@ -0,0 +1,37 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + + + +public interface ClassNameResolver +{ + /** + * Based on the execution context, should the class represented + * by "name" be excluded from consideration? + * @param name + * @return + */ + public boolean isExcluded (String name); + + + /** + * Based on the execution context, if a duplicate class + * represented by "name" is detected, should the existing + * one be overridden or not? + * @param name + * @return + */ + public boolean shouldOverride (String name); +} diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Configuration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Configuration.java new file mode 100644 index 00000000000..a9740978edc --- /dev/null +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Configuration.java @@ -0,0 +1,145 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + +import java.util.EventListener; + +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.FilterMapping; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.servlet.ServletMapping; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; + +/** + * Configuration + * + * + */ +public class Configuration extends org.eclipse.jetty.plus.webapp.Configuration +{ + public static final String __web_inf_pattern = "org.eclipse.jetty.server.webapp.WebInfIncludeAnnotationJarPattern"; + public static final String __container_pattern = "org.eclipse.jetty.server.webapp.ContainerIncludeAnnotationJarPattern"; + + + + public Configuration () throws ClassNotFoundException + { + super(); + } + + /** + * @see org.eclipse.jetty.plus.webapp.AbstractConfiguration#parseAnnotations() + */ + public void parseAnnotations() throws Exception + { + /* + * TODO Need to also take account of hidden classes on system classpath that should never + * contribute annotations to a webapp (system and server classes): + * + * --- when scanning system classpath: + * + system classes : should always be scanned (subject to pattern) + * + server classes : always ignored + * + * --- when scanning webapp classpath: + * + system classes : always ignored + * + server classes : always scanned + * + * + * If same class is found in both container and in context then need to use + * webappcontext parentloaderpriority to work out which one contributes the + * annotation. + */ + + + AnnotationFinder finder = new AnnotationFinder(); + + //if no pattern for the container path is defined, then by default scan NOTHING + Log.debug("Scanning system jars"); + finder.find(getWebAppContext().getClassLoader().getParent(), true, getWebAppContext().getInitParameter(__container_pattern), false, + new ClassNameResolver () + { + public boolean isExcluded (String name) + { + if (getWebAppContext().isSystemClass(name)) return false; + if (getWebAppContext().isServerClass(name)) return true; + return false; + } + + public boolean shouldOverride (String name) + { + //looking at system classpath + if (getWebAppContext().isParentLoaderPriority()) + return true; + return false; + } + }); + + Log.debug("Scanning WEB-INF/lib jars"); + //if no pattern for web-inf/lib is defined, then by default scan everything in it + finder.find (getWebAppContext().getClassLoader(), false, getWebAppContext().getInitParameter(__web_inf_pattern), true, + new ClassNameResolver() + { + public boolean isExcluded (String name) + { + if (getWebAppContext().isSystemClass(name)) return true; + if (getWebAppContext().isServerClass(name)) return false; + return false; + } + + public boolean shouldOverride (String name) + { + //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp? + if (getWebAppContext().isParentLoaderPriority()) + return false; + return true; + } + }); + + Log.debug("Scanning classes in WEB-INF/classes"); + finder.find(_context.getWebInf().addPath("classes/"), + new ClassNameResolver() + { + public boolean isExcluded (String name) + { + if (getWebAppContext().isSystemClass(name)) return true; + if (getWebAppContext().isServerClass(name)) return false; + return false; + } + + public boolean shouldOverride (String name) + { + //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp? + if (getWebAppContext().isParentLoaderPriority()) + return false; + return true; + } + }); + + AnnotationProcessor processor = new AnnotationProcessor(getWebAppContext(), finder, _runAsCollection, _injections, _callbacks, + LazyList.getList(_servlets), LazyList.getList(_filters), LazyList.getList(_listeners), + LazyList.getList(_servletMappings), LazyList.getList(_filterMappings)); + processor.process(); + _servlets = processor.getServlets(); + _filters = processor.getFilters(); + _servletMappings = processor.getServletMappings(); + _filterMappings = processor.getFilterMappings(); + _listeners = processor.getListeners(); + _servletHandler.setFilters((FilterHolder[])LazyList.toArray(_filters,FilterHolder.class)); + _servletHandler.setFilterMappings((FilterMapping[])LazyList.toArray(_filterMappings,FilterMapping.class)); + _servletHandler.setServlets((ServletHolder[])LazyList.toArray(_servlets,ServletHolder.class)); + _servletHandler.setServletMappings((ServletMapping[])LazyList.toArray(_servletMappings,ServletMapping.class)); + getWebAppContext().setEventListeners((EventListener[])LazyList.toArray(_listeners,EventListener.class)); + } +} diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java new file mode 100644 index 00000000000..b7978c1b09e --- /dev/null +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java @@ -0,0 +1,49 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + +import java.util.List; + +/** + * Util + * + * + */ +public class Util +{ + + + /** + * Find all annotations directly and inherited on a class. + * + * @param clazz + * @return + * @throws Exception + */ + public List findMethodAnnotations (Class clazz) + throws Exception + { + //TODO + + return null; + } + + public List findFieldAnnotations (Class clazz) + throws Exception + { + //TODO + + return null; + } +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassA.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassA.java new file mode 100644 index 00000000000..6d353e74e3b --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassA.java @@ -0,0 +1,95 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + + +/** + * ClassA + * + * + */ +@Sample(1) +public class ClassA +{ + private Integer e; + private Integer f; + private Integer g; + private Integer h; + private Integer j; + private Integer k; + + + public static class Foo + { + + } + + @Sample(7) + private Integer m; + + @Sample(2) + public void a (Integer[] x) + { + System.err.println("ClassA.public"); + } + + @Sample(3) + protected void b(Foo[] f) + { + System.err.println("ClassA.protected"); + } + + @Sample(4) + void c(int[] x) + { + System.err.println("ClassA.package"); + } + + @Sample(5) + private void d(int x, String y) + { + System.err.println("ClassA.private"); + } + + @Sample(6) + protected void l() + { + System.err.println("ClassA.protected method l"); + } + + public Integer getE() + { + return this.e; + } + + public Integer getF() + { + return this.f; + } + + public Integer getG() + { + return this.g; + } + + public Integer getJ() + { + return this.j; + } + + public void x() + { + System.err.println("ClassA.x"); + } +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassB.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassB.java new file mode 100644 index 00000000000..2bd5fb73ba2 --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassB.java @@ -0,0 +1,53 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + + + +/** + * ClassB + * + * + */ +@Sample(value=50) +public class ClassB extends ClassA +{ + + //test override of public scope method + @Sample(value=51) + public void a() + { + System.err.println("ClassB.public"); + } + + //test override of package scope method + @Sample(value=52) + void c() + { + System.err.println("ClassB.package"); + } + + public void l() + { + System.err.println("Overridden method l has no annotation"); + } + + + //test no annotation + public void z() + { + System.err.println("ClassB.z"); + } + +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassC.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassC.java new file mode 100644 index 00000000000..f3828a0a89f --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ClassC.java @@ -0,0 +1,75 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.annotations; + +import java.io.IOException; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Resource; +import javax.annotation.security.RunAs; +import javax.servlet.DispatcherType; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.annotation.FilterMapping; +import javax.servlet.http.annotation.InitParam; +import javax.servlet.http.annotation.Servlet; +import javax.servlet.http.annotation.ServletFilter; +import javax.servlet.http.annotation.jaxrs.GET; +import javax.servlet.http.annotation.jaxrs.POST; + + +@Servlet(urlMappings = { "/foo/*", "/bah/*" }, name="CServlet", initParams={@InitParam(name="x", value="y")}) +@ServletFilter(filterName="CFilter", filterMapping=@FilterMapping(dispatcherTypes={DispatcherType.REQUEST}, urlPattern = {"/*"}), initParams={@InitParam(name="a", value="99")}) +@RunAs("admin") +public class ClassC +{ + @Resource (mappedName="foo") + private Double foo; + + @PreDestroy + public void pre () + { + + } + + @PostConstruct + public void post() + { + + } + + @GET() + @POST() + public void anything (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + response.setContentType("text/html"); + response.getWriter().println("

Pojo Servlet

"); + response.getWriter().println("Acting like a Servlet."); + } + + + public void doFilter (HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws java.io.IOException, javax.servlet.ServletException + { + HttpSession session = request.getSession(true); + String val = request.getParameter("action"); + if (val!=null) + session.setAttribute("action", val); + chain.doFilter(request, response); + } +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/Sample.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/Sample.java new file mode 100644 index 00000000000..891f0872dd0 --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/Sample.java @@ -0,0 +1,26 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface Sample +{ + int value(); +} + diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletAnnotationTest.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletAnnotationTest.java new file mode 100644 index 00000000000..3d251b5887e --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletAnnotationTest.java @@ -0,0 +1,155 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.annotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NameNotFoundException; + +import junit.framework.TestCase; + +import org.eclipse.jetty.plus.annotation.Injection; +import org.eclipse.jetty.plus.annotation.InjectionCollection; +import org.eclipse.jetty.plus.annotation.LifeCycleCallback; +import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection; +import org.eclipse.jetty.plus.annotation.PojoFilter; +import org.eclipse.jetty.plus.annotation.PojoServlet; +import org.eclipse.jetty.plus.annotation.RunAs; +import org.eclipse.jetty.plus.annotation.RunAsCollection; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.FilterMapping; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.servlet.ServletMapping; +import org.eclipse.jetty.webapp.WebAppContext; + +public class ServletAnnotationTest extends TestCase +{ + + public void tearDown() + throws Exception + { + InitialContext ic = new InitialContext(); + Context comp = (Context)ic.lookup("java:comp"); + comp.destroySubcontext("env"); + } + + public void testAnnotations() throws Exception + { + Server server = new Server(); + WebAppContext wac = new WebAppContext(); + wac.setServer(server); + + InitialContext ic = new InitialContext(); + Context comp = (Context)ic.lookup("java:comp"); + Context env = null; + try + { + env = (Context)comp.lookup("env"); + } + catch (NameNotFoundException e) + { + env = comp.createSubcontext("env"); + } + + org.eclipse.jetty.plus.jndi.EnvEntry foo = new org.eclipse.jetty.plus.jndi.EnvEntry("foo", new Double(1000.00), false); + List servlets = new ArrayList(); + List filters = new ArrayList(); + List listeners = new ArrayList(); + List servletMappings = new ArrayList(); + List filterMappings = new ArrayList(); + + List classes = new ArrayList(); + classes.add("org.eclipse.jetty.annotations.ClassC"); + + AnnotationFinder finder = new AnnotationFinder(); + finder.find (classes, + new ClassNameResolver() + { + + public boolean isExcluded(String name) + { + return false; + } + + public boolean shouldOverride(String name) + { + return true; + } + + }); + + + RunAsCollection runAs = new RunAsCollection(); + InjectionCollection injections = new InjectionCollection(); + LifeCycleCallbackCollection callbacks = new LifeCycleCallbackCollection(); + + AnnotationProcessor processor = new AnnotationProcessor (wac, finder, runAs, injections, callbacks, + servlets, filters, listeners, servletMappings, filterMappings); + processor.process(); + + + assertEquals(1, servlets.size()); + ServletHolder sholder = (ServletHolder)servlets.get(0); + assertEquals("CServlet", sholder.getName()); + assertTrue(sholder.getServlet() instanceof PojoServlet); + PojoServlet ps = (PojoServlet)sholder.getServlet(); + assertEquals("anything", ps.getGetMethodName()); + assertEquals("anything", ps.getPostMethodName()); + Map sinitparams = sholder.getInitParameters(); + assertEquals(1, sinitparams.size()); + assertTrue(sinitparams.containsKey("x")); + assertTrue(sinitparams.containsValue("y")); + assertEquals(1, filters.size()); + FilterHolder fholder = (FilterHolder)filters.get(0); + assertTrue(fholder.getFilter() instanceof PojoFilter); + + Map finitparams = fholder.getInitParameters(); + assertEquals(1, finitparams.size()); + assertTrue(finitparams.containsKey("a")); + assertTrue(finitparams.containsValue("99")); + assertEquals(1, servletMappings.size()); + ServletMapping smap = (ServletMapping)servletMappings.get(0); + assertEquals("CServlet", smap.getServletName()); + assertEquals(2, smap.getPathSpecs().length); + assertEquals(1, filterMappings.size()); + FilterMapping fmap = (FilterMapping)filterMappings.get(0); + assertEquals("CFilter", fmap.getFilterName()); + assertEquals(1, fmap.getPathSpecs().length); + + List fieldInjections = injections.getFieldInjections(ClassC.class); + assertNotNull(fieldInjections); + assertEquals(1, fieldInjections.size()); + + RunAs ra = runAs.getRunAs(sholder); + assertNotNull(ra); + assertEquals("admin", ra.getRoleName()); + + List predestroys = callbacks.getPreDestroyCallbacks(sholder.getServlet()); + assertNotNull(predestroys); + assertEquals(1, predestroys.size()); + LifeCycleCallback cb = (LifeCycleCallback)predestroys.get(0); + assertTrue(cb.getTarget().equals(ClassC.class.getDeclaredMethod("pre", new Class[]{}))); + + List postconstructs = callbacks.getPostConstructCallbacks(sholder.getServlet()); + assertNotNull(postconstructs); + assertEquals(1, postconstructs.size()); + cb = (LifeCycleCallback)postconstructs.get(0); + assertTrue(cb.getTarget().equals(ClassC.class.getDeclaredMethod("post", new Class[]{}))); + } + +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationFinder.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationFinder.java new file mode 100644 index 00000000000..89e0e4ed556 --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationFinder.java @@ -0,0 +1,105 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +import junit.framework.TestCase; + +public class TestAnnotationFinder extends TestCase +{ + + public void testNormalize() + { + assertEquals("org.eclipse.test.Foo", AnnotationFinder.normalize("Lorg/eclipse/test/Foo;")); + assertEquals("org.eclipse.test.Foo.Bar", AnnotationFinder.normalize("org/eclipse/test/Foo$Bar.class")); + } + + public void testConvertType () + throws Exception + { + + } + + + public void testSampleAnnotation () + throws Exception + { + long start = System.currentTimeMillis(); + + String[] classNames = new String[]{"org.eclipse.jetty.annotations.ClassA"}; + AnnotationFinder finder = new AnnotationFinder(); + + finder.find(classNames, new ClassNameResolver () + { + + public boolean isExcluded(String name) + { + return false; + } + + public boolean shouldOverride(String name) + { + return false; + } + + }); + long end = System.currentTimeMillis(); + + System.err.println("Time to parse class: "+((end-start))); + + start = System.currentTimeMillis(); + List> classes = finder.getClassesForAnnotation(Sample.class); + end = System.currentTimeMillis(); + System.err.println("Time to find classes matching annotation: "+((end-start))); + + assertNotNull(classes); + assertEquals(1, classes.size()); + assertTrue(classes.contains(org.eclipse.jetty.annotations.ClassA.class)); + + start = System.currentTimeMillis(); + List methods = finder.getMethodsForAnnotation(Sample.class); + end = System.currentTimeMillis(); + System.err.println("Time to find methods matching annotation : "+((end-start))); + + assertNotNull(methods); + assertEquals (5, methods.size()); + + Method a = ClassA.class.getDeclaredMethod("a", new Class[]{Array.newInstance(Integer.class, 0).getClass()}); + Method b = ClassA.class.getDeclaredMethod("b", new Class[]{Array.newInstance(ClassA.Foo.class, 0).getClass()}); + Method c = ClassA.class.getDeclaredMethod("c", new Class[]{Array.newInstance(Integer.TYPE, 0).getClass()}); + Method d = ClassA.class.getDeclaredMethod("d", new Class[]{Integer.TYPE, String.class}); + Method l = ClassA.class.getDeclaredMethod("l", new Class[]{}); + + assertTrue(methods.contains(a)); + assertTrue(methods.contains(b)); + assertTrue(methods.contains(c)); + assertTrue(methods.contains(d)); + assertTrue(methods.contains(l)); + + start = System.currentTimeMillis(); + List fields = finder.getFieldsForAnnotation(Sample.class); + end = System.currentTimeMillis(); + System.err.println("Time to find fields matching annotation : "+((end-start))); + + assertNotNull(fields); + assertEquals(1, fields.size()); + + Field m = ClassA.class.getDeclaredField("m"); + assertTrue(fields.contains(m)); + } +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java new file mode 100644 index 00000000000..6efe9eac2db --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java @@ -0,0 +1,249 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.Resource; +import javax.annotation.Resources; +import javax.naming.Context; +import javax.naming.InitialContext; + +import junit.framework.TestCase; + +import org.eclipse.jetty.annotations.resources.ResourceA; +import org.eclipse.jetty.annotations.resources.ResourceB; +import org.eclipse.jetty.plus.annotation.Injection; +import org.eclipse.jetty.plus.annotation.InjectionCollection; +import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection; +import org.eclipse.jetty.plus.annotation.RunAsCollection; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.webapp.WebAppContext; + +/** + * TestAnnotationInheritance + * + * + */ +public class TestAnnotationInheritance extends TestCase +{ + List classNames = new ArrayList(); + + + public void tearDown () throws Exception + { + classNames.clear(); + InitialContext ic = new InitialContext(); + Context comp = (Context)ic.lookup("java:comp"); + comp.destroySubcontext("env"); + } + + public void testInheritance () + throws Exception + { + classNames.add(ClassA.class.getName()); + classNames.add(ClassB.class.getName()); + + AnnotationFinder finder = new AnnotationFinder(); + finder.find(classNames, new ClassNameResolver () + { + public boolean isExcluded(String name) + { + return false; + } + + public boolean shouldOverride(String name) + { + return false; + } + }); + + List> classes = finder.getClassesForAnnotation(Sample.class); + assertEquals(2, classes.size()); + + //check methods + //List methods = collection.getMethods(); + List methods = finder.getMethodsForAnnotation(Sample.class); + + assertTrue(methods!=null); + assertFalse(methods.isEmpty()); + } + + + public void testExclusions() + throws Exception + { + AnnotationFinder finder = new AnnotationFinder(); + finder.find(ClassA.class.getName(), new ClassNameResolver() + { + public boolean isExcluded(String name) + { + return true; + } + + public boolean shouldOverride(String name) + { + return false; + } + }); + assertTrue(finder.getClassesForAnnotation(Sample.class).isEmpty()); + + finder.find (ClassA.class.getName(), new ClassNameResolver() + { + public boolean isExcluded(String name) + { + return false; + } + + public boolean shouldOverride(String name) + { + return false; + } + }); + assertEquals(1, finder.getClassesForAnnotation(Sample.class).size()); + } + + + public void testResourceAnnotations () + throws Exception + { + Server server = new Server(); + WebAppContext wac = new WebAppContext(); + wac.setServer(server); + + InitialContext ic = new InitialContext(); + Context comp = (Context)ic.lookup("java:comp"); + Context env = comp.createSubcontext("env"); + + org.eclipse.jetty.plus.jndi.EnvEntry resourceA = new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resA", new Integer(1000), false); + org.eclipse.jetty.plus.jndi.EnvEntry resourceB = new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resB", new Integer(2000), false); + + + classNames.add(ResourceA.class.getName()); + classNames.add(ResourceB.class.getName()); + AnnotationFinder finder = new AnnotationFinder(); + finder.find(classNames, new ClassNameResolver() + { + public boolean isExcluded(String name) + { + return false; + } + + public boolean shouldOverride(String name) + { + return false; + } + }); + + List> resourcesClasses = finder.getClassesForAnnotation(Resources.class); + assertNotNull(resourcesClasses); + assertEquals(1, resourcesClasses.size()); + + List> annotatedClasses = finder.getClassesForAnnotation(Resource.class); + List annotatedMethods = finder.getMethodsForAnnotation(Resource.class); + List annotatedFields = finder.getFieldsForAnnotation(Resource.class); + assertNotNull(annotatedClasses); + assertEquals(0, annotatedClasses.size()); + assertEquals(3, annotatedMethods.size()); + assertEquals(6, annotatedFields.size()); + + InjectionCollection injections = new InjectionCollection(); + LifeCycleCallbackCollection callbacks = new LifeCycleCallbackCollection(); + RunAsCollection runAses = new RunAsCollection(); + AnnotationProcessor processor = new AnnotationProcessor(wac, finder, runAses, injections, callbacks, + Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST); + //process with all the specific annotations turned into injections, callbacks etc + processor.process(); + + //processing classA should give us these jndi name bindings: + // java:comp/env/myf + // java:comp/env/org.eclipse.jetty.annotations.resources.ResourceA/g + // java:comp/env/mye + // java:comp/env/org.eclipse.jetty.annotations.resources.ResourceA/h + // java:comp/env/resA + // java:comp/env/org.eclipse.jetty.annotations.resources.ResourceB/f + // java:comp/env/org.eclipse.jetty.annotations.resources.ResourceA/n + // + assertEquals(resourceB.getObjectToBind(), env.lookup("myf")); + assertEquals(resourceA.getObjectToBind(), env.lookup("mye")); + assertEquals(resourceA.getObjectToBind(), env.lookup("resA")); + assertEquals(resourceA.getObjectToBind(), env.lookup("org.eclipse.jetty.annotations.resources.ResourceA/g")); + assertEquals(resourceA.getObjectToBind(), env.lookup("org.eclipse.jetty.annotations.resources.ResourceA/h")); + assertEquals(resourceB.getObjectToBind(), env.lookup("org.eclipse.jetty.annotations.resources.ResourceB/f")); + assertEquals(resourceB.getObjectToBind(), env.lookup("org.eclipse.jetty.annotations.resources.ResourceA/n")); + + //we should have Injections + assertNotNull(injections); + + List fieldInjections = injections.getFieldInjections(ResourceB.class); + assertNotNull(fieldInjections); + + Iterator itor = fieldInjections.iterator(); + System.err.println("Field injections:"); + while (itor.hasNext()) + { + System.err.println(itor.next()); + } + //only 1 field injection because the other has no Resource mapping + assertEquals(1, fieldInjections.size()); + + fieldInjections = injections.getFieldInjections(ResourceA.class); + assertNotNull(fieldInjections); + assertEquals(4, fieldInjections.size()); + + + List methodInjections = injections.getMethodInjections(ResourceB.class); + itor = methodInjections.iterator(); + System.err.println("Method injections:"); + while (itor.hasNext()) + System.err.println(itor.next()); + + assertNotNull(methodInjections); + assertEquals(0, methodInjections.size()); + + methodInjections = injections.getMethodInjections(ResourceA.class); + assertNotNull(methodInjections); + assertEquals(3, methodInjections.size()); + + //test injection + ResourceB binst = new ResourceB(); + injections.inject(binst); + + //check injected values + Field f = ResourceB.class.getDeclaredField ("f"); + f.setAccessible(true); + assertEquals(resourceB.getObjectToBind() , f.get(binst)); + + //@Resource(mappedName="resA") //test the default naming scheme but using a mapped name from the environment + f = ResourceA.class.getDeclaredField("g"); + f.setAccessible(true); + assertEquals(resourceA.getObjectToBind(), f.get(binst)); + + //@Resource(name="resA") //test using the given name as the name from the environment + f = ResourceA.class.getDeclaredField("j"); + f.setAccessible(true); + assertEquals(resourceA.getObjectToBind(), f.get(binst)); + + //@Resource(mappedName="resB") //test using the default name on an inherited field + f = ResourceA.class.getDeclaredField("n"); + f.setAccessible(true); + assertEquals(resourceB.getObjectToBind(), f.get(binst)); + } + +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceA.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceA.java new file mode 100644 index 00000000000..ef72623b2e7 --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceA.java @@ -0,0 +1,115 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations.resources; + +import java.io.IOException; + +import javax.annotation.Resource; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +/** + * ResourceA + * + * + */ +public class ResourceA implements javax.servlet.Servlet +{ + private Integer e; + private Integer h; + private Integer k; + + + @Resource(name="myf", mappedName="resB") //test giving both a name and mapped name from the environment + private Integer f;//test a non inherited field that needs injection + + @Resource(mappedName="resA") //test the default naming scheme but using a mapped name from the environment + private Integer g; + + @Resource(name="resA") //test using the given name as the name from the environment + private Integer j; + + @Resource(mappedName="resB") //test using the default name on an inherited field + protected Integer n; //TODO - if it's inherited, is it supposed to use the classname of the class it is inherited by? + + + @Resource(name="mye", mappedName="resA", type=Integer.class) + public void setE(Integer e) + { + this.e=e; + } + public Integer getE() + { + return this.e; + } + + public Integer getF() + { + return this.f; + } + + public Integer getG() + { + return this.g; + } + + public Integer getJ() + { + return this.j; + } + + @Resource(mappedName="resA") + public void setH(Integer h) + { + this.h=h; + } + + @Resource(name="resA") + public void setK(Integer k) + { + this.k=k; + } + public void x() + { + System.err.println("ResourceA.x"); + } + public void destroy() + { + // TODO Auto-generated method stub + + } + public ServletConfig getServletConfig() + { + // TODO Auto-generated method stub + return null; + } + public String getServletInfo() + { + // TODO Auto-generated method stub + return null; + } + public void init(ServletConfig arg0) throws ServletException + { + // TODO Auto-generated method stub + + } + public void service(ServletRequest arg0, ServletResponse arg1) + throws ServletException, IOException + { + // TODO Auto-generated method stub + + } +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceB.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceB.java new file mode 100644 index 00000000000..dbbb1ada70c --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceB.java @@ -0,0 +1,40 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.annotations.resources; +import javax.annotation.Resource; +import javax.annotation.Resources; + +/** + * ResourceB + * + * + */ +@Resources({ + @Resource(name="fluff", mappedName="resA"), + @Resource(name="stuff", mappedName="resB") +}) +public class ResourceB extends ResourceA +{ + @Resource(mappedName="resB") + private Integer f;//test no inheritance of private fields + + @Resource + private Integer p = new Integer(8); //test no injection because no value + + //test no annotation + public void z() + { + System.err.println("ResourceB.z"); + } +} diff --git a/jetty-assembly-descriptor/pom.xml b/jetty-assembly-descriptor/pom.xml new file mode 100644 index 00000000000..0d7705563f3 --- /dev/null +++ b/jetty-assembly-descriptor/pom.xml @@ -0,0 +1,10 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-assembly-descriptor + Jetty :: Assembly Descriptor + \ No newline at end of file diff --git a/jetty-assembly-descriptor/src/main/resources/assemblies/config.xml b/jetty-assembly-descriptor/src/main/resources/assemblies/config.xml new file mode 100644 index 00000000000..f24602ad402 --- /dev/null +++ b/jetty-assembly-descriptor/src/main/resources/assemblies/config.xml @@ -0,0 +1,17 @@ + + + config + false + + jar + + + + src/main/config + + + ** + + + + diff --git a/jetty-assembly-descriptor/src/main/resources/assemblies/site-component.xml b/jetty-assembly-descriptor/src/main/resources/assemblies/site-component.xml new file mode 100644 index 00000000000..cc3f9079da9 --- /dev/null +++ b/jetty-assembly-descriptor/src/main/resources/assemblies/site-component.xml @@ -0,0 +1,14 @@ + + site-component + + jar + + + + ${basedir} + + src/main/resources/org/eclipse/** + + + + \ No newline at end of file diff --git a/jetty-assembly/pom.xml b/jetty-assembly/pom.xml new file mode 100644 index 00000000000..c7a3a8fa38d --- /dev/null +++ b/jetty-assembly/pom.xml @@ -0,0 +1,503 @@ + + 4.0.0 + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + jetty-assembly + Jetty :: Distribution Assemblies + pom + + target/assembly-prep + 1.5.6 + 9.1.02.B04.p0 + + + + + maven-dependency-plugin + 2.1 + + + unpack + generate-resources + + unpack + + + + + org.eclipse.jetty + jetty-rewrite + ${project.version} + config + jar + true + ** + ${assembly.directory} + + + org.eclipse.jetty + jetty-ajp + ${project.version} + config + jar + true + ** + ${assembly.directory} + + + org.eclipse.jetty + jetty-test-webapp + ${project.version} + config + jar + true + ** + ${assembly.directory} + + + org.eclipse.jetty + jetty-jmx + ${project.version} + config + jar + true + ** + ${assembly.directory} + + + org.eclipse.jetty + jetty-util + ${project.version} + config + jar + true + ** + ${assembly.directory} + + + org.eclipse.jetty + jetty-webapp + ${project.version} + config + jar + true + ** + ${assembly.directory} + + + org.eclipse.jetty + jetty-plus + ${project.version} + config + jar + true + ** + ${assembly.directory} + + + org.eclipse.jetty + jetty-server + ${project.version} + config + jar + true + ** + ${assembly.directory} + + + + + + + + + + copy + generate-resources + + copy + + + + + + org.eclipse.jetty + jetty-util + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-io + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-http + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-server + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-security + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-servlet + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-servlets + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-xml + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-webapp + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-deploy + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + + org.eclipse.jetty + jetty-jmx + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + + org.eclipse.jetty + jetty-rewrite + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-ajp + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-annotations + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-client + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + + org.eclipse.jetty + jetty-jndi + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.mortbay.jetty + jsp-api-2.1-glassfish + ${jsp-2-1-version} + jar + true + ** + ${assembly.directory}/lib/jsp-2.1 + + + org.mortbay.jetty + jsp-2.1-glassfish + ${jsp-2-1-version} + jar + true + ** + ${assembly.directory}/lib/jsp-2.1 + + + + org.eclipse.jetty + jetty-plus + ${project.version} + jar + true + ** + ${assembly.directory}/lib + + + org.mortbay.jetty + servlet-api + ${servlet-api-version} + jar + true + ** + ${assembly.directory}/lib + + + org.eclipse.jetty + jetty-test-webapp + ${project.version} + war + true + ** + ${assembly.directory}/webapps + test.war + + + + org.eclipse.jetty + jetty-start + ${project.version} + jar + true + ** + ${assembly.directory} + start.jar + + + org.slf4j + slf4j-api + ${slf4j-version} + jar + true + ** + ${assembly.directory}/lib/slf4j + + + org.slf4j + jcl104-over-slf4j + ${slf4j-version} + jar + true + ** + ${assembly.directory}/lib/slf4j + + + org.slf4j + slf4j-simple + ${slf4j-version} + jar + true + ** + ${assembly.directory}/lib/slf4j + + + + + + generate-resources + + copy + + + + + ant + ant + ${ant-version} + + + org.eclipse.jdt + core + ${eclipse-compiler-version} + + + org.mortbay.jetty + jsp-api-2.1-glassfish + ${jsp-2-1-version} + + + org.mortbay.jetty + jsp-2.1-glassfish + ${jsp-2-1-version} + + + ${assembly.directory}/lib/jsp-2.1 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + src/main/assembly/jetty.xml + + + + + package + + single + + + + + + + + + org.eclipse.jetty + jetty-deploy + ${project.version} + + + org.eclipse.jetty + jetty-rewrite + ${project.version} + + + org.eclipse.jetty + jetty-ajp + ${project.version} + + + org.eclipse.jetty + jetty-annotations + ${project.version} + + + org.eclipse.jetty + jetty-test-webapp + war + ${project.version} + + + org.eclipse.jetty + jetty-jmx + ${project.version} + + + org.eclipse.jetty + jetty-jndi + ${project.version} + + + org.eclipse.jetty + jetty-plus + ${project.version} + + + org.eclipse.jetty + jetty-client + ${project.version} + + + org.eclipse.jetty + jetty-start + ${project.version} + + + diff --git a/jetty-assembly/src/main/assembly/jetty-src.xml b/jetty-assembly/src/main/assembly/jetty-src.xml new file mode 100644 index 00000000000..b1b6bd8ca82 --- /dev/null +++ b/jetty-assembly/src/main/assembly/jetty-src.xml @@ -0,0 +1,21 @@ + + src + + tar.gz + tar.bz2 + zip + + + + ${basedir}/target/dist-src + + + ** + + + **/target/** + **/.svn/** + + + + diff --git a/jetty-assembly/src/main/assembly/jetty.xml b/jetty-assembly/src/main/assembly/jetty.xml new file mode 100644 index 00000000000..43b7dc727f8 --- /dev/null +++ b/jetty-assembly/src/main/assembly/jetty.xml @@ -0,0 +1,19 @@ + + + tar.gz + tar.bz2 + zip + + + + ${assembly.directory} + + + ** + + + *-config.jar + + + + \ No newline at end of file diff --git a/jetty-assembly/src/main/assembly/site-component.xml b/jetty-assembly/src/main/assembly/site-component.xml new file mode 100644 index 00000000000..c87eab9547d --- /dev/null +++ b/jetty-assembly/src/main/assembly/site-component.xml @@ -0,0 +1,18 @@ + + site-component + + jar + + + + ${basedir}/target/dist-src + jetty-distribution-${version}-site-component + + target/site/** + README** + VERSION** + LICENSES/** + + + + \ No newline at end of file diff --git a/jetty-assembly/src/main/resources/bin/build_release_bundles.sh b/jetty-assembly/src/main/resources/bin/build_release_bundles.sh new file mode 100755 index 00000000000..a9a2187026c --- /dev/null +++ b/jetty-assembly/src/main/resources/bin/build_release_bundles.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +[ $# -eq 1 ] || { echo "Usage - $0 jetty-dir" >&2 ; exit 1 ; } + +cd $1 +D=$(pwd) +N=$(basename $D) +D=$(dirname $D) +cd $D + + find $N -type f |\ + egrep -v /\\.svn |\ + egrep -v /target |\ + egrep -v /\\. |\ + egrep -v $N/start.jar |\ + egrep -v $N/lib |\ + egrep -v $N/logs |\ + egrep -v $N/webapp |\ + egrep -v $N/javadoc |\ + xargs zip $D/$N-src.zip $N/logs + + find $N -type f |\ + egrep -v /\\.svn |\ + egrep -v /target |\ + egrep -v /\\. |\ + egrep -v $N/logs |\ + xargs zip $D/$N.zip $N/logs diff --git a/jetty-assembly/src/main/resources/bin/jetty-xinetd.sh b/jetty-assembly/src/main/resources/bin/jetty-xinetd.sh new file mode 100755 index 00000000000..f74b932d659 --- /dev/null +++ b/jetty-assembly/src/main/resources/bin/jetty-xinetd.sh @@ -0,0 +1,14 @@ +#!/bin/bash + + +# look for JETTY_HOME +if [ -z "$JETTY_HOME" ] +then + JETTY_HOME_1=`dirname "$0"` + JETTY_HOME_1=`dirname "$JETTY_HOME_1"` + JETTY_HOME=${JETTY_HOME_1} +fi + +cd $JETTY_HOME +exec /usr/bin/java -Djetty.port=8088 -jar start.jar etc/jetty.xml etc/jetty-xinetd.xml + diff --git a/jetty-assembly/src/main/resources/bin/jetty.sh b/jetty-assembly/src/main/resources/bin/jetty.sh new file mode 100755 index 00000000000..42216dc7df0 --- /dev/null +++ b/jetty-assembly/src/main/resources/bin/jetty.sh @@ -0,0 +1,656 @@ +#!/bin/bash +# +# Startup script for jetty under *nix systems (it works under NT/cygwin too). +# +# Configuration files +# +# /etc/default/jetty +# If it exists, this is read at the start of script. It may perform any +# sequence of shell commands, like setting relevant environment variables. +# +# $HOME/.jettyrc +# If it exists, this is read at the start of script. It may perform any +# sequence of shell commands, like setting relevant environment variables. +# +# /etc/jetty.conf +# If found, and no configurations were given on the command line, +# the file will be used as this script's configuration. +# Each line in the file may contain: +# - A comment denoted by the pound (#) sign as first non-blank character. +# - The path to a regular file, which will be passed to jetty as a +# config.xml file. +# - The path to a directory. Each *.xml file in the directory will be +# passed to jetty as a config.xml file. +# +# The files will be checked for existence before being passed to jetty. +# +# $JETTY_HOME/etc/jetty.xml +# If found, used as this script's configuration file, but only if +# /etc/jetty.conf was not present. See above. +# +# Configuration variables +# +# JAVA_HOME +# Home of Java installation. +# +# JAVA +# Command to invoke Java. If not set, $JAVA_HOME/bin/java will be +# used. +# +# JAVA_OPTIONS +# Extra options to pass to the JVM +# +# JETTY_HOME +# Where Jetty is installed. If not set, the script will try go +# guess it by first looking at the invocation path for the script, +# and then by looking in standard locations as $HOME/opt/jetty +# and /opt/jetty. The java system property "jetty.home" will be +# set to this value for use by configure.xml files, f.e.: +# +# /webapps/jetty.war +# +# JETTY_PORT +# Override the default port for Jetty servers. If not set then the +# default value in the xml configuration file will be used. The java +# system property "jetty.port" will be set to this value for use in +# configure.xml files. For example, the following idiom is widely +# used in the demo config files to respect this property in Listener +# configuration elements: +# +# +# +# Note: that the config file could ignore this property simply by saying: +# +# 8080 +# +# JETTY_RUN +# Where the jetty.pid file should be stored. It defaults to the +# first available of /var/run, /usr/var/run, and /tmp if not set. +# +# JETTY_PID +# The Jetty PID file, defaults to $JETTY_RUN/jetty.pid +# +# JETTY_ARGS +# The default arguments to pass to jetty. +# +# JETTY_USER +# if set, then used as a username to run the server as +# + +usage() +{ + echo "Usage: $0 {start|stop|run|restart|check|supervise} [ CONFIGS ... ] " + exit 1 +} + +[ $# -gt 0 ] || usage + + +################################################## +# Some utility functions +################################################## +findDirectory() +{ + OP=$1 + shift + for L in $* ; do + [ $OP $L ] || continue + echo $L + break + done +} + +running() +{ + [ -f $1 ] || return 1 + PID=$(cat $1) + ps -p $PID >/dev/null 2>/dev/null || return 1 + return 0 +} + + + + + + + +################################################## +# Get the action & configs +################################################## + +ACTION=$1 +shift +ARGS="$*" +CONFIGS="" +NO_START=0 + +################################################## +# See if there's a default configuration file +################################################## +if [ -f /etc/default/jetty6 ] ; then + . /etc/default/jetty6 +elif [ -f /etc/default/jetty ] ; then + . /etc/default/jetty +fi + + +################################################## +# See if there's a user-specific configuration file +################################################## +if [ -f $HOME/.jettyrc ] ; then + . $HOME/.jettyrc +fi + +################################################## +# Set tmp if not already set. +################################################## + +if [ -z "$TMP" ] +then + TMP=/tmp +fi + +################################################## +# Jetty's hallmark +################################################## +JETTY_INSTALL_TRACE_FILE="etc/jetty.xml" +TMPJ=$TMP/j$$ + + +################################################## +# Try to determine JETTY_HOME if not set +################################################## +if [ -z "$JETTY_HOME" ] +then + JETTY_HOME_1=`dirname "$0"` + JETTY_HOME_1=`dirname "$JETTY_HOME_1"` + if [ -f "${JETTY_HOME_1}/${JETTY_INSTALL_TRACE_FILE}" ] ; + then + JETTY_HOME=${JETTY_HOME_1} + fi +fi + + +################################################## +# if no JETTY_HOME, search likely locations. +################################################## +if [ "$JETTY_HOME" = "" ] ; then + STANDARD_LOCATIONS=" \ + /usr/share \ + /usr/share/java \ + $HOME \ + $HOME/src \ + ${HOME}/opt/ \ + /opt \ + /java \ + /usr/local \ + /usr/local/share \ + /usr/local/share/java \ + /home \ + " + JETTY_DIR_NAMES=" \ + jetty-6 \ + jetty6 \ + jetty-6.* \ + jetty \ + Jetty-6 \ + Jetty6 \ + Jetty-6.* \ + Jetty \ + " + + JETTY_HOME= + for L in $STANDARD_LOCATIONS + do + for N in $JETTY_DIR_NAMES + do + if [ -d $L/$N ] && [ -f "$L/${N}/${JETTY_INSTALL_TRACE_FILE}" ] ; + then + JETTY_HOME="$L/$N" + fi + done + [ ! -z "$JETTY_HOME" ] && break + done +fi + + +################################################## +# No JETTY_HOME yet? We're out of luck! +################################################## +if [ -z "$JETTY_HOME" ] ; then + echo "** ERROR: JETTY_HOME not set, you need to set it or install in a standard location" + exit 1 +fi +cd $JETTY_HOME +JETTY_HOME=`pwd` + +##################################################### +# Check that jetty is where we think it is +##################################################### +if [ ! -r $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE ] +then + echo "** ERROR: Oops! Jetty doesn't appear to be installed in $JETTY_HOME" + echo "** ERROR: $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE is not readable!" + exit 1 +fi + + +########################################################### +# Get the list of config.xml files from the command line. +########################################################### +if [ ! -z "$ARGS" ] +then + for A in $ARGS + do + if [ -f $A ] + then + CONF="$A" + elif [ -f $JETTY_HOME/etc/$A ] + then + CONF="$JETTY_HOME/etc/$A" + elif [ -f ${A}.xml ] + then + CONF="${A}.xml" + elif [ -f $JETTY_HOME/etc/${A}.xml ] + then + CONF="$JETTY_HOME/etc/${A}.xml" + else + echo "** ERROR: Cannot find configuration '$A' specified in the command line." + exit 1 + fi + if [ ! -r $CONF ] + then + echo "** ERROR: Cannot read configuration '$A' specified in the command line." + exit 1 + fi + CONFIGS="$CONFIGS $CONF" + done +fi + + +################################################## +# Try to find this script's configuration file, +# but only if no configurations were given on the +# command line. +################################################## +if [ -z "$JETTY_CONF" ] +then + if [ -f /etc/jetty.conf ] + then + JETTY_CONF=/etc/jetty.conf + elif [ -f "${JETTY_HOME}/etc/jetty.conf" ] + then + JETTY_CONF="${JETTY_HOME}/etc/jetty.conf" + fi +fi + +################################################## +# Read the configuration file if one exists +################################################## +CONFIG_LINES= +if [ -z "$CONFIGS" ] && [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ] +then + CONFIG_LINES=`cat $JETTY_CONF | grep -v "^[:space:]*#" | tr "\n" " "` +fi + +################################################## +# Get the list of config.xml files from jetty.conf +################################################## +if [ ! -z "${CONFIG_LINES}" ] +then + for CONF in ${CONFIG_LINES} + do + if [ ! -r "$CONF" ] + then + echo "** WARNING: Cannot read '$CONF' specified in '$JETTY_CONF'" + elif [ -f "$CONF" ] + then + # assume it's a configure.xml file + CONFIGS="$CONFIGS $CONF" + elif [ -d "$CONF" ] + then + # assume it's a directory with configure.xml files + # for example: /etc/jetty.d/ + # sort the files before adding them to the list of CONFIGS + XML_FILES=`ls ${CONF}/*.xml | sort | tr "\n" " "` + for FILE in ${XML_FILES} + do + if [ -r "$FILE" ] && [ -f "$FILE" ] + then + CONFIGS="$CONFIGS $FILE" + else + echo "** WARNING: Cannot read '$FILE' specified in '$JETTY_CONF'" + fi + done + else + echo "** WARNING: Don''t know what to do with '$CONF' specified in '$JETTY_CONF'" + fi + done +fi + +##################################################### +# Run the standard server if there's nothing else to run +##################################################### +if [ -z "$CONFIGS" ] +then + CONFIGS="${JETTY_HOME}/etc/jetty-logging.xml ${JETTY_HOME}/etc/jetty.xml" +fi + + +##################################################### +# Find a location for the pid file +##################################################### +if [ -z "$JETTY_RUN" ] +then + JETTY_RUN=`findDirectory -w /var/run /usr/var/run /tmp` +fi + +##################################################### +# Find a PID for the pid file +##################################################### +if [ -z "$JETTY_PID" ] +then + JETTY_PID="$JETTY_RUN/jetty.pid" +fi + + +################################################## +# Check for JAVA_HOME +################################################## +if [ -z "$JAVA_HOME" ] +then + # If a java runtime is not defined, search the following + # directories for a JVM and sort by version. Use the highest + # version number. + + # Java search path + JAVA_LOCATIONS="\ + /usr/java \ + /usr/bin \ + /usr/local/bin \ + /usr/local/java \ + /usr/local/jdk \ + /usr/local/jre \ + /usr/lib/jvm \ + /opt/java \ + /opt/jdk \ + /opt/jre \ + " + JAVA_NAMES="java jdk jre" + for N in $JAVA_NAMES ; do + for L in $JAVA_LOCATIONS ; do + [ -d $L ] || continue + find $L -name "$N" ! -type d | grep -v threads | while read J ; do + [ -x $J ] || continue + VERSION=`eval $J -version 2>&1` + [ $? = 0 ] || continue + VERSION=`expr "$VERSION" : '.*"\(1.[0-9\.]*\)["_]'` + [ "$VERSION" = "" ] && continue + expr $VERSION \< 1.2 >/dev/null && continue + echo $VERSION:$J + done + done + done | sort | tail -1 > $TMPJ + JAVA=`cat $TMPJ | cut -d: -f2` + JVERSION=`cat $TMPJ | cut -d: -f1` + + JAVA_HOME=`dirname $JAVA` + while [ ! -z "$JAVA_HOME" -a "$JAVA_HOME" != "/" -a ! -f "$JAVA_HOME/lib/tools.jar" ] ; do + JAVA_HOME=`dirname $JAVA_HOME` + done + [ "$JAVA_HOME" = "" ] && JAVA_HOME= + + echo "Found JAVA=$JAVA in JAVA_HOME=$JAVA_HOME" +fi + + +################################################## +# Determine which JVM of version >1.2 +# Try to use JAVA_HOME +################################################## +if [ "$JAVA" = "" -a "$JAVA_HOME" != "" ] +then + if [ ! -z "$JAVACMD" ] + then + JAVA="$JAVACMD" + else + [ -x $JAVA_HOME/bin/jre -a ! -d $JAVA_HOME/bin/jre ] && JAVA=$JAVA_HOME/bin/jre + [ -x $JAVA_HOME/bin/java -a ! -d $JAVA_HOME/bin/java ] && JAVA=$JAVA_HOME/bin/java + fi +fi + +if [ "$JAVA" = "" ] +then + echo "Cannot find a JRE or JDK. Please set JAVA_HOME to a >=1.2 JRE" 2>&2 + exit 1 +fi + +JAVA_VERSION=`expr "$($JAVA -version 2>&1 | head -1)" : '.*1\.\([0-9]\)'` + +##################################################### +# See if JETTY_PORT is defined +##################################################### +if [ "$JETTY_PORT" != "" ] +then + JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.port=$JETTY_PORT" +fi + +##################################################### +# See if JETTY_LOGS is defined +##################################################### +if [ "$JETTY_LOGS" != "" ] +then + JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.logs=$JETTY_LOGS" +fi + +##################################################### +# Are we running on Windows? Could be, with Cygwin/NT. +##################################################### +case "`uname`" in +CYGWIN*) PATH_SEPARATOR=";";; +*) PATH_SEPARATOR=":";; +esac + + +##################################################### +# Add jetty properties to Java VM options. +##################################################### +JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.home=$JETTY_HOME -Djava.io.tmpdir=$TMP" + +[ -f $JETTY_HOME/etc/start.config ] && JAVA_OPTIONS="-DSTART=$JETTY_HOME/etc/start.config $JAVA_OPTIONS" + +##################################################### +# This is how the Jetty server will be started +##################################################### + +JETTY_START=$JETTY_HOME/start.jar +[ ! -f $JETTY_START ] && JETTY_START=$JETTY_HOME/lib/start.jar + +RUN_ARGS="$JAVA_OPTIONS -jar $JETTY_START $JETTY_ARGS $CONFIGS" +RUN_CMD="$JAVA $RUN_ARGS" + +##################################################### +# Comment these out after you're happy with what +# the script is doing. +##################################################### +#echo "JETTY_HOME = $JETTY_HOME" +#echo "JETTY_CONF = $JETTY_CONF" +#echo "JETTY_RUN = $JETTY_RUN" +#echo "JETTY_PID = $JETTY_PID" +#echo "JETTY_ARGS = $JETTY_ARGS" +#echo "CONFIGS = $CONFIGS" +#echo "JAVA_OPTIONS = $JAVA_OPTIONS" +#echo "JAVA = $JAVA" + + +################################################## +# Do the action +################################################## +case "$ACTION" in + start) + echo -n "Starting Jetty: " + + if [ "$NO_START" = "1" ]; then + echo "Not starting jetty - NO_START=1 in /etc/default/jetty6"; + exit 0; + fi + + + if type start-stop-daemon > /dev/null 2>&1 + then + [ x$JETTY_USER = x ] && JETTY_USER=$(whoami) + [ $UID = 0 ] && CH_USER="-c $JETTY_USER" + if start-stop-daemon -S -p$JETTY_PID $CH_USER -d $JETTY_HOME -b -m -a $JAVA -- $RUN_ARGS + then + sleep 1 + if running $JETTY_PID + then + echo OK + else + echo FAILED + fi + fi + + else + + if [ -f $JETTY_PID ] + then + if running $JETTY_PID + then + echo "Already Running!!" + exit 1 + else + # dead pid file - remove + rm -f $JETTY_PID + fi + fi + + if [ x$JETTY_USER != x ] + then + touch $JETTY_PID + chown $JETTY_USER $JETTY_PID + su - $JETTY_USER -c " + $RUN_CMD & + PID=\$! + disown \$PID + echo \$PID > $JETTY_PID" + else + $RUN_CMD & + PID=$! + disown $PID + echo $PID > $JETTY_PID + fi + + echo "STARTED Jetty `date`" + fi + + ;; + + stop) + echo -n "Stopping Jetty: " + if type start-stop-daemon > /dev/null 2>&1; then + start-stop-daemon -K -p $JETTY_PID -d $JETTY_HOME -a $JAVA -s HUP + sleep 1 + if running $JETTY_PID + then + sleep 3 + if running $JETTY_PID + then + sleep 30 + if running $JETTY_PID + then + start-stop-daemon -K -p $JETTY_PID -d $JETTY_HOME -a $JAVA -s KILL + fi + fi + fi + + rm -f $JETTY_PID + echo OK + else + PID=`cat $JETTY_PID 2>/dev/null` + TIMEOUT=30 + while running $JETTY_PID && [ $TIMEOUT -gt 0 ] + do + kill $PID 2>/dev/null + sleep 1 + let TIMEOUT=$TIMEOUT-1 + done + + [ $TIMEOUT -gt 0 ] || kill -9 $PID 2>/dev/null + + rm -f $JETTY_PID + echo OK + fi + ;; + + restart) + JETTY_SH=$0 + if [ ! -f $JETTY_SH ]; then + if [ ! -f $JETTY_HOME/bin/jetty.sh ]; then + echo "$JETTY_HOME/bin/jetty.sh does not exist." + exit 1 + fi + JETTY_SH=$JETTY_HOME/bin/jetty.sh + fi + $JETTY_SH stop $* + sleep 5 + $JETTY_SH start $* + ;; + + supervise) + # + # Under control of daemontools supervise monitor which + # handles restarts and shutdowns via the svc program. + # + exec $RUN_CMD + ;; + + run|demo) + echo "Running Jetty: " + + if [ -f $JETTY_PID ] + then + if running $JETTY_PID + then + echo "Already Running!!" + exit 1 + else + # dead pid file - remove + rm -f $JETTY_PID + fi + fi + + exec $RUN_CMD + ;; + + check) + echo "Checking arguments to Jetty: " + echo "JETTY_HOME = $JETTY_HOME" + echo "JETTY_CONF = $JETTY_CONF" + echo "JETTY_RUN = $JETTY_RUN" + echo "JETTY_PID = $JETTY_PID" + echo "JETTY_PORT = $JETTY_PORT" + echo "JETTY_LOGS = $JETTY_LOGS" + echo "CONFIGS = $CONFIGS" + echo "JAVA_OPTIONS = $JAVA_OPTIONS" + echo "JAVA = $JAVA" + echo "CLASSPATH = $CLASSPATH" + echo "RUN_CMD = $RUN_CMD" + echo + + if [ -f $JETTY_RUN/jetty.pid ] + then + echo "Jetty running pid="`cat $JETTY_RUN/jetty.pid` + exit 0 + fi + exit 1 + ;; + +*) + usage + ;; +esac + +exit 0 + + diff --git a/jetty-assembly/src/main/resources/contexts-available/README.TXT b/jetty-assembly/src/main/resources/contexts-available/README.TXT new file mode 100644 index 00000000000..7fa2ac54dae --- /dev/null +++ b/jetty-assembly/src/main/resources/contexts-available/README.TXT @@ -0,0 +1,3 @@ + +This directory contains example contexts that may be deployed by +moving/copying/linking them to the ../contexts directory. diff --git a/jetty-assembly/src/main/resources/contexts/README.TXT b/jetty-assembly/src/main/resources/contexts/README.TXT new file mode 100644 index 00000000000..36f67d70fa6 --- /dev/null +++ b/jetty-assembly/src/main/resources/contexts/README.TXT @@ -0,0 +1,15 @@ + +This directory is scanned by the ContextDeployer instance +configured by the standard $JETTY_HOME/etc/jetty.xml configuration. + +It should contain XmlConfiguration files that describe individual +contexts to be deployed to the server. This directory is scanned +for additions, removals and updates for hot deployment. + +Frequenty the context configuration files here will reference +war files or directories from $JETTY_HOME/webapps. Care must be +taken to avoid a WebAppDeployer deploying duplicates of such +webapplications. + +The directory ../contexts-available contains more example contexts +that may be deployed by being copied here. diff --git a/jetty-assembly/src/main/resources/contexts/javadoc.xml b/jetty-assembly/src/main/resources/contexts/javadoc.xml new file mode 100644 index 00000000000..342e6b99578 --- /dev/null +++ b/jetty-assembly/src/main/resources/contexts/javadoc.xml @@ -0,0 +1,28 @@ + + + + + + + Configure javadoc.xml + /javadoc + /javadoc/ + + + + + index.html + contents.html + + + max-age=3600,public + + + + + diff --git a/jetty-assembly/src/main/resources/javadoc/contents.html b/jetty-assembly/src/main/resources/javadoc/contents.html new file mode 100644 index 00000000000..f1dbfdd9b44 --- /dev/null +++ b/jetty-assembly/src/main/resources/javadoc/contents.html @@ -0,0 +1,7 @@ +

Jetty Javadoc - NOT BUILT!

+ +Please run build the project-website module + + + + diff --git a/jetty-assembly/src/main/resources/resources/log4j.properties b/jetty-assembly/src/main/resources/resources/log4j.properties new file mode 100644 index 00000000000..8899c004be3 --- /dev/null +++ b/jetty-assembly/src/main/resources/resources/log4j.properties @@ -0,0 +1,9 @@ + +# This is not needed by Jetty - but it helps with many web apps. + +log4j.rootLogger=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n + diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml new file mode 100644 index 00000000000..3835056f3a3 --- /dev/null +++ b/jetty-client/pom.xml @@ -0,0 +1,85 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-client + Jetty :: Asynchronous HTTP Client + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + J2SE-1.4 + org.eclipse.jetty.client;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + + + org.eclipse.jetty + jetty-http + ${project.version} + + + org.eclipse.jetty + jetty-server + ${project.version} + test + + + org.eclipse.jetty + jetty-security + ${project.version} + test + + + junit + junit + test + + + org.eclipse.jetty + jetty-servlet + ${project.version} + test + + + + diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java new file mode 100644 index 00000000000..a5576318b59 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java @@ -0,0 +1,86 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.net.InetSocketAddress; + +/** + * @version $Revision: 4135 $ $Date: 2008-12-02 11:57:07 +0100 (Tue, 02 Dec 2008) $ + */ +public class Address +{ + private final String host; + private final int port; + + public static Address from(String hostAndPort) + { + String host; + int port; + int colon = hostAndPort.indexOf(':'); + if (colon >= 0) + { + host = hostAndPort.substring(0, colon); + port = Integer.parseInt(hostAndPort.substring(colon + 1)); + } + else + { + host = hostAndPort; + port = 0; + } + return new Address(host, port); + } + + public Address(String host, int port) + { + this.host = host.trim(); + this.port = port; + } + + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Address that = (Address)obj; + if (!host.equals(that.host)) return false; + return port == that.port; + } + + public int hashCode() + { + int result = host.hashCode(); + result = 31 * result + port; + return result; + } + + public String getHost() + { + return host; + } + + public int getPort() + { + return port; + } + + public InetSocketAddress toSocketAddress() + { + return new InetSocketAddress(getHost(), getPort()); + } + + @Override + public String toString() + { + return host + ":" + port; + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/CachedExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/CachedExchange.java new file mode 100644 index 00000000000..cc36a413db3 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/CachedExchange.java @@ -0,0 +1,68 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.client; + +import java.io.IOException; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.io.Buffer; + +/** + * An exchange that caches response status and fields for later use. + * + * + * + */ +public class CachedExchange extends HttpExchange +{ + int _responseStatus; + HttpFields _responseFields; + + public CachedExchange(boolean cacheFields) + { + if (cacheFields) + _responseFields = new HttpFields(); + } + + /* ------------------------------------------------------------ */ + public int getResponseStatus() + { + if (_status < HttpExchange.STATUS_PARSING_HEADERS) + throw new IllegalStateException("Response not received"); + return _responseStatus; + } + + /* ------------------------------------------------------------ */ + public HttpFields getResponseFields() + { + if (_status < HttpExchange.STATUS_PARSING_CONTENT) + throw new IllegalStateException("Headers not complete"); + return _responseFields; + } + + /* ------------------------------------------------------------ */ + protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + _responseStatus = status; + super.onResponseStatus(version,status,reason); + } + + /* ------------------------------------------------------------ */ + protected void onResponseHeader(Buffer name, Buffer value) throws IOException + { + if (_responseFields != null) + _responseFields.add(name,value); + super.onResponseHeader(name,value); + } + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ContentExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ContentExchange.java new file mode 100644 index 00000000000..5e11073c7fb --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ContentExchange.java @@ -0,0 +1,126 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.util.StringUtil; + +/** + * A CachedExchange that retains all content for later use. + * + */ +public class ContentExchange extends CachedExchange +{ + int _contentLength = 1024; + String _encoding = "utf-8"; + ByteArrayOutputStream _responseContent; + + File _fileForUpload; + + public ContentExchange() + { + super(false); + } + + /* ------------------------------------------------------------ */ + public ContentExchange(boolean cacheFields) + { + super(cacheFields); + } + + /* ------------------------------------------------------------ */ + public String getResponseContent() throws UnsupportedEncodingException + { + if (_responseContent != null) + { + return _responseContent.toString(_encoding); + } + return null; + } + + /* ------------------------------------------------------------ */ + protected void onResponseHeader(Buffer name, Buffer value) throws IOException + { + super.onResponseHeader(name,value); + int header = HttpHeaders.CACHE.getOrdinal(value); + switch (header) + { + case HttpHeaders.CONTENT_LENGTH_ORDINAL: + _contentLength = BufferUtil.toInt(value); + break; + case HttpHeaders.CONTENT_TYPE_ORDINAL: + + String mime = StringUtil.asciiToLowerCase(value.toString()); + int i = mime.indexOf("charset="); + if (i > 0) + { + mime = mime.substring(i + 8); + i = mime.indexOf(';'); + if (i > 0) + mime = mime.substring(0,i); + } + if (mime != null && mime.length() > 0) + _encoding = mime; + break; + } + } + + protected void onResponseContent(Buffer content) throws IOException + { + super.onResponseContent( content ); + if (_responseContent == null) + _responseContent = new ByteArrayOutputStream(_contentLength); + content.writeTo(_responseContent); + } + + protected void onRetry() throws IOException + { + if ( _fileForUpload != null ) + { + _requestContent = null; + _requestContentSource = getInputStream(); + } + else if ( _requestContentSource != null ) + { + throw new IOException("Unsupported Retry attempt, no registered file for upload."); + } + + super.onRetry(); + } + + private InputStream getInputStream() throws IOException + { + return new FileInputStream( _fileForUpload ); + } + + public File getFileForUpload() + { + return _fileForUpload; + } + + public void setFileForUpload(File fileForUpload) throws IOException + { + this._fileForUpload = fileForUpload; + _requestContentSource = getInputStream(); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java new file mode 100644 index 00000000000..9f5788d3989 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -0,0 +1,699 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.io.InputStream; +import java.net.UnknownHostException; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import org.eclipse.jetty.client.security.Authorization; +import org.eclipse.jetty.client.security.RealmResolver; +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.io.AbstractBuffers; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ThreadPool; +import org.eclipse.jetty.util.thread.Timeout; + +/** + * Http Client. + *

+ * HttpClient is the main active component of the client API implementation. + * It is the opposite of the Connectors in standard Jetty, in that it listens + * for responses rather than requests. Just like the connectors, there is a + * blocking socket version and a non-blocking NIO version (implemented as nested classes + * selected by {@link #setConnectorType(int)}). + *

+ * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method + * to send a request. The exchange contains both the headers and content (source) of the request + * plus the callbacks to handle responses. A HttpClient can have many exchanges outstanding + * and they may be queued on the {@link HttpDestination} waiting for a {@link HttpConnection}, + * queued in the {@link HttpConnection} waiting to be transmitted or pipelined on the actual + * TCP/IP connection waiting for a response. + *

+ * The {@link HttpDestination} class is an aggregation of {@link HttpConnection}s for the + * same host, port and protocol. A destination may limit the number of connections + * open and they provide a pool of open connections that may be reused. Connections may also + * be allocated from a destination, so that multiple request sources are not multiplexed + * over the same connection. + * + * + * + * + * @see {@link HttpExchange} + * @see {@link HttpDestination} + */ +public class HttpClient extends AbstractBuffers implements Attributes +{ + public static final int CONNECTOR_SOCKET = 0; + public static final int CONNECTOR_SELECT_CHANNEL = 2; + + private int _connectorType = CONNECTOR_SELECT_CHANNEL; + private boolean _useDirectBuffers = true; + private int _maxConnectionsPerAddress = 32; + private Map _destinations = new HashMap(); + ThreadPool _threadPool; + Connector _connector; + private long _idleTimeout = 20000; + private long _timeout = 320000; + int _soTimeout = 10000; + private Timeout _timeoutQ = new Timeout(); + private Address _proxy; + private Authorization _proxyAuthentication; + private Set _noProxy; + private int _maxRetries = 3; + private LinkedList _registeredListeners; + + // TODO clean up and add getters/setters to some of this maybe + private String _keyStoreLocation; + private String _keyStoreType = "JKS"; + private String _keyStorePassword; + private String _keyManagerAlgorithm = "SunX509"; + private String _keyManagerPassword; + private String _trustStoreLocation; + private String _trustStoreType = "JKS"; + private String _trustStorePassword; + private String _trustManagerAlgorithm = "SunX509"; + + private SSLContext _sslContext; + + private String _protocol = "TLS"; + private String _provider; + private String _secureRandomAlgorithm; + + private RealmResolver _realmResolver; + + private AttributesMap _attributes=new AttributesMap(); + + /* ------------------------------------------------------------------------------- */ + public void dump() throws IOException + { + for (Map.Entry entry : _destinations.entrySet()) + { + System.err.println("\n" + entry.getKey() + ":"); + entry.getValue().dump(); + } + } + + /* ------------------------------------------------------------------------------- */ + public void send(HttpExchange exchange) throws IOException + { + boolean ssl = HttpSchemes.HTTPS_BUFFER.equalsIgnoreCase(exchange.getScheme()); + exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_CONNECTION); + HttpDestination destination = getDestination(exchange.getAddress(), ssl); + destination.send(exchange); + } + + /* ------------------------------------------------------------ */ + /** + * @return the threadPool + */ + public ThreadPool getThreadPool() + { + return _threadPool; + } + + /* ------------------------------------------------------------ */ + /** + * @param threadPool the threadPool to set + */ + public void setThreadPool(ThreadPool threadPool) + { + _threadPool = threadPool; + } + + + /* ------------------------------------------------------------ */ + /** + * @param name + * @return Attribute associated with client + */ + public Object getAttribute(String name) + { + return _attributes.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @return names of attributes associated with client + */ + public Enumeration getAttributeNames() + { + return _attributes.getAttributeNames(); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + */ + public void removeAttribute(String name) + { + _attributes.removeAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * Set an attribute on the HttpClient. + * Attributes are not used by the client, but are provided for + * so that users of a shared HttpClient may share other structures. + * @param name + * @param attribute + */ + public void setAttribute(String name, Object attribute) + { + _attributes.setAttribute(name,attribute); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @return + */ + public void clearAttributes() + { + _attributes.clearAttributes(); + } + + /* ------------------------------------------------------------------------------- */ + public HttpDestination getDestination(Address remote, boolean ssl) throws UnknownHostException, IOException + { + if (remote == null) + throw new UnknownHostException("Remote socket address cannot be null."); + + synchronized (_destinations) + { + HttpDestination destination = _destinations.get(remote); + if (destination == null) + { + destination = new HttpDestination(this, remote, ssl, _maxConnectionsPerAddress); + if (_proxy != null && (_noProxy == null || !_noProxy.contains(remote.getHost()))) + { + destination.setProxy(_proxy); + if (_proxyAuthentication != null) + destination.setProxyAuthentication(_proxyAuthentication); + } + _destinations.put(remote, destination); + } + return destination; + } + } + + /* ------------------------------------------------------------ */ + public void schedule(Timeout.Task task) + { + _timeoutQ.schedule(task); + } + + /* ------------------------------------------------------------ */ + public void cancel(Timeout.Task task) + { + task.cancel(); + } + + /* ------------------------------------------------------------ */ + /** + * Get whether the connector can use direct NIO buffers. + */ + public boolean getUseDirectBuffers() + { + return _useDirectBuffers; + } + + /* ------------------------------------------------------------ */ + public void setRealmResolver(RealmResolver resolver) + { + _realmResolver = resolver; + } + + /* ------------------------------------------------------------ */ + /** + * returns the SecurityRealmResolver registered with the HttpClient or null + * + * @return + */ + public RealmResolver getRealmResolver() + { + return _realmResolver; + } + + /* ------------------------------------------------------------ */ + public boolean hasRealms() + { + return _realmResolver == null ? false : true; + } + + + /** + * Registers a listener that can listen to the stream of execution between the client and the + * server and influence events. Sequential calls to the method wrapper sequentially wrap the preceeding + * listener in a delegation model. + *

+ * NOTE: the SecurityListener is a special listener which doesn't need to be added via this + * mechanic, if you register security realms then it will automatically be added as the top listener of the + * delegation stack. + * + * @param listenerClass + */ + public void registerListener(String listenerClass) + { + if (_registeredListeners == null) + { + _registeredListeners = new LinkedList(); + } + _registeredListeners.add(listenerClass); + } + + public LinkedList getRegisteredListeners() + { + return _registeredListeners; + } + + + /* ------------------------------------------------------------ */ + /** + * Set to use NIO direct buffers. + * + * @param direct If True (the default), the connector can use NIO direct + * buffers. Some JVMs have memory management issues (bugs) with + * direct buffers. + */ + public void setUseDirectBuffers(boolean direct) + { + _useDirectBuffers = direct; + } + + /* ------------------------------------------------------------ */ + /** + * Get the type of connector (socket, blocking or select) in use. + */ + public int getConnectorType() + { + return _connectorType; + } + + /* ------------------------------------------------------------ */ + public void setConnectorType(int connectorType) + { + this._connectorType = connectorType; + } + + /* ------------------------------------------------------------ */ + /** + * Create a new NIO buffer. If using direct buffers, it will create a direct + * NIO buffer, other than an indirect buffer. + */ + public Buffer newBuffer(int size) + { + if (_connectorType != CONNECTOR_SOCKET) + { + Buffer buf = null; + if (size==getHeaderBufferSize()) + buf=new IndirectNIOBuffer(size); + else if (_useDirectBuffers) + buf=new DirectNIOBuffer(size); + else + buf=new IndirectNIOBuffer(size); + return buf; + } + else + { + return new ByteArrayBuffer(size); + } + } + + /* ------------------------------------------------------------ */ + public int getMaxConnectionsPerAddress() + { + return _maxConnectionsPerAddress; + } + + /* ------------------------------------------------------------ */ + public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress) + { + _maxConnectionsPerAddress = maxConnectionsPerAddress; + } + + /* ------------------------------------------------------------ */ + protected void doStart() throws Exception + { + super.doStart(); + + _timeoutQ.setNow(); + _timeoutQ.setDuration(_timeout); + + if (_threadPool == null) + { + QueuedThreadPool pool = new QueuedThreadPool(); + pool.setMaxThreads(16); + pool.setDaemon(true); + pool.setName("HttpClient"); + _threadPool = pool; + } + + if (_threadPool instanceof LifeCycle) + { + ((LifeCycle)_threadPool).start(); + } + + + if (_connectorType == CONNECTOR_SELECT_CHANNEL) + { + + _connector = new SelectConnector(this); + } + else + { + _connector = new SocketConnector(this); + } + _connector.start(); + + _threadPool.dispatch(new Runnable() + { + public void run() + { + while (isRunning()) + { + _timeoutQ.setNow(); + _timeoutQ.tick(); + try + { + Thread.sleep(1000); + } + catch (InterruptedException e) + { + } + } + } + }); + + } + + /* ------------------------------------------------------------ */ + protected void doStop() throws Exception + { + _connector.stop(); + _connector = null; + if (_threadPool instanceof LifeCycle) + { + ((LifeCycle)_threadPool).stop(); + } + for (HttpDestination destination : _destinations.values()) + { + destination.close(); + } + + _timeoutQ.cancelAll(); + super.doStop(); + } + + /* ------------------------------------------------------------ */ + interface Connector extends LifeCycle + { + public void startConnection(HttpDestination destination) throws IOException; + + } + + /** + * if a keystore location has been provided then client will attempt to use it as the keystore, + * otherwise we simply ignore certificates and run with a loose ssl context. + * + * @return + * @throws IOException + */ + protected SSLContext getSSLContext() throws IOException + { + if (_sslContext == null) + { + if (_keyStoreLocation == null) + { + _sslContext = getLooseSSLContext(); + } + else + { + _sslContext = getStrictSSLContext(); + } + } + return _sslContext; + } + + protected SSLContext getStrictSSLContext() throws IOException + { + + try + { + if (_trustStoreLocation == null) + { + _trustStoreLocation = _keyStoreLocation; + _trustStoreType = _keyStoreType; + } + + KeyManager[] keyManagers = null; + InputStream keystoreInputStream = null; + + keystoreInputStream = Resource.newResource(_keyStoreLocation).getInputStream(); + KeyStore keyStore = KeyStore.getInstance(_keyStoreType); + keyStore.load(keystoreInputStream, _keyStorePassword == null ? null : _keyStorePassword.toString().toCharArray()); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerAlgorithm); + keyManagerFactory.init(keyStore, _keyManagerPassword == null ? null : _keyManagerPassword.toString().toCharArray()); + keyManagers = keyManagerFactory.getKeyManagers(); + + TrustManager[] trustManagers = null; + InputStream truststoreInputStream = null; + + truststoreInputStream = Resource.newResource(_trustStoreLocation).getInputStream(); + KeyStore trustStore = KeyStore.getInstance(_trustStoreType); + trustStore.load(truststoreInputStream, _trustStorePassword == null ? null : _trustStorePassword.toString().toCharArray()); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerAlgorithm); + trustManagerFactory.init(trustStore); + trustManagers = trustManagerFactory.getTrustManagers(); + + SecureRandom secureRandom = _secureRandomAlgorithm == null ? null : SecureRandom.getInstance(_secureRandomAlgorithm); + SSLContext context = _provider == null ? SSLContext.getInstance(_protocol) : SSLContext.getInstance(_protocol, _provider); + context.init(keyManagers, trustManagers, secureRandom); + return context; + } + catch (Exception e) + { + e.printStackTrace(); + throw new IOException("error generating ssl context for " + _keyStoreLocation + " " + e.getMessage()); + } + } + + protected SSLContext getLooseSSLContext() throws IOException + { + + // Create a trust manager that does not validate certificate + // chains + TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() + { + public java.security.cert.X509Certificate[] getAcceptedIssuers() + { + return null; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) + { + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) + { + } + }}; + + HostnameVerifier hostnameVerifier = new HostnameVerifier() + { + public boolean verify(String urlHostName, SSLSession session) + { + Log.warn("Warning: URL Host: " + urlHostName + " vs." + session.getPeerHost()); + return true; + } + }; + + // Install the all-trusting trust manager + try + { + // TODO real trust manager + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + return sslContext; + } + catch (Exception e) + { + throw new IOException("issue ignoring certs"); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return the period in milliseconds a {@link HttpConnection} can be idle for before it is closed. + */ + public long getIdleTimeout() + { + return _idleTimeout; + } + + /* ------------------------------------------------------------ */ + /** + * @param ms the period in milliseconds a {@link HttpConnection} can be idle for before it is closed. + */ + public void setIdleTimeout(long ms) + { + _idleTimeout = ms; + } + + /* ------------------------------------------------------------ */ + public int getSoTimeout() + { + return _soTimeout; + } + + /* ------------------------------------------------------------ */ + public void setSoTimeout(int so) + { + _soTimeout = so; + } + + /* ------------------------------------------------------------ */ + /** + * @return the period in ms that an exchange will wait for a response from the server. + */ + public long getTimeout() + { + return _timeout; + } + + /* ------------------------------------------------------------ */ + /** + * @param ms the period in ms that an exchange will wait for a response from the server. + */ + public void setTimeout(long ms) + { + _timeout = ms; + } + + /* ------------------------------------------------------------ */ + public Address getProxy() + { + return _proxy; + } + + /* ------------------------------------------------------------ */ + public void setProxy(Address proxy) + { + this._proxy = proxy; + } + + /* ------------------------------------------------------------ */ + public Authorization getProxyAuthentication() + { + return _proxyAuthentication; + } + + /* ------------------------------------------------------------ */ + public void setProxyAuthentication(Authorization authentication) + { + _proxyAuthentication = authentication; + } + + /* ------------------------------------------------------------ */ + public boolean isProxied() + { + return this._proxy != null; + } + + /* ------------------------------------------------------------ */ + public Set getNoProxy() + { + return _noProxy; + } + + /* ------------------------------------------------------------ */ + public void setNoProxy(Set noProxyAddresses) + { + _noProxy = noProxyAddresses; + } + + /* ------------------------------------------------------------ */ + public int maxRetries() + { + return _maxRetries; + } + + /* ------------------------------------------------------------ */ + public void setMaxRetries(int retries) + { + _maxRetries = retries; + } + + public String getTrustStoreLocation() + { + return _trustStoreLocation; + } + + public void setTrustStoreLocation(String trustStoreLocation) + { + this._trustStoreLocation = trustStoreLocation; + } + + public String getKeyStoreLocation() + { + return _keyStoreLocation; + } + + public void setKeyStoreLocation(String keyStoreLocation) + { + this._keyStoreLocation = keyStoreLocation; + } + + public void setKeyStorePassword(String _keyStorePassword) + { + this._keyStorePassword = _keyStorePassword; + } + + public void setKeyManagerPassword(String _keyManagerPassword) + { + this._keyManagerPassword = _keyManagerPassword; + } + + public void setTrustStorePassword(String _trustStorePassword) + { + this._trustStorePassword = _trustStorePassword; + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java new file mode 100644 index 00000000000..df1ef405c9c --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -0,0 +1,574 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; + +import org.eclipse.jetty.client.security.Authorization; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.ssl.SslSelectChannelEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.thread.Timeout; + + +/** + * + * + * + */ +public class HttpConnection implements Connection +{ + HttpDestination _destination; + EndPoint _endp; + HttpGenerator _generator; + HttpParser _parser; + boolean _http11 = true; + Buffer _connectionHeader; + Buffer _requestContentChunk; + long _last; + boolean _requestComplete; + public String _message; + public Throwable _throwable; + public boolean _reserved; + + /* The current exchange waiting for a response */ + volatile HttpExchange _exchange; + HttpExchange _pipeline; + + public void dump() throws IOException + { + System.err.println("endp=" + _endp + " " + _endp.isBufferingInput() + " " + _endp.isBufferingOutput()); + System.err.println("generator=" + _generator); + System.err.println("parser=" + _parser.getState() + " " + _parser.isMoreInBuffer()); + System.err.println("exchange=" + _exchange); + if (_endp instanceof SslSelectChannelEndPoint) + ((SslSelectChannelEndPoint)_endp).dump(); + } + + Timeout.Task _timeout = new Timeout.Task() + { + public void expired() + { + HttpExchange ex = null; + try + { + synchronized (HttpConnection.this) + { + ex = _exchange; + _exchange = null; + if (ex != null) + _destination.returnConnection(HttpConnection.this,true); + } + } + catch (Exception e) + { + Log.debug(e); + } + finally + { + try + { + _endp.close(); + } + catch (IOException e) + { + Log.ignore(e); + } + + if (ex!=null && ex.getStatus() < HttpExchange.STATUS_COMPLETED) + { + ex.setStatus(HttpExchange.STATUS_EXPIRED); + } + } + } + }; + + /* ------------------------------------------------------------ */ + HttpConnection(Buffers buffers, EndPoint endp, int hbs, int cbs) + { + _endp = endp; + _generator = new HttpGenerator(buffers,endp,hbs,cbs); + _parser = new HttpParser(buffers,endp,new Handler(),hbs,cbs); + } + + public void setReserved (boolean reserved) + { + _reserved = reserved; + } + + public boolean isReserved() + { + return _reserved; + } + + /* ------------------------------------------------------------ */ + public HttpDestination getDestination() + { + return _destination; + } + + /* ------------------------------------------------------------ */ + public void setDestination(HttpDestination destination) + { + _destination = destination; + } + + /* ------------------------------------------------------------ */ + public boolean send(HttpExchange ex) throws IOException + { + // _message = + // Thread.currentThread().getName()+": Generator instance="+_generator + // .hashCode()+" state= "+_generator.getState()+" _exchange="+_exchange; + _throwable = new Throwable(); + synchronized (this) + { + if (_exchange != null) + { + if (_pipeline != null) + throw new IllegalStateException(this + " PIPELINED!!! _exchange=" + _exchange); + _pipeline = ex; + return true; + } + + if (!_endp.isOpen()) + return false; + + ex.setStatus(HttpExchange.STATUS_WAITING_FOR_COMMIT); + _exchange = ex; + + if (_endp.isBlocking()) + this.notify(); + else + { + SelectChannelEndPoint scep = (SelectChannelEndPoint)_endp; + scep.scheduleWrite(); + } + + if (!_endp.isBlocking()) + _destination.getHttpClient().schedule(_timeout); + + return true; + } + } + + /* ------------------------------------------------------------ */ + public void handle() throws IOException + { + int no_progress = 0; + long flushed = 0; + + boolean failed = false; + while (_endp.isBufferingInput() || _endp.isOpen()) + { + synchronized (this) + { + while (_exchange == null) + { + if (_endp.isBlocking()) + { + try + { + this.wait(); + } + catch (InterruptedException e) + { + throw new InterruptedIOException(); + } + } + else + { + // Hopefully just space? + _parser.fill(); + _parser.skipCRLF(); + if (_parser.isMoreInBuffer()) + { + Log.warn("unexpected data"); + _endp.close(); + } + + return; + } + } + } + if (_exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT) + { + no_progress = 0; + commitRequest(); + } + + try + { + long io = 0; + _endp.flush(); + + if (_generator.isComplete()) + { + if (!_requestComplete) + { + _requestComplete = true; + _exchange.getEventListener().onRequestComplete(); + } + } + else + { + // Write as much of the request as possible + synchronized (this) + { + if (_exchange == null) + continue; + flushed = _generator.flushBuffer(); + io += flushed; + } + + if (!_generator.isComplete()) + { + InputStream in = _exchange.getRequestContentSource(); + if (in != null) + { + if (_requestContentChunk == null || _requestContentChunk.length() == 0) + { + _requestContentChunk = _exchange.getRequestContentChunk(); + if (_requestContentChunk != null) + _generator.addContent(_requestContentChunk,false); + else + _generator.complete(); + io += _generator.flushBuffer(); + } + } + else + _generator.complete(); + } + } + + + // If we are not ended then parse available + if (!_parser.isComplete() && _generator.isCommitted()) + { + long filled = _parser.parseAvailable(); + io += filled; + } + + if (io > 0) + no_progress = 0; + else if (no_progress++ >= 2 && !_endp.isBlocking()) + { + // SSL may need an extra flush as it may have made "no progress" while actually doing a handshake. + if (_endp instanceof SslSelectChannelEndPoint && !_generator.isComplete() && !_generator.isEmpty()) + { + if (_generator.flushBuffer()>0) + continue; + } + return; + } + } + catch (IOException e) + { + synchronized (this) + { + if (_exchange != null) + { + _exchange.getEventListener().onException(e); + _exchange.setStatus(HttpExchange.STATUS_EXCEPTED); + } + } + failed = true; + throw e; + } + finally + { + boolean complete = false; + boolean close = failed; // always close the connection on error + if (!failed) + { + // are we complete? + if (_generator.isComplete()) + { + if (!_requestComplete) + { + _requestComplete = true; + _exchange.getEventListener().onRequestComplete(); + } + + // we need to return the HttpConnection to a state that + // it can be reused or closed out + if (_parser.isComplete()) + { + _destination.getHttpClient().cancel(_timeout); + complete = true; + } + } + } + + if (complete || failed) + { + synchronized (this) + { + if (!close) + close = shouldClose(); + + reset(true); + no_progress = 0; + flushed = -1; + if (_exchange != null) + { + _exchange = null; + + if (_pipeline == null) + { + if (!isReserved()) + _destination.returnConnection(this,close); + if (close) + return; + } + else + { + if (close) + { + if (!isReserved()) + _destination.returnConnection(this,close); + _destination.send(_pipeline); + _pipeline = null; + return; + } + + HttpExchange ex = _pipeline; + _pipeline = null; + + send(ex); + } + } + } + } + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + synchronized (this) + { + return _exchange == null; + } + } + + /* ------------------------------------------------------------ */ + public EndPoint getEndPoint() + { + return _endp; + } + + /* ------------------------------------------------------------ */ + private void commitRequest() throws IOException + { + synchronized (this) + { + if (_exchange.getStatus() != HttpExchange.STATUS_WAITING_FOR_COMMIT) + throw new IllegalStateException(); + + _exchange.setStatus(HttpExchange.STATUS_SENDING_REQUEST); + _generator.setVersion(_exchange._version); + + String uri = _exchange._uri; + if (_destination.isProxied() && uri.startsWith("/")) + { + // TODO suppress port 80 or 443 + uri = (_destination.isSecure()?HttpSchemes.HTTPS:HttpSchemes.HTTP) + "://" + _destination.getAddress().getHost() + ":" + + _destination.getAddress().getPort() + uri; + Authorization auth = _destination.getProxyAuthentication(); + if (auth != null) + auth.setCredentials(_exchange); + } + + _generator.setRequest(_exchange._method,uri); + + if (_exchange._version >= HttpVersions.HTTP_1_1_ORDINAL) + { + if (!_exchange._requestFields.containsKey(HttpHeaders.HOST_BUFFER)) + _exchange._requestFields.add(HttpHeaders.HOST_BUFFER,_destination.getHostHeader()); + } + + if (_exchange._requestContent != null) + { + _exchange._requestFields.putLongField(HttpHeaders.CONTENT_LENGTH,_exchange._requestContent.length()); + _generator.completeHeader(_exchange._requestFields,false); + _generator.addContent(_exchange._requestContent,true); + } + else if (_exchange._requestContentSource != null) + { + _generator.completeHeader(_exchange._requestFields,false); + int available = _exchange._requestContentSource.available(); + if (available > 0) + { + // TODO deal with any known content length + + // TODO reuse this buffer! + byte[] buf = new byte[available]; + int length = _exchange._requestContentSource.read(buf); + _generator.addContent(new ByteArrayBuffer(buf,0,length),false); + } + } + else + { + _exchange._requestFields.remove(HttpHeaders.CONTENT_LENGTH); // TODO + // : + // should + // not + // be + // needed + _generator.completeHeader(_exchange._requestFields,true); + } + + _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE); + } + } + + /* ------------------------------------------------------------ */ + protected void reset(boolean returnBuffers) throws IOException + { + _requestComplete = false; + _connectionHeader = null; + _parser.reset(returnBuffers); + _generator.reset(returnBuffers); + _http11 = true; + } + + /* ------------------------------------------------------------ */ + private boolean shouldClose() + { + if (_connectionHeader!=null) + { + if (HttpHeaderValues.CLOSE_BUFFER.equals(_connectionHeader)) + return true; + if (HttpHeaderValues.KEEP_ALIVE_BUFFER.equals(_connectionHeader)) + return false; + } + return !_http11; + } + + /* ------------------------------------------------------------ */ + private class Handler extends HttpParser.EventHandler + { + @Override + public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException + { + // System.out.println( method.toString() + "///" + url.toString() + + // "///" + version.toString() ); + // TODO validate this is acceptable, the _connections = new LinkedList(); + private final ArrayList _idle = new ArrayList(); + private final HttpClient _client; + private final boolean _ssl; + private int _maxConnections; + private int _pendingConnections = 0; + private ArrayBlockingQueue _newQueue = new ArrayBlockingQueue(10, true); + private int _newConnection = 0; + private Address _proxy; + private Authorization _proxyAuthentication; + private PathMap _authorizations; + private List _cookies; + + public void dump() throws IOException + { + synchronized (this) + { + System.err.println(this); + System.err.println("connections=" + _connections.size()); + System.err.println("idle=" + _idle.size()); + System.err.println("pending=" + _pendingConnections); + for (HttpConnection c : _connections) + { + if (!c.isIdle()) + c.dump(); + } + } + } + + /* The queue of exchanged for this destination if connections are limited */ + private LinkedList _queue = new LinkedList(); + + /* ------------------------------------------------------------ */ + HttpDestination(HttpClient pool, Address address, boolean ssl, int maxConnections) + { + _client = pool; + _address = address; + _ssl = ssl; + _maxConnections = maxConnections; + String addressString = address.getHost(); + if (address.getPort() != (_ssl ? 443 : 80)) addressString += ":" + address.getPort(); + _hostHeader = new ByteArrayBuffer(addressString); + } + + /* ------------------------------------------------------------ */ + public Address getAddress() + { + return _address; + } + + /* ------------------------------------------------------------ */ + public Buffer getHostHeader() + { + return _hostHeader; + } + + /* ------------------------------------------------------------ */ + public HttpClient getHttpClient() + { + return _client; + } + + /* ------------------------------------------------------------ */ + public boolean isSecure() + { + return _ssl; + } + + /* ------------------------------------------------------------ */ + public void addAuthorization(String pathSpec, Authorization authorization) + { + synchronized (this) + { + if (_authorizations == null) + _authorizations = new PathMap(); + _authorizations.put(pathSpec, authorization); + } + + // TODO query and remove methods + } + + /* ------------------------------------------------------------------------------- */ + public void addCookie(HttpCookie cookie) + { + synchronized (this) + { + if (_cookies == null) + _cookies = new ArrayList(); + _cookies.add(cookie); + } + + // TODO query, remove and age methods + } + + /* ------------------------------------------------------------------------------- */ + /** + * Get a connection. We either get an idle connection if one is available, or + * we make a new connection, if we have not yet reached maxConnections. If we + * have reached maxConnections, we wait until the number reduces. + * @param timeout max time prepared to block waiting to be able to get a connection + * @return + * @throws IOException + */ + private HttpConnection getConnection(long timeout) throws IOException + { + HttpConnection connection = null; + + while ((connection == null) && (connection = getIdleConnection()) == null && timeout>0) + { + int totalConnections = 0; + boolean starting = false; + synchronized (this) + { + totalConnections = _connections.size() + _pendingConnections; + if (totalConnections < _maxConnections) + { + _newConnection++; + startNewConnection(); + starting = true; + } + } + + if (!starting) + { + try + { + Thread.currentThread().sleep(200); + timeout-=200; + } + catch (InterruptedException e) + { + Log.ignore(e); + } + } + else + { + try + { + Object o = _newQueue.take(); + if (o instanceof HttpConnection) + { + connection = (HttpConnection)o; + } + else + throw (IOException)o; + } + catch (InterruptedException e) + { + Log.ignore(e); + } + } + } + return connection; + } + + /* ------------------------------------------------------------------------------- */ + public HttpConnection reserveConnection(long timeout) throws IOException + { + HttpConnection connection = getConnection(timeout); + if (connection != null) + connection.setReserved(true); + return connection; + } + + /* ------------------------------------------------------------------------------- */ + public HttpConnection getIdleConnection() throws IOException + { + synchronized (this) + { + long now = System.currentTimeMillis(); + long idleTimeout = _client.getIdleTimeout(); + + // Find an idle connection + while (_idle.size() > 0) + { + HttpConnection connection = _idle.remove(_idle.size() - 1); + long last = connection.getLast(); + if (connection.getEndPoint().isOpen() && (last == 0 || ((now - last) < idleTimeout))) + return connection; + else + { + _connections.remove(connection); + connection.getEndPoint().close(); + } + } + + return null; + } + } + + /* ------------------------------------------------------------------------------- */ + protected void startNewConnection() + { + try + { + synchronized (this) + { + _pendingConnections++; + } + _client._connector.startConnection(this); + } + catch (Exception e) + { + e.printStackTrace(); + onConnectionFailed(e); + } + } + + /* ------------------------------------------------------------------------------- */ + public void onConnectionFailed(Throwable throwable) + { + Throwable connect_failure = null; + + synchronized (this) + { + _pendingConnections--; + if (_newConnection > 0) + { + connect_failure = throwable; + _newConnection--; + } + else if (_queue.size() > 0) + { + HttpExchange ex = _queue.removeFirst(); + ex.getEventListener().onConnectionFailed(throwable); + } + } + + if (connect_failure != null) + { + try + { + _newQueue.put(connect_failure); + } + catch (InterruptedException e) + { + Log.ignore(e); + } + } + } + + /* ------------------------------------------------------------------------------- */ + public void onException(Throwable throwable) + { + synchronized (this) + { + _pendingConnections--; + if (_queue.size() > 0) + { + HttpExchange ex = _queue.removeFirst(); + ex.getEventListener().onException(throwable); + ex.setStatus(HttpExchange.STATUS_EXCEPTED); + } + } + } + + /* ------------------------------------------------------------------------------- */ + public void onNewConnection(HttpConnection connection) throws IOException + { + HttpConnection q_connection = null; + + synchronized (this) + { + _pendingConnections--; + _connections.add(connection); + + if (_newConnection > 0) + { + q_connection = connection; + _newConnection--; + } + else if (_queue.size() == 0) + { + _idle.add(connection); + } + else + { + HttpExchange ex = _queue.removeFirst(); + connection.send(ex); + } + } + + if (q_connection != null) + { + try + { + _newQueue.put(q_connection); + } + catch (InterruptedException e) + { + Log.ignore(e); + } + } + } + + /* ------------------------------------------------------------------------------- */ + public void returnConnection(HttpConnection connection, boolean close) throws IOException + { + if (connection.isReserved()) + connection.setReserved(false); + + if (close) + { + try + { + connection.close(); + } + catch (IOException e) + { + Log.ignore(e); + } + } + + if (!_client.isStarted()) + return; + + if (!close && connection.getEndPoint().isOpen()) + { + synchronized (this) + { + if (_queue.size() == 0) + { + connection.setLast(System.currentTimeMillis()); + _idle.add(connection); + } + else + { + HttpExchange ex = _queue.removeFirst(); + connection.send(ex); + } + this.notifyAll(); + } + } + else + { + synchronized (this) + { + _connections.remove(connection); + if (!_queue.isEmpty()) + startNewConnection(); + } + } + } + + /* ------------------------------------------------------------ */ + public void send(HttpExchange ex) throws IOException + { + LinkedList listeners = _client.getRegisteredListeners(); + + if (listeners != null) + { + // Add registered listeners, fail if we can't load them + for (int i = listeners.size(); i > 0; --i) + { + String listenerClass = listeners.get(i - 1); + + try + { + Class listener = Class.forName(listenerClass); + Constructor constructor = listener.getDeclaredConstructor(HttpDestination.class, HttpExchange.class); + HttpEventListener elistener = (HttpEventListener)constructor.newInstance(this, ex); + ex.setEventListener(elistener); + } + catch (Exception e) + { + e.printStackTrace(); + throw new IOException("Unable to instantiate registered listener for destination: " + listenerClass); + } + } + } + + // Security is supported by default and should be the first consulted + if (_client.hasRealms()) + { + ex.setEventListener(new SecurityListener(this, ex)); + } + + doSend(ex); + } + + /* ------------------------------------------------------------ */ + public void resend(HttpExchange ex) throws IOException + { + ex.getEventListener().onRetry(); + doSend(ex); + } + + /* ------------------------------------------------------------ */ + protected void doSend(HttpExchange ex) throws IOException + { + // add cookies + // TODO handle max-age etc. + if (_cookies != null) + { + StringBuilder buf = null; + for (HttpCookie cookie : _cookies) + { + if (buf == null) + buf = new StringBuilder(); + else + buf.append("; "); + buf.append(cookie.getName()); // TODO quotes + buf.append("="); + buf.append(cookie.getValue()); // TODO quotes + } + if (buf != null) + ex.addRequestHeader(HttpHeaders.COOKIE, buf.toString()); + } + + // Add any known authorizations + if (_authorizations != null) + { + Authorization auth = (Authorization)_authorizations.match(ex.getURI()); + if (auth != null) + ((Authorization)auth).setCredentials(ex); + } + + synchronized (this) + { + //System.out.println( "Sending: " + ex.toString() ); + + HttpConnection connection = null; + if (_queue.size() > 0 || (connection = getIdleConnection()) == null || !connection.send(ex)) + { + _queue.add(ex); + if (_connections.size() + _pendingConnections < _maxConnections) + { + startNewConnection(); + } + } + } + } + + /* ------------------------------------------------------------ */ + public synchronized String toString() + { + return "HttpDestination@" + hashCode() + "//" + _address.getHost() + ":" + _address.getPort() + "(" + _connections.size() + "," + _idle.size() + "," + _queue.size() + ")"; + } + + /* ------------------------------------------------------------ */ + public synchronized String toDetailString() + { + StringBuilder b = new StringBuilder(); + b.append(toString()); + b.append('\n'); + synchronized (this) + { + for (HttpConnection connection : _connections) + { + if (connection._exchange != null) + { + b.append(connection.toDetailString()); + if (_idle.contains(connection)) + b.append(" IDLE"); + b.append('\n'); + } + } + } + b.append("--"); + b.append('\n'); + + return b.toString(); + } + + /* ------------------------------------------------------------ */ + public void setProxy(Address proxy) + { + _proxy = proxy; + } + + /* ------------------------------------------------------------ */ + public Address getProxy() + { + return _proxy; + } + + /* ------------------------------------------------------------ */ + public Authorization getProxyAuthentication() + { + return _proxyAuthentication; + } + + /* ------------------------------------------------------------ */ + public void setProxyAuthentication(Authorization authentication) + { + _proxyAuthentication = authentication; + } + + /* ------------------------------------------------------------ */ + public boolean isProxied() + { + return _proxy != null; + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + synchronized (this) + { + for (HttpConnection connection : _connections) + { + connection.close(); + } + } + } + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListener.java new file mode 100644 index 00000000000..d4cef3d9ec7 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListener.java @@ -0,0 +1,63 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; + +/** + * + * + * + */ +public interface HttpEventListener +{ + + // TODO review the methods here, we can probably trim these down on what to expose + + public void onRequestCommitted() throws IOException; + + + public void onRequestComplete() throws IOException; + + + public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException; + + + public void onResponseHeader(Buffer name, Buffer value) throws IOException; + + + public void onResponseHeaderComplete() throws IOException; + + + public void onResponseContent(Buffer content) throws IOException; + + + public void onResponseComplete() throws IOException; + + + public void onConnectionFailed(Throwable ex); + + + public void onException(Throwable ex); + + + public void onExpire(); + + public void onRetry(); + + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java new file mode 100644 index 00000000000..97e1a8d91f9 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java @@ -0,0 +1,139 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.client; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; + +public class HttpEventListenerWrapper implements HttpEventListener +{ + HttpEventListener _listener; + boolean _delegatingRequests; + boolean _delegatingResponses; + + public HttpEventListenerWrapper() + { + _listener=null; + _delegatingRequests=false; + _delegatingResponses=false; + } + + public HttpEventListenerWrapper(HttpEventListener eventListener,boolean delegating) + { + _listener=eventListener; + _delegatingRequests=delegating; + _delegatingResponses=delegating; + } + + public HttpEventListener getEventListener() + { + return _listener; + } + + public void setEventListener(HttpEventListener listener) + { + _listener = listener; + } + + public boolean isDelegatingRequests() + { + return _delegatingRequests; + } + + public boolean isDelegatingResponses() + { + return _delegatingResponses; + } + + public void setDelegatingRequests(boolean delegating) + { + _delegatingRequests = delegating; + } + + public void setDelegatingResponses(boolean delegating) + { + _delegatingResponses = delegating; + } + + public void onConnectionFailed(Throwable ex) + { + if (_delegatingRequests) + _listener.onConnectionFailed(ex); + } + + public void onException(Throwable ex) + { + if (_delegatingRequests||_delegatingResponses) + _listener.onException(ex); + } + + public void onExpire() + { + if (_delegatingRequests||_delegatingResponses) + _listener.onExpire(); + } + + public void onRequestCommitted() throws IOException + { + if (_delegatingRequests) + _listener.onRequestCommitted(); + } + + public void onRequestComplete() throws IOException + { + if (_delegatingRequests) + _listener.onRequestComplete(); + } + + public void onResponseComplete() throws IOException + { + if (_delegatingResponses) + _listener.onResponseComplete(); + } + + public void onResponseContent(Buffer content) throws IOException + { + if (_delegatingResponses) + _listener.onResponseContent(content); + } + + public void onResponseHeader(Buffer name, Buffer value) throws IOException + { + if (_delegatingResponses) + _listener.onResponseHeader(name,value); + } + + public void onResponseHeaderComplete() throws IOException + { + if (_delegatingResponses) + _listener.onResponseHeaderComplete(); + } + + public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if (_delegatingResponses) + _listener.onResponseStatus(version,status,reason); + } + + public void onRetry() + { + if (_delegatingRequests) + _listener.onRetry(); + } + + + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java new file mode 100644 index 00000000000..23d4070cb64 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java @@ -0,0 +1,645 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.util.log.Log; + + +/** + * An HTTP client API that encapsulates Exchange with a HTTP server. + * + * This object encapsulates:
    + *
  • The HTTP server. (see {@link #setAddress(InetSocketAddress)} or {@link #setURL(String)}) + *
  • The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setURI(String)}, and {@link #setVersion(int)} + *
  • The Request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)}) + *
  • The Request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)}) + *
  • The status of the exchange (see {@link #getStatus()}) + *
  • Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()}) + *
  • The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)} + *
+ * + * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous + * interaction with the the exchange. Typically a developer will extend the HttpExchange class with a derived + * class that implements some or all of the onXxx callbacks. There are also some predefined HttpExchange subtypes + * that can be used as a basis (see {@link ContentExchange} and {@link CachedExchange}. + * + *

Typically the HttpExchange is passed to a the {@link HttpClient#send(HttpExchange)} method, which in + * turn selects a {@link HttpDestination} and calls it's {@link HttpDestination#send(HttpExchange), which + * then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange). + * A developer may wish to directly call send on the destination or connection if they wish to bypass + * some handling provided (eg Cookie handling in the HttpDestination). + * + *

In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed + * pipeline request, authentication retry or redirection). In such cases, the HttpClient and/or HttpDestination + * may insert their own HttpExchangeListener to intercept and filter the call backs intended for the + * HttpExchange. + * + * + * + */ +public class HttpExchange +{ + public static final int STATUS_START = 0; + public static final int STATUS_WAITING_FOR_CONNECTION = 1; + public static final int STATUS_WAITING_FOR_COMMIT = 2; + public static final int STATUS_SENDING_REQUEST = 3; + public static final int STATUS_WAITING_FOR_RESPONSE = 4; + public static final int STATUS_PARSING_HEADERS = 5; + public static final int STATUS_PARSING_CONTENT = 6; + public static final int STATUS_COMPLETED = 7; + public static final int STATUS_EXPIRED = 8; + public static final int STATUS_EXCEPTED = 9; + + Address _address; + String _method = HttpMethods.GET; + Buffer _scheme = HttpSchemes.HTTP_BUFFER; + int _version = HttpVersions.HTTP_1_1_ORDINAL; + String _uri; + int _status = STATUS_START; + HttpFields _requestFields = new HttpFields(); + Buffer _requestContent; + InputStream _requestContentSource; + Buffer _requestContentChunk; + boolean _retryStatus = false; + + + /** + * boolean controlling if the exchange will have listeners autoconfigured by + * the destination + */ + boolean _configureListeners = true; + + + private HttpEventListener _listener = new Listener(); + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + // methods to build request + + /* ------------------------------------------------------------ */ + public int getStatus() + { + return _status; + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public void waitForStatus(int status) throws InterruptedException + { + synchronized (this) + { + while (_status < status) + { + this.wait(); + } + } + } + + + public int waitForDone () throws InterruptedException + { + synchronized (this) + { + while (!isDone(_status)) + this.wait(); + } + return _status; + } + + + + + /* ------------------------------------------------------------ */ + public void reset() + { + setStatus(STATUS_START); + } + + /* ------------------------------------------------------------ */ + void setStatus(int status) + { + synchronized (this) + { + _status = status; + this.notifyAll(); + + try + { + switch (status) + { + case STATUS_WAITING_FOR_CONNECTION: + break; + + case STATUS_WAITING_FOR_COMMIT: + break; + + case STATUS_SENDING_REQUEST: + break; + + case HttpExchange.STATUS_WAITING_FOR_RESPONSE: + getEventListener().onRequestCommitted(); + break; + + case STATUS_PARSING_HEADERS: + break; + + case STATUS_PARSING_CONTENT: + getEventListener().onResponseHeaderComplete(); + break; + + case STATUS_COMPLETED: + getEventListener().onResponseComplete(); + break; + + case STATUS_EXPIRED: + getEventListener().onExpire(); + break; + + } + } + catch (IOException e) + { + Log.warn(e); + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isDone (int status) + { + return ((status == STATUS_COMPLETED) || (status == STATUS_EXPIRED) || (status == STATUS_EXCEPTED)); + } + + /* ------------------------------------------------------------ */ + public HttpEventListener getEventListener() + { + return _listener; + } + + /* ------------------------------------------------------------ */ + public void setEventListener(HttpEventListener listener) + { + _listener=listener; + } + + /* ------------------------------------------------------------ */ + /** + * @param url Including protocol, host and port + */ + public void setURL(String url) + { + HttpURI uri = new HttpURI(url); + String scheme = uri.getScheme(); + if (scheme != null) + { + if (HttpSchemes.HTTP.equalsIgnoreCase(scheme)) + setScheme(HttpSchemes.HTTP_BUFFER); + else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme)) + setScheme(HttpSchemes.HTTPS_BUFFER); + else + setScheme(new ByteArrayBuffer(scheme)); + } + + int port = uri.getPort(); + if (port <= 0) + port = "https".equalsIgnoreCase(scheme)?443:80; + + setAddress(new Address(uri.getHost(),port)); + + String completePath = uri.getCompletePath(); + if (completePath == null) + completePath = "/"; + + setURI(completePath); + } + + /* ------------------------------------------------------------ */ + /** + * @param address + */ + public void setAddress(Address address) + { + _address = address; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public Address getAddress() + { + return _address; + } + + /* ------------------------------------------------------------ */ + /** + * @param scheme + */ + public void setScheme(Buffer scheme) + { + _scheme = scheme; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public Buffer getScheme() + { + return _scheme; + } + + /* ------------------------------------------------------------ */ + /** + * @param version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1 + */ + public void setVersion(int version) + { + _version = version; + } + + /* ------------------------------------------------------------ */ + public void setVersion(String version) + { + CachedBuffer v = HttpVersions.CACHE.get(version); + if (v == null) + _version = 10; + else + _version = v.getOrdinal(); + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public int getVersion() + { + return _version; + } + + /* ------------------------------------------------------------ */ + /** + * @param method + */ + public void setMethod(String method) + { + _method = method; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public String getMethod() + { + return _method; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public String getURI() + { + return _uri; + } + + /* ------------------------------------------------------------ */ + /** + * @param uri + */ + public void setURI(String uri) + { + _uri = uri; + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + */ + public void addRequestHeader(String name, String value) + { + getRequestFields().add(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + */ + public void addRequestHeader(Buffer name, Buffer value) + { + getRequestFields().add(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + */ + public void setRequestHeader(String name, String value) + { + getRequestFields().put(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + */ + public void setRequestHeader(Buffer name, Buffer value) + { + getRequestFields().put(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param value + */ + public void setRequestContentType(String value) + { + getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value); + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public HttpFields getRequestFields() + { + return _requestFields; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + // methods to commit and/or send the request + + /* ------------------------------------------------------------ */ + /** + * @param requestContent + */ + public void setRequestContent(Buffer requestContent) + { + _requestContent = requestContent; + } + + /* ------------------------------------------------------------ */ + /** + * @param in + */ + public void setRequestContentSource(InputStream in) + { + _requestContentSource = in; + } + + /* ------------------------------------------------------------ */ + public InputStream getRequestContentSource() + { + return _requestContentSource; + } + + /* ------------------------------------------------------------ */ + public Buffer getRequestContentChunk() throws IOException + { + synchronized (this) + { + if (_requestContentChunk == null) + _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure + else + { + if (_requestContentChunk.hasContent()) + throw new IllegalStateException(); + _requestContentChunk.clear(); + } + + int read = _requestContentChunk.capacity(); + int length = _requestContentSource.read(_requestContentChunk.array(),0,read); + if (length >= 0) + { + _requestContentChunk.setPutIndex(length); + return _requestContentChunk; + } + return null; + } + } + + /* ------------------------------------------------------------ */ + public Buffer getRequestContent() + { + return _requestContent; + } + + public boolean getRetryStatus() + { + return _retryStatus; + } + + public void setRetryStatus( boolean retryStatus ) + { + _retryStatus = retryStatus; + } + + /* ------------------------------------------------------------ */ + /** Cancel this exchange + * Currently this implementation does nothing. + */ + public void cancel() + { + + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return "HttpExchange@" + hashCode() + "=" + _method + "//" + _address.getHost() + ":" + _address.getPort() + _uri + "#" + _status; + } + + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + // methods to handle response + protected void onRequestCommitted() throws IOException + { + } + + protected void onRequestComplete() throws IOException + { + } + + protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + } + + protected void onResponseHeader(Buffer name, Buffer value) throws IOException + { + } + + protected void onResponseHeaderComplete() throws IOException + { + } + + protected void onResponseContent(Buffer content) throws IOException + { + } + + protected void onResponseComplete() throws IOException + { + } + + protected void onConnectionFailed(Throwable ex) + { + Log.warn("CONNECTION FAILED on " + this,ex); + } + + protected void onException(Throwable ex) + { + + Log.warn("EXCEPTION on " + this,ex); + } + + protected void onExpire() + { + Log.debug("EXPIRED " + this); + } + + protected void onRetry() throws IOException + {} + + /** + * true of the exchange should have listeners configured for it by the destination + * + * false if this is being managed elsewhere + * + * @return + */ + public boolean configureListeners() + { + return _configureListeners; + } + + public void setConfigureListeners(boolean autoConfigure ) + { + this._configureListeners = autoConfigure; + } + + private class Listener implements HttpEventListener + { + public void onConnectionFailed(Throwable ex) + { + HttpExchange.this.onConnectionFailed(ex); + } + + public void onException(Throwable ex) + { + HttpExchange.this.onException(ex); + } + + public void onExpire() + { + HttpExchange.this.onExpire(); + } + + public void onRequestCommitted() throws IOException + { + HttpExchange.this.onRequestCommitted(); + } + + public void onRequestComplete() throws IOException + { + HttpExchange.this.onRequestComplete(); + } + + public void onResponseComplete() throws IOException + { + HttpExchange.this.onResponseComplete(); + } + + public void onResponseContent(Buffer content) throws IOException + { + HttpExchange.this.onResponseContent(content); + } + + public void onResponseHeader(Buffer name, Buffer value) throws IOException + { + HttpExchange.this.onResponseHeader(name,value); + } + + public void onResponseHeaderComplete() throws IOException + { + HttpExchange.this.onResponseHeaderComplete(); + } + + public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + HttpExchange.this.onResponseStatus(version,status,reason); + } + + public void onRetry() + { + HttpExchange.this.setRetryStatus( true ); + try + { + HttpExchange.this.onRetry(); + } + catch (IOException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + + /** + * @deprecated use {@link org.eclipse.jetty.client.CachedExchange} + * + */ + public static class CachedExchange extends org.eclipse.jetty.client.CachedExchange + { + public CachedExchange(boolean cacheFields) + { + super(cacheFields); + } + } + + /** + * @deprecated use {@link org.eclipse.jetty.client.ContentExchange} + * + */ + public static class ContentExchange extends org.eclipse.jetty.client.ContentExchange + { + + } + + + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java new file mode 100644 index 00000000000..7f3f36fae0c --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java @@ -0,0 +1,198 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.ssl.SslSelectChannelEndPoint; +import org.eclipse.jetty.io.AbstractBuffers; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectorManager; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + +class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector, Runnable +{ + private final HttpClient _httpClient; + private SSLContext _sslContext; + private Buffers _sslBuffers; + + SelectorManager _selectorManager=new Manager(); + + /** + * @param httpClient + */ + SelectConnector(HttpClient httpClient) + { + _httpClient = httpClient; + } + + protected void doStart() throws Exception + { + _selectorManager.start(); + _httpClient._threadPool.dispatch(this); + } + + protected void doStop() throws Exception + { + _selectorManager.stop(); + } + + public void startConnection( HttpDestination destination ) + throws IOException + { + SocketChannel channel = SocketChannel.open(); + Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress(); + channel.connect(address.toSocketAddress()); + channel.configureBlocking( false ); + channel.socket().setSoTimeout( _httpClient._soTimeout ); + _selectorManager.register( channel, destination ); + } + + public void run() + { + while (_httpClient.isRunning()) + { + try + { + _selectorManager.doSelect(0); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + class Manager extends SelectorManager + { + protected SocketChannel acceptChannel(SelectionKey key) throws IOException + { + throw new IllegalStateException(); + } + + public boolean dispatch(Runnable task) + { + return SelectConnector.this._httpClient._threadPool.dispatch(task); + } + + protected void endPointOpened(SelectChannelEndPoint endpoint) + { + } + + protected void endPointClosed(SelectChannelEndPoint endpoint) + { + } + + protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) + { + return new HttpConnection(_httpClient,endpoint,SelectConnector.this._httpClient.getHeaderBufferSize(),SelectConnector.this._httpClient.getRequestBufferSize()); + } + + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException + { + // key should have destination at this point (will be replaced by endpoint after this call) + HttpDestination dest=(HttpDestination)key.attachment(); + + + SelectChannelEndPoint ep=null; + + if (dest.isSecure()) + { + if (dest.isProxied()) + { + String connect = HttpMethods.CONNECT+" "+dest.getAddress()+HttpVersions.HTTP_1_0+"\r\n\r\n"; + // TODO need to send this over channel unencrypted and setup endpoint to ignore the 200 OK response. + + throw new IllegalStateException("Not Implemented"); + } + + SSLEngine engine=newSslEngine(); + ep = new SslSelectChannelEndPoint(_sslBuffers,channel,selectSet,key,engine); + } + else + { + ep=new SelectChannelEndPoint(channel,selectSet,key); + } + + HttpConnection connection=(HttpConnection)ep.getConnection(); + connection.setDestination(dest); + dest.onNewConnection(connection); + return ep; + } + + private synchronized SSLEngine newSslEngine() throws IOException + { + if (_sslContext==null) + { + _sslContext = SelectConnector.this._httpClient.getSSLContext(); + } + + SSLEngine sslEngine = _sslContext.createSSLEngine(); + sslEngine.setUseClientMode(true); + sslEngine.beginHandshake(); + + if (_sslBuffers==null) + { + AbstractBuffers buffers = new AbstractBuffers() + { + public Buffer newBuffer( int size ) + { + return new IndirectNIOBuffer( size); + } + }; + + buffers.setRequestBufferSize( sslEngine.getSession().getPacketBufferSize()); + buffers.setResponseBufferSize(sslEngine.getSession().getApplicationBufferSize()); + + try + { + buffers.start(); + } + catch(Exception e) + { + throw new IllegalStateException(e); + } + _sslBuffers=buffers; + } + + return sslEngine; + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.io.nio.SelectorManager#connectionFailed(java.nio.channels.SocketChannel, java.lang.Throwable, java.lang.Object) + */ + protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) + { + if (attachment instanceof HttpDestination) + ((HttpDestination)attachment).onConnectionFailed(ex); + else + Log.warn(ex); + } + + } + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java new file mode 100644 index 00000000000..275a7b26513 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java @@ -0,0 +1,87 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Socket; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; + +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + +class SocketConnector extends AbstractLifeCycle implements HttpClient.Connector +{ + /** + * + */ + private final HttpClient _httpClient; + + /** + * @param httpClient + */ + SocketConnector(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public void startConnection(final HttpDestination destination) throws IOException + { + Socket socket=null; + + if ( destination.isSecure() ) + { + SSLContext sslContext = _httpClient.getSSLContext(); + socket = sslContext.getSocketFactory().createSocket(); + } + else + { + Log.debug("Using Regular Socket"); + socket = SocketFactory.getDefault().createSocket(); + } + + Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress(); + socket.connect(address.toSocketAddress()); + + EndPoint endpoint=new SocketEndPoint(socket); + + final HttpConnection connection=new HttpConnection(_httpClient,endpoint,_httpClient.getHeaderBufferSize(),_httpClient.getRequestBufferSize()); + connection.setDestination(destination); + destination.onNewConnection(connection); + _httpClient.getThreadPool().dispatch(new Runnable() + { + public void run() + { + try + { + connection.handle(); + } + catch (IOException e) + { + if (e instanceof InterruptedIOException) + Log.ignore(e); + else + { + Log.warn(e); + destination.onException(e); + } + } + } + }); + + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/Authorization.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/Authorization.java new file mode 100644 index 00000000000..02f64ae97e0 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/Authorization.java @@ -0,0 +1,28 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + + + +import java.io.IOException; + +import org.eclipse.jetty.client.HttpExchange; + +/** + * Simple authentication interface that sets required fields on the exchange. + */ +public interface Authorization +{ + public void setCredentials( HttpExchange exchange) throws IOException; +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/BasicAuthorization.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/BasicAuthorization.java new file mode 100644 index 00000000000..a5a25fe94ef --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/BasicAuthorization.java @@ -0,0 +1,52 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + + +import java.io.IOException; + +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.StringUtil; + +/** + * Sets authentication headers for BASIC authentication challenges + * + * + */ +public class BasicAuthorization implements Authorization +{ + private Buffer _authorization; + + public BasicAuthorization(Realm realm) throws IOException + { + String authenticationString = "Basic " + B64Code.encode( realm.getPrincipal() + ":" + realm.getCredentials(), StringUtil.__ISO_8859_1); + _authorization= new ByteArrayBuffer(authenticationString); + } + + /** + * BASIC authentication is of the form + * + * encoded credentials are of the form: username:password + * + * + */ + public void setCredentials( HttpExchange exchange ) throws IOException + { + exchange.setRequestHeader( HttpHeaders.AUTHORIZATION_BUFFER, _authorization); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/DigestAuthorization.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/DigestAuthorization.java new file mode 100644 index 00000000000..e58ae09bf90 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/DigestAuthorization.java @@ -0,0 +1,138 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + + +import java.io.IOException; +import java.security.MessageDigest; +import java.util.Map; + +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; + +public class DigestAuthorization implements Authorization +{ + private static final String NC = "00000001"; + Realm securityRealm; + Map details; + + public DigestAuthorization(Realm realm, Map details) + { + this.securityRealm=realm; + this.details=details; + } + + + public void setCredentials( HttpExchange exchange ) + throws IOException + { + StringBuilder buffer = new StringBuilder().append("Digest"); + + buffer.append(" ").append("username").append('=').append('"').append(securityRealm.getPrincipal()).append('"'); + + buffer.append(", ").append("realm").append('=').append('"').append(String.valueOf(details.get("realm"))).append('"'); + + buffer.append(", ").append("nonce").append('=').append('"').append(String.valueOf(details.get("nonce"))).append('"'); + + buffer.append(", ").append("uri").append('=').append('"').append(exchange.getURI()).append('"'); + + buffer.append(", ").append("algorithm").append('=').append(String.valueOf(details.get("algorithm"))); + + String cnonce = newCnonce(exchange, securityRealm, details); + + buffer.append(", ").append("response").append('=').append('"').append(newResponse(cnonce, + exchange, securityRealm, details)).append('"'); + + buffer.append(", ").append("qop").append('=').append(String.valueOf(details.get("qop"))); + + + buffer.append(", ").append("nc").append('=').append(NC); + + buffer.append(", ").append("cnonce").append('=').append('"').append(cnonce).append('"'); + + exchange.setRequestHeader( HttpHeaders.AUTHORIZATION, + new String(buffer.toString().getBytes(StringUtil.__ISO_8859_1))); + } + + protected String newResponse(String cnonce, HttpExchange exchange, Realm securityRealm, Map details) + { + try{ + MessageDigest md = MessageDigest.getInstance("MD5"); + + // calc A1 digest + md.update(securityRealm.getPrincipal().getBytes(StringUtil.__ISO_8859_1)); + md.update((byte)':'); + md.update(String.valueOf(details.get("realm")).getBytes(StringUtil.__ISO_8859_1)); + md.update((byte)':'); + md.update(securityRealm.getCredentials().getBytes(StringUtil.__ISO_8859_1)); + byte[] ha1 = md.digest(); + // calc A2 digest + md.reset(); + md.update(exchange.getMethod().getBytes(StringUtil.__ISO_8859_1)); + md.update((byte)':'); + md.update(exchange.getURI().getBytes(StringUtil.__ISO_8859_1)); + byte[] ha2=md.digest(); + + md.update(TypeUtil.toString(ha1,16).getBytes(StringUtil.__ISO_8859_1)); + md.update((byte)':'); + md.update(String.valueOf(details.get("nonce")).getBytes(StringUtil.__ISO_8859_1)); + md.update((byte)':'); + md.update(NC.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte)':'); + md.update(cnonce.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte)':'); + md.update(String.valueOf(details.get("qop")).getBytes(StringUtil.__ISO_8859_1)); + md.update((byte)':'); + md.update(TypeUtil.toString(ha2,16).getBytes(StringUtil.__ISO_8859_1)); + byte[] digest=md.digest(); + + // check digest + return encode(digest); + } + catch(Exception e) + { + e.printStackTrace(); + return null; + } + } + + protected String newCnonce(HttpExchange exchange, Realm securityRealm, Map details) + { + try + { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] b= md.digest(String.valueOf(System.currentTimeMillis()).getBytes(StringUtil.__ISO_8859_1)); + return encode(b); + } + catch(Exception e) + { + e.printStackTrace(); + return null; + } + } + + private static String encode(byte[] data) + { + StringBuilder buffer = new StringBuilder(); + for (int i=0; i>> 4)); + buffer.append(Integer.toHexString(data[i] & 0x0f)); + } + return buffer.toString(); + } + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/HashRealmResolver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/HashRealmResolver.java new file mode 100644 index 00000000000..8ec1cbd61d3 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/HashRealmResolver.java @@ -0,0 +1,40 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.client.HttpDestination; + +public class HashRealmResolver implements RealmResolver +{ + private Map_realmMap; + + public void addSecurityRealm( Realm realm ) + { + if (_realmMap == null) + { + _realmMap = new HashMap(); + } + _realmMap.put( realm.getId(), realm ); + } + + public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException + { + return _realmMap.get( realmName ); + } + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/ProxyAuthorization.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/ProxyAuthorization.java new file mode 100644 index 00000000000..f4846bd038c --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/ProxyAuthorization.java @@ -0,0 +1,52 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + + +import java.io.IOException; + +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.StringUtil; + +/** + * Sets proxy authentication headers for BASIC authentication challenges + * + * + */ +public class ProxyAuthorization implements Authorization +{ + private Buffer _authorization; + + public ProxyAuthorization(String username,String password) throws IOException + { + String authenticationString = "Basic " + B64Code.encode( username + ":" + password, StringUtil.__ISO_8859_1); + _authorization= new ByteArrayBuffer(authenticationString); + } + + /** + * BASIC proxy authentication is of the form + * + * encoded credentials are of the form: username:password + * + * + */ + public void setCredentials( HttpExchange exchange ) throws IOException + { + exchange.setRequestHeader( HttpHeaders.PROXY_AUTHORIZATION_BUFFER, _authorization); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/Realm.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/Realm.java new file mode 100644 index 00000000000..01023e984bb --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/Realm.java @@ -0,0 +1,27 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + +/** + * Simple security realm interface. + */ +public interface Realm +{ + public String getId(); + + public String getPrincipal(); + + public String getCredentials(); + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/RealmResolver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/RealmResolver.java new file mode 100644 index 00000000000..c5a971a9a5d --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/RealmResolver.java @@ -0,0 +1,22 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.client.security; + +import java.io.IOException; + +import org.eclipse.jetty.client.HttpDestination; + +public interface RealmResolver +{ + public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException; +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/SecurityListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/SecurityListener.java new file mode 100644 index 00000000000..e41ac3baf34 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/SecurityListener.java @@ -0,0 +1,260 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.HttpEventListenerWrapper; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; + + +/** + * SecurityListener + * + * Allow for insertion of security dialog when performing an + * HttpExchange. + */ +public class SecurityListener extends HttpEventListenerWrapper +{ + private HttpDestination _destination; + private HttpExchange _exchange; + private boolean _requestComplete; + private boolean _responseComplete; + private boolean _needIntercept; + + private int _attempts = 0; // TODO remember to settle on winning solution + + public SecurityListener(HttpDestination destination, HttpExchange ex) + { + // Start of sending events through to the wrapped listener + // Next decision point is the onResponseStatus + super(ex.getEventListener(),true); + _destination=destination; + _exchange=ex; + } + + + /** + * scrapes an authentication type from the authString + * + * @param authString + * @return + */ + protected String scrapeAuthenticationType( String authString ) + { + String authType; + + if ( authString.indexOf( " " ) == -1 ) + { + authType = authString.toString().trim(); + } + else + { + String authResponse = authString.toString(); + authType = authResponse.substring( 0, authResponse.indexOf( " " ) ).trim(); + } + return authType; + } + + /** + * scrapes a set of authentication details from the authString + * + * @param authString + * @return + */ + protected Map scrapeAuthenticationDetails( String authString ) + { + Map authenticationDetails = new HashMap(); + authString = authString.substring( authString.indexOf( " " ) + 1, authString.length() ); + StringTokenizer strtok = new StringTokenizer( authString, ","); + + while ( strtok.hasMoreTokens() ) + { + String[] pair = strtok.nextToken().split( "=" ); + if ( pair.length == 2 ) + { + String itemName = pair[0].trim(); + String itemValue = pair[1].trim(); + + itemValue = StringUtil.unquote( itemValue ); + + authenticationDetails.put( itemName, itemValue ); + } + else + { + throw new IllegalArgumentException( "unable to process authentication details" ); + } + } + return authenticationDetails; + } + + + public void onResponseStatus( Buffer version, int status, Buffer reason ) + throws IOException + { + if (Log.isDebugEnabled()) + Log.debug("SecurityListener:Response Status: " + status ); + + if ( status == HttpStatus.UNAUTHORIZED_401 && _attempts<_destination.getHttpClient().maxRetries()) + { + // Let's absorb events until we have done some retries + setDelegatingResponses(false); + _needIntercept = true; + } + else + { + setDelegatingResponses(true); + setDelegatingRequests(true); + _needIntercept = false; + } + super.onResponseStatus(version,status,reason); + } + + + public void onResponseHeader( Buffer name, Buffer value ) + throws IOException + { + if (Log.isDebugEnabled()) + Log.debug( "SecurityListener:Header: " + name.toString() + " / " + value.toString() ); + + + if (!isDelegatingResponses()) + { + int header = HttpHeaders.CACHE.getOrdinal(name); + switch (header) + { + case HttpHeaders.WWW_AUTHENTICATE_ORDINAL: + + // TODO don't hard code this bit. + String authString = value.toString(); + String type = scrapeAuthenticationType( authString ); + + // TODO maybe avoid this map creation + Map details = scrapeAuthenticationDetails( authString ); + String pathSpec="/"; // TODO work out the real path spec + RealmResolver realmResolver = _destination.getHttpClient().getRealmResolver(); + + if ( realmResolver == null ) + { + break; + } + + Realm realm = realmResolver.getRealm( details.get("realm"), _destination, pathSpec ); // TODO work our realm correctly + + if ( realm == null ) + { + Log.warn( "Unknown Security Realm: " + details.get("realm") ); + } + else if ("digest".equalsIgnoreCase(type)) + { + _destination.addAuthorization("/",new DigestAuthorization(realm,details)); + + } + else if ("basic".equalsIgnoreCase(type)) + { + _destination.addAuthorization(pathSpec,new BasicAuthorization(realm)); + } + + break; + } + } + super.onResponseHeader(name,value); + } + + + public void onRequestComplete() throws IOException + { + _requestComplete = true; + + if (_needIntercept) + { + if (_requestComplete && _responseComplete) + { + if (Log.isDebugEnabled()) + Log.debug("onRequestComplete, Both complete: Resending from onResponseComplete "+_exchange); + _responseComplete = false; + _requestComplete = false; + setDelegatingRequests(true); + setDelegatingResponses(true); + _destination.resend(_exchange); + } + else + { + if (Log.isDebugEnabled()) + Log.debug("onRequestComplete, Response not yet complete onRequestComplete, calling super for "+_exchange); + super.onRequestComplete(); + } + } + else + { + if (Log.isDebugEnabled()) + Log.debug("onRequestComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange); + super.onRequestComplete(); + } + } + + + public void onResponseComplete() throws IOException + { + _responseComplete = true; + if (_needIntercept) + { + if (_requestComplete && _responseComplete) + { + if (Log.isDebugEnabled()) + Log.debug("onResponseComplete, Both complete: Resending from onResponseComplete"+_exchange); + _responseComplete = false; + _requestComplete = false; + setDelegatingResponses(true); + setDelegatingRequests(true); + _destination.resend(_exchange); + + } + else + { + if (Log.isDebugEnabled()) + Log.debug("onResponseComplete, Request not yet complete from onResponseComplete, calling super "+_exchange); + super.onResponseComplete(); + } + } + else + { + if (Log.isDebugEnabled()) + Log.debug("OnResponseComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange); + super.onResponseComplete(); + } + } + + public void onRetry() + { + _attempts++; + setDelegatingRequests(true); + setDelegatingResponses(true); + _requestComplete=false; + _responseComplete=false; + _needIntercept=false; + super.onRetry(); + } + + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java new file mode 100644 index 00000000000..6840998980f --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java @@ -0,0 +1,39 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + +import java.io.IOException; + +import org.eclipse.jetty.client.HttpDestination; + +/** + * Simple Realm Resolver. + *

A Realm Resolver that wraps a single realm. + * + * + */ +public class SimpleRealmResolver implements RealmResolver +{ + private Realm _realm; + + public SimpleRealmResolver( Realm realm ) + { + _realm=realm; + } + + public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException + { + return _realm; + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/MkcolExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/MkcolExchange.java new file mode 100644 index 00000000000..c71007686ba --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/MkcolExchange.java @@ -0,0 +1,55 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.webdav; + +import java.io.IOException; + +import org.eclipse.jetty.client.CachedExchange; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.util.log.Log; + + +public class MkcolExchange extends CachedExchange +{ + boolean exists = false; + + public MkcolExchange() + { + super(true); + } + + /* ------------------------------------------------------------ */ + protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if ( status == HttpStatus.CREATED_201 ) + { + Log.debug( "MkcolExchange:Status: Successfully created resource" ); + exists = true; + } + + if ( status == HttpStatus.METHOD_NOT_ALLOWED_405 ) // returned when resource exists + { + Log.debug( "MkcolExchange:Status: Resource must exist" ); + exists = true; + } + + super.onResponseStatus(version, status, reason); + } + + public boolean exists() + { + return exists; + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/PropfindExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/PropfindExchange.java new file mode 100644 index 00000000000..cd286c3e7ee --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/PropfindExchange.java @@ -0,0 +1,48 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.webdav; + +import java.io.IOException; + +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.util.log.Log; + + +public class PropfindExchange extends HttpExchange +{ + boolean _propertyExists = false; + + /* ------------------------------------------------------------ */ + protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if ( status == HttpStatus.OK_200 ) + { + Log.debug( "PropfindExchange:Status: Exists" ); + _propertyExists = true; + } + else + { + Log.debug( "PropfindExchange:Status: Not Exists" ); + } + + super.onResponseStatus(version, status, reason); + } + + public boolean exists() + { + return _propertyExists; + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavListener.java new file mode 100644 index 00000000000..c2173c0f595 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavListener.java @@ -0,0 +1,312 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.client.webdav; + +import java.io.IOException; + +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.HttpEventListenerWrapper; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.security.SecurityListener; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * WebdavListener + * + * + * + * + */ +public class WebdavListener extends HttpEventListenerWrapper +{ + private HttpDestination _destination; + private HttpExchange _exchange; + private boolean _requestComplete; + private boolean _responseComplete; + private boolean _webdavEnabled; + private boolean _needIntercept; + + public WebdavListener(HttpDestination destination, HttpExchange ex) + { + // Start of sending events through to the wrapped listener + // Next decision point is the onResponseStatus + super(ex.getEventListener(),true); + _destination=destination; + _exchange=ex; + + // We'll only enable webdav if this is a PUT request + if ( HttpMethods.PUT.equalsIgnoreCase( _exchange.getMethod() ) ) + { + _webdavEnabled = true; + } + } + + public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if ( !_webdavEnabled ) + { + _needIntercept = false; + super.onResponseStatus(version, status, reason); + return; + } + + if (Log.isDebugEnabled()) + Log.debug("WebdavListener:Response Status: " + status ); + + // The dav spec says that CONFLICT should be returned when the parent collection doesn't exist but I am seeing + // FORBIDDEN returned instead so running with that. + if ( status == HttpStatus.FORBIDDEN_403 || status == HttpStatus.CONFLICT_409 ) + { + if ( _webdavEnabled ) + { + if (Log.isDebugEnabled()) + Log.debug("WebdavListener:Response Status: dav enabled, taking a stab at resolving put issue" ); + setDelegatingResponses( false ); // stop delegating, we can try and fix this request + _needIntercept = true; + } + else + { + if (Log.isDebugEnabled()) + Log.debug("WebdavListener:Response Status: Webdav Disabled" ); + setDelegatingResponses( true ); // just make sure we delegate + setDelegatingRequests( true ); + _needIntercept = false; + } + } + else + { + _needIntercept = false; + setDelegatingResponses( true ); + setDelegatingRequests( true ); + } + + super.onResponseStatus(version, status, reason); + } + + public void onResponseComplete() throws IOException + { + _responseComplete = true; + if (_needIntercept) + { + if ( _requestComplete && _responseComplete) + { + try + { + // we have some work to do before retrying this + if ( resolveCollectionIssues() ) + { + setDelegatingRequests( true ); + setDelegatingResponses(true); + _requestComplete = false; + _responseComplete = false; + _destination.resend(_exchange); + } + else + { + // admit defeat but retry because someone else might have + setDelegatingRequests( true ); + setDelegatingResponses(true); + super.onResponseComplete(); + } + } + catch ( IOException ioe ) + { + Log.debug("WebdavListener:Complete:IOException: might not be dealing with dav server, delegate"); + super.onResponseComplete(); + } + } + else + { + if (Log.isDebugEnabled()) + Log.debug("WebdavListener:Not ready, calling super"); + super.onResponseComplete(); + } + } + else + { + super.onResponseComplete(); + } + } + + + + public void onRequestComplete () throws IOException + { + _requestComplete = true; + if (_needIntercept) + { + if ( _requestComplete && _responseComplete) + { + try + { + // we have some work to do before retrying this + if ( resolveCollectionIssues() ) + { + setDelegatingRequests( true ); + setDelegatingResponses(true); + _requestComplete = false; + _responseComplete = false; + _destination.resend(_exchange); + } + else + { + // admit defeat but retry because someone else might have + setDelegatingRequests( true ); + setDelegatingResponses(true); + super.onRequestComplete(); + } + } + catch ( IOException ioe ) + { + Log.debug("WebdavListener:Complete:IOException: might not be dealing with dav server, delegate"); + super.onRequestComplete(); + } + } + else + { + if (Log.isDebugEnabled()) + Log.debug("WebdavListener:Not ready, calling super"); + super.onRequestComplete(); + } + } + else + { + super.onRequestComplete(); + } + } + + + + + /** + * walk through the steps to try and resolve missing parent collection issues via webdav + * + * @return + * @throws IOException + */ + private boolean resolveCollectionIssues() throws IOException + { + + String uri = _exchange.getURI(); + String[] uriCollection = _exchange.getURI().split("/"); + int checkNum = uriCollection.length; + int rewind = 0; + + String parentUri = URIUtil.parentPath( uri ); + while ( !checkExists( parentUri ) ) + { + ++rewind; + parentUri = URIUtil.parentPath( parentUri ); + } + + // confirm webdav is supported for this collection + if ( checkWebdavSupported() ) + { + for (int i = 0; i < rewind;) + { + makeCollection(parentUri + "/" + uriCollection[checkNum - rewind - 1]); + parentUri = parentUri + "/" + uriCollection[checkNum - rewind - 1]; + --rewind; + } + } + else + { + return false; + } + + return true; + } + + private boolean checkExists( String uri ) throws IOException + { + PropfindExchange propfindExchange = new PropfindExchange(); + propfindExchange.setAddress( _exchange.getAddress() ); + propfindExchange.setMethod( HttpMethods.GET ); // PROPFIND acts wonky, just use get + propfindExchange.setScheme( _exchange.getScheme() ); + propfindExchange.setEventListener( new SecurityListener( _destination, propfindExchange ) ); + propfindExchange.setConfigureListeners( false ); + propfindExchange.setURI( uri ); + + _destination.send( propfindExchange ); + + try + { + propfindExchange.waitForDone(); + + return propfindExchange.exists(); + } + catch ( InterruptedException ie ) + { + Log.ignore( ie ); + return false; + } + } + + private boolean makeCollection( String uri ) throws IOException + { + MkcolExchange mkcolExchange = new MkcolExchange(); + mkcolExchange.setAddress( _exchange.getAddress() ); + mkcolExchange.setMethod( "MKCOL " + uri + " HTTP/1.1" ); + mkcolExchange.setScheme( _exchange.getScheme() ); + mkcolExchange.setEventListener( new SecurityListener( _destination, mkcolExchange ) ); + mkcolExchange.setConfigureListeners( false ); + mkcolExchange.setURI( uri ); + + _destination.send( mkcolExchange ); + + try + { + mkcolExchange.waitForDone(); + + return mkcolExchange.exists(); + } + catch ( InterruptedException ie ) + { + Log.ignore( ie ); + return false; + } + } + + + private boolean checkWebdavSupported() throws IOException + { + WebdavSupportedExchange supportedExchange = new WebdavSupportedExchange(); + supportedExchange.setAddress( _exchange.getAddress() ); + supportedExchange.setMethod( HttpMethods.OPTIONS ); + supportedExchange.setScheme( _exchange.getScheme() ); + supportedExchange.setEventListener( new SecurityListener( _destination, supportedExchange ) ); + supportedExchange.setConfigureListeners( false ); + supportedExchange.setURI( _exchange.getURI() ); + + _destination.send( supportedExchange ); + + try + { + supportedExchange.waitTilCompletion(); + return supportedExchange.isWebdavSupported(); + } + catch (InterruptedException ie ) + { + Log.ignore( ie ); + return false; + } + + } + +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java new file mode 100644 index 00000000000..b75e1d41542 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java @@ -0,0 +1,65 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.webdav; + +import java.io.IOException; + +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.util.log.Log; + + +public class WebdavSupportedExchange extends HttpExchange +{ + private boolean _webdavSupported = false; + private boolean _isComplete = false; + + protected void onResponseHeader(Buffer name, Buffer value) throws IOException + { + if (Log.isDebugEnabled()) + Log.debug("WebdavSupportedExchange:Header:" + name.toString() + " / " + value.toString() ); + if ( "DAV".equals( name.toString() ) ) + { + if ( value.toString().indexOf( "1" ) >= 0 || value.toString().indexOf( "2" ) >= 0 ) + { + _webdavSupported = true; + } + } + + super.onResponseHeader(name, value); + } + + public void waitTilCompletion() throws InterruptedException + { + synchronized (this) + { + while ( !_isComplete) + { + this.wait(); + } + } + } + + protected void onResponseComplete() throws IOException + { + _isComplete = true; + + super.onResponseComplete(); + } + + public boolean isWebdavSupported() + { + return _webdavSupported; + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java new file mode 100644 index 00000000000..7241fc39f2e --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java @@ -0,0 +1,34 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +public class AsyncSslHttpExchangeTest extends SslHttpExchangeTest +{ + + protected void setUp() throws Exception + { + _scheme="https://"; + startServer(); + _httpClient=new HttpClient(); + _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + _httpClient.setMaxConnectionsPerAddress(2); + _httpClient.start(); + } + + public void testPerf() throws Exception + { + super.testPerf(); + } + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslSecurityListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslSecurityListenerTest.java new file mode 100644 index 00000000000..721cc88878e --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslSecurityListenerTest.java @@ -0,0 +1,25 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +public class AsyncSslSecurityListenerTest extends SslSecurityListenerTest +{ + + protected void setUp() throws Exception + { + _type = HttpClient.CONNECTOR_SELECT_CHANNEL; + super.setUp(); + } + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExpireTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExpireTest.java new file mode 100644 index 00000000000..e554eeaeb3c --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExpireTest.java @@ -0,0 +1,161 @@ +package org.eclipse.jetty.client; + +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.log.Log; + +/* Test expiring connections + * + * Test contributed by: Michiel Thuys for JETTY-806 + */ +public class ExpireTest extends TestCase +{ + HttpClient client; + + Server server; + + AtomicInteger expireCount = new AtomicInteger(); + + final String host = "localhost"; + + int _port; + + @Override + protected void setUp() throws Exception + { + client = new HttpClient(); + client.setConnectorType( HttpClient.CONNECTOR_SELECT_CHANNEL ); + client.setTimeout( 200 ); + client.setMaxRetries( 0 ); + client.setMaxConnectionsPerAddress(100); + try + { + client.start(); + } + catch ( Exception e ) + { + throw new Error( "Cannot start HTTP client: " + e ); + } + + // Create server + server = new Server(); + SelectChannelConnector connector = new SelectChannelConnector(); + connector.setHost( host ); + connector.setPort( 0 ); + server.setConnectors( new Connector[] { connector } ); + server.setHandler( new AbstractHandler() + { + public void handle( String target, HttpServletRequest servletRequest, HttpServletResponse response ) throws IOException, + ServletException + { + Request request = (Request) servletRequest; + try + { + Thread.sleep( 2000 ); + } + catch ( InterruptedException e ) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + request.setHandled( true ); + } + } ); + try + { + server.start(); + _port = connector.getLocalPort(); + } + catch ( Exception e ) + { + Log.warn( "Cannot create server: " + e ); + } + } + + @Override + protected void tearDown() throws Exception + { + client.stop(); + server.stop(); + } + + public void testExpire() throws IOException + { + String baseUrl = "http://" + host + ":" + _port + "/"; + + int count = 200; + expireCount.set( 0 ); + Log.info( "Starting test on " + baseUrl ); + + for (int i=0;i0 && loops < 10 ) // max out at 30 seconds + { + Log.info( "waiting for test to complete: "+expireCount.get()+" of "+count ); + ++loops; + Thread.sleep( 2000 ); + } + Thread.sleep( 2000 ); + } + catch ( InterruptedException e ) + { + } + System.err.println('!'); + + assertEquals( 0, expireCount.get() ); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java new file mode 100644 index 00000000000..cbb069b323b --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java @@ -0,0 +1,38 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.File; + +public class ExternalKeyStoreAsyncSslHttpExchangeTest extends SslHttpExchangeTest +{ + + protected void setUp() throws Exception + { + _scheme="https://"; + startServer(); + _httpClient=new HttpClient(); + _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + _httpClient.setMaxConnectionsPerAddress(2); + + String keystore = System.getProperty("user.dir") + File.separator + "src" + File.separator + "test" + File.separator + "resources" + File.separator + + "keystore"; + + _httpClient.setKeyStoreLocation( keystore ); + _httpClient.setKeyStorePassword( "storepwd"); + _httpClient.setKeyManagerPassword( "keypwd" ); + _httpClient.start(); + } + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java new file mode 100644 index 00000000000..9d41442126a --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java @@ -0,0 +1,391 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.client.security.ProxyAuthorization; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; + +/** + * Functional testing for HttpExchange. + * + * + * + */ +public class HttpExchangeTest extends TestCase +{ + private boolean _stress=Boolean.getBoolean("STRESS"); + protected int _maxConnectionsPerAddress = 2; + protected String _scheme = "http://"; + protected Server _server; + protected int _port; + protected HttpClient _httpClient; + protected Connector _connector; + protected AtomicInteger _count = new AtomicInteger(); + + protected void setUp() throws Exception + { + startServer(); + _httpClient=new HttpClient(); + _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + _httpClient.setMaxConnectionsPerAddress(_maxConnectionsPerAddress); + _httpClient.start(); + } + + protected void tearDown() throws Exception + { + _httpClient.stop(); + Thread.sleep(500); + stopServer(); + } + + public void testPerf() throws Exception + { + sender(1,false); + sender(1,true); + sender(10,false); + sender(10,true); + sender(100,false); + sender(100,true); + if (_stress) + { + sender(1000,false); + sender(1000,true); + } + } + + /** + * Test sending data through the exchange. + * + * @throws IOException + */ + public void sender(final int nb,final boolean close) throws Exception + { + _count.set(0); + final CountDownLatch complete=new CountDownLatch(nb); + final CountDownLatch latch=new CountDownLatch(nb); + HttpExchange[] httpExchange = new HttpExchange[nb]; + long start=System.currentTimeMillis(); + for (int i=0; i0) + System.err.println(nb+"/"+_count+" c="+close+" rate="+(nb*1000/elapsed)); + + assertEquals("nb="+nb+" close="+close,0,latch.getCount()); + } + + public void testPostWithContentExchange() throws Exception + { + for (int i=0;i<200;i++) + { + ContentExchange httpExchange=new ContentExchange(); + //httpExchange.setURL(_scheme+"localhost:"+_port+"/"); + httpExchange.setURL(_scheme+"localhost:"+_port); + httpExchange.setMethod(HttpMethods.POST); + httpExchange.setRequestContent(new ByteArrayBuffer("")); + _httpClient.send(httpExchange); + int status = httpExchange.waitForDone(); + //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED); + String result=httpExchange.getResponseContent(); + assertEquals(HttpExchange.STATUS_COMPLETED, status); + assertEquals("i="+i,"",result); + } + } + + public void testGetWithContentExchange() throws Exception + { + for (int i=0;i<200;i++) + { + ContentExchange httpExchange=new ContentExchange(); + httpExchange.setURL(_scheme+"localhost:"+_port+"/?i="+i); + httpExchange.setMethod(HttpMethods.GET); + _httpClient.send(httpExchange); + int status = httpExchange.waitForDone(); + //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED); + String result=httpExchange.getResponseContent(); + assertEquals("i="+i,0,result.indexOf("")); + assertEquals("i="+i,result.length()-10,result.indexOf("")); + assertEquals(HttpExchange.STATUS_COMPLETED, status); + Thread.sleep(5); + } + } + + public void testProxy() throws Exception + { + if (_scheme.equals("https://")) + return; + try + { + _httpClient.setProxy(new Address("127.0.0.1",_port)); + _httpClient.setProxyAuthentication(new ProxyAuthorization("user","password")); + + ContentExchange httpExchange=new ContentExchange(); + httpExchange.setAddress(new Address("jetty.eclipse.org",8080)); + httpExchange.setMethod(HttpMethods.GET); + httpExchange.setURI("/jetty-6"); + _httpClient.send(httpExchange); + int status = httpExchange.waitForDone(); + //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED); + String result=httpExchange.getResponseContent(); + result=result.trim(); + assertEquals(HttpExchange.STATUS_COMPLETED, status); + assertTrue(result.startsWith("Proxy request: http://jetty.eclipse.org:8080/jetty-6")); + assertTrue(result.endsWith("Basic dXNlcjpwYXNzd29yZA==")); + } + finally + { + _httpClient.setProxy(null); + } + } + + + public void testReserveConnections () throws Exception + { + final HttpDestination destination = _httpClient.getDestination (new Address("localhost", _port), _scheme.equalsIgnoreCase("https://")); + final org.eclipse.jetty.client.HttpConnection[] connections = new org.eclipse.jetty.client.HttpConnection[_maxConnectionsPerAddress]; + for (int i=0; i < _maxConnectionsPerAddress; i++) + { + connections[i] = destination.reserveConnection(200); + assertNotNull(connections[i]); + HttpExchange ex = new ContentExchange(); + ex.setURL(_scheme+"localhost:"+_port+"/?i="+i); + ex.setMethod(HttpMethods.GET); + connections[i].send(ex); + } + + //try to get a connection, and only wait 500ms, as we have + //already reserved the max, should return null + org.eclipse.jetty.client.HttpConnection c = destination.reserveConnection(500); + assertNull(c); + + //unreserve first connection + destination.returnConnection(connections[0], false); + + //reserving one should now work + c = destination.reserveConnection(500); + assertNotNull(c); + + + } + public static void copyStream(InputStream in, OutputStream out) + { + try + { + byte[] buffer=new byte[1024]; + int len; + while ((len=in.read(buffer))>=0) + { + out.write(buffer,0,len); + } + } + catch (EofException e) + { + System.err.println(e); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + protected void newServer() throws Exception + { + _server=new Server(); + _server.setGracefulShutdown(500); + _connector=new SelectChannelConnector(); + + _connector.setPort(0); + _server.setConnectors(new Connector[] { _connector }); + } + + protected void startServer() throws Exception + { + newServer(); + _server.setHandler(new AbstractHandler() + { + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + int i=0; + try + { + Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + base_request.setHandled(true); + response.setStatus(200); + _count.incrementAndGet(); + + if (request.getServerName().equals("jetty.eclipse.org")) + { + // System.err.println("HANDLING Proxy"); + response.getOutputStream().println("Proxy request: "+request.getRequestURL()); + response.getOutputStream().println(request.getHeader(HttpHeaders.PROXY_AUTHORIZATION)); + } + else if (request.getMethod().equalsIgnoreCase("GET")) + { + // System.err.println("HANDLING Hello "+request.getRequestURI()); + response.getOutputStream().println(""); + for (; i<100; i++) + { + response.getOutputStream().println(" "+i+""); + } + else + { + // System.err.println("HANDLING "+request.getMethod()); + copyStream(request.getInputStream(),response.getOutputStream()); + } + } + catch(IOException e) + { + e.printStackTrace(); + throw e; + } + catch(Throwable e) + { + e.printStackTrace(); + throw new ServletException(e); + } + finally + { + // System.err.println("HANDLED "+i); + } + } + }); + _server.start(); + _port=_connector.getLocalPort(); + } + + private void stopServer() throws Exception + { + _server.stop(); + } + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java new file mode 100644 index 00000000000..f41b9af06d6 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java @@ -0,0 +1,330 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.client.security.Realm; +import org.eclipse.jetty.client.security.SimpleRealmResolver; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.HashLoginService; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; + +/** + * Functional testing for HttpExchange. + * + * + * + */ +public class SecurityListenerTest extends TestCase +{ + + private Server _server; + private int _port; + private HttpClient _httpClient; + + private Realm _jettyRealm; + private static final String APP_CONTEXT = "localhost /"; + + protected void setUp() throws Exception + { + startServer(); + _httpClient=new HttpClient(); + _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + _httpClient.setMaxConnectionsPerAddress(2); + _httpClient.start(); + + _jettyRealm = new Realm() + { + public String getId() + { + return "MyRealm"; + } + + public String getPrincipal() + { + return "jetty"; + } + + public String getCredentials() + { + return "jetty"; + } + }; + + _httpClient.setRealmResolver( new SimpleRealmResolver(_jettyRealm) ); + } + + protected void tearDown() throws Exception + { + stopServer(); + _httpClient.stop(); + } + + public void xtestPerf() throws Exception + { + sender(1); + Thread.sleep(200); + sender(10); + Thread.sleep(200); + sender(100); + Thread.sleep(200); + sender(1000); + Thread.sleep(200); + sender(10000); + } + + public void sender(final int nb) throws Exception + { + final CountDownLatch latch=new CountDownLatch(nb); + long l0=System.currentTimeMillis(); + for (int i=0; i0) + { + // System.err.println("waiting for "+last+" sent "+(System.currentTimeMillis()-l0)/1000 + "s ago ..."); + latch.await(5,TimeUnit.SECONDS); + long next=latch.getCount(); + if (last==next) + break; + last=next; + } + // System.err.println("missed "+latch.getCount()+" sent "+(System.currentTimeMillis()-l0)/1000 + "s ago."); + assertEquals(0,latch.getCount()); + long l1=System.currentTimeMillis(); + } + + //TODO jaspi hangs ??? +// public void testGetWithContentExchange() throws Exception +// { +// int i = 1; +// +// final CyclicBarrier barrier = new CyclicBarrier(2); +// ContentExchange httpExchange = new ContentExchange() +// { +// protected void onResponseComplete() throws IOException +// { +// super.onResponseComplete(); +// try{barrier.await();}catch(Exception e){} +// } +// }; +// httpExchange.setURL("http://localhost:" + _port + "/?i=" + i); +// httpExchange.setMethod(HttpMethods.GET); +// +// _httpClient.send(httpExchange); +// +// try{barrier.await();}catch(Exception e){} +// +// } + + + public void testDestinationSecurityCaching() throws Exception + { + final CyclicBarrier barrier = new CyclicBarrier(2); + + ContentExchange httpExchange = new ContentExchange() + { + protected void onResponseComplete() throws IOException + { + super.onResponseComplete(); + try{barrier.await();}catch(Exception e){} + } + }; + + httpExchange.setURL("http://localhost:" + _port + "/?i=1"); + httpExchange.setMethod(HttpMethods.GET); + + _httpClient.send(httpExchange); + + try{barrier.await();}catch(Exception e){} + + + barrier.reset(); + ContentExchange httpExchange2 = new ContentExchange() + { + protected void onResponseComplete() throws IOException + { + super.onResponseComplete(); + try{barrier.await();}catch(Exception e){} + } + }; + + httpExchange2.setURL("http://localhost:" + _port + "/?i=2"); + httpExchange2.setMethod(HttpMethods.GET); + + _httpClient.send(httpExchange2); + + try{barrier.await();}catch(Exception e){} + + assertFalse( "exchange was retried", httpExchange2.getRetryStatus() ); + + } + + public static void copyStream(InputStream in, OutputStream out) + { + try + { + byte[] buffer=new byte[1024]; + int len; + while ((len=in.read(buffer))>=0) + { + out.write(buffer,0,len); + } + } + catch (EofException e) + { + System.err.println(e); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + private void startServer() throws Exception + { + _server = new Server(); + _server.setGracefulShutdown(500); + Connector connector = new SelectChannelConnector(); + + connector.setPort(0); + _server.setConnectors(new Connector[]{connector}); + + Constraint constraint = new Constraint(); + constraint.setName("Need User or Admin"); + constraint.setRoles(new String[]{"user", "admin"}); + constraint.setAuthenticate(true); + + ConstraintMapping cm = new ConstraintMapping(); + cm.setConstraint(constraint); + cm.setPathSpec("/*"); + + LoginService loginService = new HashLoginService("MyRealm","src/test/resources/realm.properties"); + ConstraintSecurityHandler sh = new ConstraintSecurityHandler(); + sh.setLoginService(loginService); + sh.setAuthenticator(new BasicAuthenticator()); + + //ServerAuthentication serverAuthentication = new BasicServerAuthentication(loginService, "MyRealm"); + //sh.setServerAuthentication(serverAuthentication); + _server.setHandler(sh); + + Handler testHandler = new AbstractHandler() + { + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + // System.out.println("passed authentication!"); + Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + base_request.setHandled(true); + response.setStatus(200); + if (request.getServerName().equals("jetty.eclipse.org")) + { + response.getOutputStream().println("Proxy request: "+request.getRequestURL()); + } + else if (request.getMethod().equalsIgnoreCase("GET")) + { + response.getOutputStream().println(""); + for (int i=0; i<100; i++) + { + response.getOutputStream().println(" "+i+""); + if (i%20==0) + response.getOutputStream().flush(); + } + response.getOutputStream().println(""); + } + else + { + copyStream(request.getInputStream(),response.getOutputStream()); + } + } + }; + + sh.setHandler(testHandler); + + _server.start(); + _port = connector.getLocalPort(); + } + + + private void stopServer() throws Exception + { + _server.stop(); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java new file mode 100644 index 00000000000..84f4a6e8922 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java @@ -0,0 +1,59 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.File; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ssl.SslSocketConnector; + +/** + * Functional testing for HttpExchange. + * + * + * + */ +public class SslHttpExchangeTest extends HttpExchangeTest +{ + protected void setUp() throws Exception + { + _scheme="https://"; + startServer(); + _httpClient=new HttpClient(); + // _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + _httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); + _httpClient.setMaxConnectionsPerAddress(2); + _httpClient.start(); + } + + protected void newServer() + { + _server = new Server(); + //SslSelectChannelConnector connector = new SslSelectChannelConnector(); + SslSocketConnector connector = new SslSocketConnector(); + + String keystore = System.getProperty("user.dir") + File.separator + "src" + File.separator + "test" + File.separator + "resources" + File.separator + + "keystore"; + + connector.setPort(0); + connector.setKeystore(keystore); + connector.setPassword("storepwd"); + connector.setKeyPassword("keypwd"); + + _server.setConnectors(new Connector[] + { connector }); + _connector=connector; + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java new file mode 100644 index 00000000000..83de795d34b --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java @@ -0,0 +1,232 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.client.security.HashRealmResolver; +import org.eclipse.jetty.client.security.Realm; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.HashLoginService; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.ssl.SslSocketConnector; + +/** + * Functional testing. + */ +public class SslSecurityListenerTest extends TestCase +{ + protected Server _server; + protected int _port; + protected HttpClient _httpClient; + protected Realm _jettyRealm; + protected int _type = HttpClient.CONNECTOR_SOCKET; + private static final String APP_CONTEXT = "localhost /"; + + protected void setUp() throws Exception + { + startServer(); + _httpClient = new HttpClient(); + _httpClient.setConnectorType(_type); + _httpClient.setMaxConnectionsPerAddress(2); + _httpClient.start(); + + _jettyRealm = new Realm() + { + public String getId() + { + return "MyRealm"; + } + + public String getPrincipal() + { + return "jetty"; + } + + public String getCredentials() + { + return "jetty"; + } + }; + + HashRealmResolver resolver = new HashRealmResolver(); + resolver.addSecurityRealm(_jettyRealm); + _httpClient.setRealmResolver(resolver); + } + + protected void tearDown() throws Exception + { + Thread.sleep(1000); + _httpClient.stop(); + Thread.sleep(1000); + stopServer(); + } + + public void testSslGet() throws Exception + { + final CyclicBarrier barrier = new CyclicBarrier(2); + + ContentExchange httpExchange = new ContentExchange(true) + { + protected void onResponseComplete() throws IOException + { + super.onResponseComplete(); + try{barrier.await();}catch(Exception e){} + } + }; + + // httpExchange.setURL("https://dav.codehaus.org/user/jesse/index.html"); + httpExchange.setURL("https://localhost:" + _port + "/"); + httpExchange.setMethod(HttpMethods.GET); + // httpExchange.setRequestHeader("Connection","close"); + + _httpClient.send(httpExchange); + + barrier.await(10000,TimeUnit.SECONDS); + + assertEquals(HttpServletResponse.SC_OK,httpExchange.getResponseStatus()); + + // System.err.println(httpExchange.getResponseContent()); + assertTrue(httpExchange.getResponseContent().length()>400); + + } + + protected void startServer() throws Exception + { + _server = new Server(); + //SslSelectChannelConnector connector = new SslSelectChannelConnector(); + SslSocketConnector connector = new SslSocketConnector(); + + String keystore = System.getProperty("user.dir") + File.separator + "src" + File.separator + "test" + File.separator + "resources" + File.separator + + "keystore"; + + connector.setPort(0); + connector.setKeystore(keystore); + connector.setPassword("storepwd"); + connector.setKeyPassword("keypwd"); + + _server.setConnectors(new Connector[] + { connector }); + + Constraint constraint = new Constraint(); + constraint.setName("Need User or Admin"); + constraint.setRoles(new String[] + { "user", "admin" }); + constraint.setAuthenticate(true); + + ConstraintMapping cm = new ConstraintMapping(); + cm.setConstraint(constraint); + cm.setPathSpec("/*"); + + HashLoginService loginService = new HashLoginService("MyRealm","src/test/resources/realm.properties"); + _server.addBean(loginService); + + BasicAuthenticator authenticator = new BasicAuthenticator(); + + ConstraintSecurityHandler sh = new ConstraintSecurityHandler(); + sh.setAuthenticator(authenticator); + + Set roles = new HashSet(Arrays.asList(new String[]{"user", "admin"})); + sh.setConstraintMappings(new ConstraintMapping[] { cm }, roles); + _server.setHandler(sh); + + Handler testHandler = new AbstractHandler() + { + + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + // System.err.println("passed authentication!\n"+((Request)request).getConnection().getRequestFields()); + + Request base_request = (request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + base_request.setHandled(true); + response.setStatus(200); + response.setContentType("text/plain"); + if (request.getServerName().equals("jetty.eclipse.org")) + { + response.getOutputStream().println("Proxy request: " + request.getRequestURL()); + } + else if (request.getMethod().equalsIgnoreCase("GET")) + { + response.getOutputStream().println(""); + for (int i = 0; i < 100; i++) + { + response.getOutputStream().println(" " + i + ""); + if (i % 20 == 0) + response.getOutputStream().flush(); + } + response.getOutputStream().println(""); + } + else + { + copyStream(request.getInputStream(),response.getOutputStream()); + } + } + }; + + sh.setHandler(testHandler); + + _server.start(); + _port = connector.getLocalPort(); + } + + public static void copyStream(InputStream in, OutputStream out) + { + try + { + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) >= 0) + { + out.write(buffer,0,len); + } + } + catch (EofException e) + { + System.err.println(e); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + private void stopServer() throws Exception + { + _server.stop(); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/WebdavListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/WebdavListenerTest.java new file mode 100644 index 00000000000..6917af4b55e --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/WebdavListenerTest.java @@ -0,0 +1,147 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.File; + +import junit.framework.TestCase; + +import org.eclipse.jetty.client.security.Realm; +import org.eclipse.jetty.client.security.SimpleRealmResolver; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; + +/** + * Functional testing for HttpExchange. + * + * + * + */ +public class WebdavListenerTest extends TestCase//extends HttpExchangeTest +{ + protected String _scheme = "http://"; + protected Server _server; + protected int _port; + protected HttpClient _httpClient; + protected Connector _connector; + + private String _username = "janb"; + private String _password = "xxxxx"; + + private String _singleFileURL; + private String _dirFileURL; + private String _dirURL; + + + + + protected void setUp() throws Exception + { + _singleFileURL = "https://dav.codehaus.org/user/" + _username + "/foo.txt"; + _dirURL = "https://dav.codehaus.org/user/" + _username + "/ttt/"; + _dirFileURL = _dirURL+"foo.txt"; + _scheme="https://"; + + _httpClient=new HttpClient(); + _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + //_httpClient.setMaxConnectionsPerAddress(4); + + _httpClient.setRealmResolver( new SimpleRealmResolver ( + new Realm(){ + public String getId() + { + return _username + "'s webspace"; //To change body of implemented methods use File | Settings | File Templates. + } + + public String getPrincipal() + { + return _username; //To change body of implemented methods use File | Settings | File Templates. + } + + public String getCredentials() + { + return _password; //To change body of implemented methods use File | Settings | File Templates. + } + } + )); + + _httpClient.registerListener( "org.eclipse.jetty.client.webdav.WebdavListener"); + _httpClient.start(); + } + + + public void tearDown () throws Exception + { + _httpClient.stop(); + } + + + public void testPUTandDELETEwithSSL() throws Exception + { + File file = new File("src/test/resources/foo.txt"); + assertTrue(file.exists()); + + + /* + * UNCOMMENT TO TEST WITH REAL DAV SERVER + * Remember to set _username and _password to a real user's account. + * + */ + /* + //PUT a FILE + ContentExchange singleFileExchange = new ContentExchange(); + singleFileExchange.setURL(_singleFileURL); + singleFileExchange.setMethod( HttpMethods.PUT ); + singleFileExchange.setFileForUpload(file); + singleFileExchange.setRequestHeader( "Content-Type", "application/octet-stream"); + singleFileExchange.setRequestHeader("Content-Length", String.valueOf( file.length() )); + _httpClient.send(singleFileExchange); + singleFileExchange.waitForDone(); + + String result = singleFileExchange.getResponseContent(); + assertEquals(201, singleFileExchange.getResponseStatus()); + + + //PUT a FILE in a directory hierarchy + ContentExchange dirFileExchange = new ContentExchange(); + dirFileExchange.setURL(_dirFileURL); + dirFileExchange.setMethod( HttpMethods.PUT ); + dirFileExchange.setFileForUpload(file); + dirFileExchange.setRequestHeader( "Content-Type", "application/octet-stream"); + dirFileExchange.setRequestHeader("Content-Length", String.valueOf( file.length() )); + _httpClient.send(dirFileExchange); + dirFileExchange.waitForDone(); + result = dirFileExchange.getResponseContent(); + assertEquals(201, singleFileExchange.getResponseStatus()); + + + + + //DELETE the single file + HttpExchange del = new HttpExchange(); + del.setURL(_singleFileURL); + del.setMethod(HttpMethods.DELETE); + _httpClient.send(del); + del.waitForCompletion(); + + //DELETE the whole dir + del.setURL(_dirURL); + del.setMethod(HttpMethods.DELETE); + del.setRequestHeader("Depth", "infinity"); + _httpClient.send(del); + del.waitForCompletion(); + */ + } + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/security/SecurityResolverTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/security/SecurityResolverTest.java new file mode 100644 index 00000000000..7c3386093b4 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/security/SecurityResolverTest.java @@ -0,0 +1,45 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client.security; + +import junit.framework.TestCase; + +public class SecurityResolverTest extends TestCase +{ + public void testNothing() + { + + } + /* TODO + + public void testCredentialParsing() throws Exception + { + SecurityListener resolver = new SecurityListener(); + Buffer value = new ByteArrayBuffer("basic a=b".getBytes()); + + assertEquals( "basic", resolver.scrapeAuthenticationType( value.toString() ) ); + assertEquals( 1, resolver.scrapeAuthenticationDetails( value.toString() ).size() ); + + value = new ByteArrayBuffer("digest a=boo, c=\"doo\" , egg=foo".getBytes()); + + assertEquals( "digest", resolver.scrapeAuthenticationType( value.toString() ) ); + Map testMap = resolver.scrapeAuthenticationDetails( value.toString() ); + assertEquals( 3, testMap.size() ); + assertEquals( "boo", testMap.get("a") ); + assertEquals( "doo", testMap.get("c") ); + assertEquals( "foo", testMap.get("egg") ); + } + + */ +} diff --git a/jetty-client/src/test/resources/foo.txt b/jetty-client/src/test/resources/foo.txt new file mode 100644 index 00000000000..58b78820701 --- /dev/null +++ b/jetty-client/src/test/resources/foo.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jetty-client/src/test/resources/keystore b/jetty-client/src/test/resources/keystore new file mode 100644 index 0000000000000000000000000000000000000000..b727bd0fb777fddb3463c81cb56963a7541f7b45 GIT binary patch literal 1426 zcmezO_TO6u1_mY|W&~r_+{*0KN+9!5EW=$#pv*3VCZ=r$d~96WY>X_7T1LX-9cnmv_92vyZY^nksPD zdgGz_%YMiA%<+G`?zc{_^OTmKsam}GdFt7neK!{P|Bco8Yq|9PvTGT0HqQC!@x9l1 z+kfrLjk$U4PfrzX4>?lEY_X)NU`c_upzVKwLzZV_Z=OB7;%H;~#q=-kB7C15mGAK^ zvQDWpwV1Q=%|)J*vS+h8eD_X&KBLa;y!Ym4fd?y?u6<@yiQRcgW4YSWJ1qI-Q@4qE za4m1Vxao>O&#^E1)!s>&+m1~AFk#ErRgE9c$R|1RHZn>ST)F2PCX`h;GftH|P3C*s zWzB^ZGW^U2Io6G9jl> z6zzz+mrM40Funf&SL@T49sAWnZvUNcc-}kaM`;+(`I*O}e=OTwz2vdB%C^t0KV2dN z8|5bLIJC=m`3p1S>EdUM&gly9o_)k?62R=&u_P~(KYzPEv+V7u$GBF7Yt*targsEg zTQzgVqnKmgxtO%H)!0fto_uWX^v1c;QdM?#toT{0*tgvCKBx=r`VraapZ2`+cgggB z-Pvqw9={ifyv(DLz=%jkh_cpEkNBn#fQtx!K-V#22ApgPb-K(wNrdO3Et=(zk zH%r#4Uj9|=lZlRA5;d`xvpUaban7~tb)L?vqO(?31t6P_KXXUmI|NTdEW^*XD%W$kc6J6C`ZJL_TRc(ux#yKj=Ue_0-Rb!XA^BcIEU@^esCUfRc7B*q_(7f!t{PH}Q2nU7; zJ3>UpKnkRbOIX-9zos$!bwcyY3l3?6uy+&YLB9AjrXwMJ9=TVxnh^gTTRrgQDAx z_HixoIbPSC|G?I^w5omSz6+C2WR&Q742Af!tZ{F_B0c(^#Lh& zza|JWF*7nSB0CQl;mkmH*$SFDWnY}}>VU(o3#<5V+huGG`Dn2??1_qdd2)==i--F? zs#KSqOx?Wrm7jRA(!0Yuu5HVhrWZZce5Lb}5Bq8!uvR=V-JM%7`INM|?JE9h{&PR9 zPS5OYv=7|qeAeX{<0}8%Th2N96wN*x(D1*r!0!3JEkDAd&tKmoUZ_-IpH*z9SS: [, ...] +# +# Passwords may be clear text, obfuscated or checksummed. The class +# org.eclipse.util.Password should be used to generate obfuscated +# passwords or password checksums +# +# If DIGEST Authentication is used, the password must be in a recoverable +# format, either plain text or OBF:. +# +# if using digest authentication, do not MD5-hash the password +jetty: jetty,user +admin: CRYPT:ad1ks..kc.1Ug,server-administrator,content-administrator,admin,user +other: OBF:1xmk1w261u9r1w1c1xmq,user +plain: plain,user +user: password,user + +# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password +digest: MD5:6e120743ad67abfbc385bc2bb754e297,user diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml new file mode 100644 index 00000000000..71faefadd27 --- /dev/null +++ b/jetty-deploy/pom.xml @@ -0,0 +1,78 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-deploy + Jetty :: Deployers + Jetty deployers + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.deploy + J2SE-1.5 + org.eclipse.jetty.deploy;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + org.eclipse.jetty + jetty-webapp + ${project.version} + + + org.eclipse.jetty + jetty-xml + ${project.version} + + + junit + junit + test + + + diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ConfigurationManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ConfigurationManager.java new file mode 100644 index 00000000000..df78fb58c30 --- /dev/null +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ConfigurationManager.java @@ -0,0 +1,28 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.deploy; + +import java.util.Map; + +/** + * ConfigurationManager + * + * Type for allow injection of property values + * for replacement in jetty xml files during deployment. + */ +public interface ConfigurationManager +{ + public Map getProperties(); + +} diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ContextDeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ContextDeployer.java new file mode 100644 index 00000000000..74015e5ecb9 --- /dev/null +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ContextDeployer.java @@ -0,0 +1,371 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.deploy; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.util.Scanner; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.xml.XmlConfiguration; + +/** + * Context Deployer + * + * This deployer scans a designated directory by + * {@link #setConfigurationDir(String)} for the appearance/disappearance or + * changes to xml configuration files. The scan is performed at startup and at + * an optional hot deployment frequency specified by + * {@link #setScanInterval(int)}. By default, the scanning is NOT recursive, + * but can be made so by {@link #setRecursive(boolean)}. + * + * Each configuration file is in {@link XmlConfiguration} format and represents + * the configuration of a instance of {@link ContextHandler} (or a subclass + * specified by the XML Configure element). + * + * The xml should configure the context and the instance is deployed to the + * {@link ContextHandlerCollection} specified by {@link #setContexts(Server)}. + * + * Similarly, when one of these existing files is removed, the corresponding + * context is undeployed; when one of these files is changed, the corresponding + * context is undeployed, the (changed) xml config file reapplied to it, and + * then (re)deployed. + * + * Note that the context itself is NOT copied into the hot deploy directory. The + * webapp directory or war file can exist anywhere. It is the xml config file + * that points to it's location and deploys it from there. + * + * It means, for example, that you can keep a "read-only" copy of your webapp + * somewhere, and apply different configurations to it simply by dropping + * different xml configuration files into the configuration directory. + * + * @org.apache.xbean.XBean element="hotDeployer" description="Creates a hot deployer + * to watch a directory for changes at a configurable interval." + * + */ +public class ContextDeployer extends AbstractLifeCycle +{ + public final static String NAME="ConfiguredDeployer"; + private int _scanInterval=10; + private Scanner _scanner; + private ScannerListener _scannerListener; + private Resource _configurationDir; + private Map _currentDeployments=new HashMap(); + private ContextHandlerCollection _contexts; + private ConfigurationManager _configMgr; + private boolean _recursive = false; + + /* ------------------------------------------------------------ */ + protected class ScannerListener implements Scanner.DiscreteListener + { + /** + * Handle a new deployment + * + * @see org.eclipse.jetty.util.Scanner.FileAddedListener#fileAdded(java.lang.String) + */ + public void fileAdded(String filename) throws Exception + { + deploy(filename); + } + + /** + * Handle a change to an existing deployment. Undeploy then redeploy. + * + * @see org.eclipse.jetty.util.Scanner.FileChangedListener#fileChanged(java.lang.String) + */ + public void fileChanged(String filename) throws Exception + { + redeploy(filename); + } + + /** + * Handle an undeploy. + * + * @see org.eclipse.jetty.util.Scanner.FileRemovedListener#fileRemoved(java.lang.String) + */ + public void fileRemoved(String filename) throws Exception + { + undeploy(filename); + } + public String toString() + { + return "ContextDeployer$Scanner"; + } + } + + /** + * Constructor + * + * @throws Exception + */ + public ContextDeployer() throws Exception + { + _scanner=new Scanner(); + } + + /* ------------------------------------------------------------ */ + /** + * @return the ContextHandlerColletion to which to deploy the contexts + */ + public ContextHandlerCollection getContexts() + { + return _contexts; + } + + /* ------------------------------------------------------------ */ + /** + * Associate with a {@link ContextHandlerCollection}. + * + * @param contexts + * the ContextHandlerColletion to which to deploy the contexts + */ + public void setContexts(ContextHandlerCollection contexts) + { + if (isStarted()||isStarting()) + throw new IllegalStateException("Cannot set Contexts after deployer start"); + _contexts=contexts; + } + + /* ------------------------------------------------------------ */ + /** + * @param seconds + * The period in second between scans for changed configuration + * files. A zero or negative interval disables hot deployment + */ + public void setScanInterval(int seconds) + { + if (isStarted()||isStarting()) + throw new IllegalStateException("Cannot change scan interval after deployer start"); + _scanInterval=seconds; + } + + /* ------------------------------------------------------------ */ + public int getScanInterval() + { + return _scanInterval; + } + + /* ------------------------------------------------------------ */ + /** + * @param dir + * @throws Exception + */ + public void setConfigurationDir(String dir) throws Exception + { + setConfigurationDir(Resource.newResource(dir)); + } + + /* ------------------------------------------------------------ */ + /** + * @param file + * @throws Exception + */ + public void setConfigurationDir(File file) throws Exception + { + setConfigurationDir(Resource.newResource(file.toURL())); + } + + /* ------------------------------------------------------------ */ + /** + * @param resource + */ + public void setConfigurationDir(Resource resource) + { + if (isStarted()||isStarting()) + throw new IllegalStateException("Cannot change hot deploy dir after deployer start"); + _configurationDir=resource; + } + + /* ------------------------------------------------------------ */ + /** + * @param directory + */ + public void setDirectory(String directory) throws Exception + { + setConfigurationDir(directory); + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public String getDirectory() + { + return getConfigurationDir().getName(); + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public Resource getConfigurationDir() + { + return _configurationDir; + } + + /* ------------------------------------------------------------ */ + /** + * @param configMgr + */ + public void setConfigurationManager(ConfigurationManager configMgr) + { + _configMgr=configMgr; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public ConfigurationManager getConfigurationManager() + { + return _configMgr; + } + + + public void setRecursive (boolean recursive) + { + _recursive=recursive; + } + + public boolean getRecursive () + { + return _recursive; + } + + public boolean isRecursive() + { + return _recursive; + } + /* ------------------------------------------------------------ */ + private void deploy(String filename) throws Exception + { + ContextHandler context=createContext(filename); + Log.info("Deploy "+filename+" -> "+ context); + _contexts.addHandler(context); + _currentDeployments.put(filename,context); + if (_contexts.isStarted()) + context.start(); + } + + /* ------------------------------------------------------------ */ + private void undeploy(String filename) throws Exception + { + ContextHandler context=(ContextHandler)_currentDeployments.get(filename); + Log.info("Undeploy "+filename+" -> "+context); + if (context==null) + return; + context.stop(); + _contexts.removeHandler(context); + _currentDeployments.remove(filename); + } + + /* ------------------------------------------------------------ */ + private void redeploy(String filename) throws Exception + { + undeploy(filename); + deploy(filename); + } + + /* ------------------------------------------------------------ */ + /** + * Start the hot deployer looking for webapps to deploy/undeploy + * + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + if (_configurationDir==null) + throw new IllegalStateException("No configuration dir specified"); + + if (_contexts==null) + throw new IllegalStateException("No context handler collection specified for deployer"); + + _scanner.setScanDir(_configurationDir.getFile()); + _scanner.setScanInterval(getScanInterval()); + _scanner.setRecursive(_recursive); //only look in the top level for deployment files? + // Accept changes only in files that could be a deployment descriptor + _scanner.setFilenameFilter(new FilenameFilter() + { + public boolean accept(File dir, String name) + { + try + { + if (name.endsWith(".xml")&&dir.equals(getConfigurationDir().getFile())) + return true; + return false; + } + catch (IOException e) + { + Log.warn(e); + return false; + } + } + }); + _scannerListener=new ScannerListener(); + _scanner.addListener(_scannerListener); + _scanner.scan(); + _scanner.start(); + _contexts.getServer().getContainer().addBean(_scanner); + } + + /* ------------------------------------------------------------ */ + /** + * Stop the hot deployer. + * + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() + */ + protected void doStop() throws Exception + { + _scanner.removeListener(_scannerListener); + _scanner.stop(); + } + + /* ------------------------------------------------------------ */ + /** + * Create a WebAppContext for the webapp being hot deployed, then apply the + * xml config file to it to configure it. + * + * @param filename + * the config file found in the hot deploy directory + * @return + * @throws Exception + */ + private ContextHandler createContext(String filename) throws Exception + { + // The config file can call any method on WebAppContext to configure + // the webapp being deployed. + Resource resource = Resource.newResource(filename); + if (!resource.exists()) + return null; + + XmlConfiguration xmlConfiguration=new XmlConfiguration(resource.getURL()); + HashMap properties = new HashMap(); + properties.put("Server", _contexts.getServer()); + if (_configMgr!=null) + properties.putAll(_configMgr.getProperties()); + + xmlConfiguration.setProperties(properties); + ContextHandler context=(ContextHandler)xmlConfiguration.configure(); + return context; + } + +} diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java new file mode 100644 index 00000000000..ab47943523a --- /dev/null +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java @@ -0,0 +1,69 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.deploy; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.jetty.util.resource.Resource; + +/** + * FileConfigurationManager + * + * Supplies properties defined in a file. + */ +public class FileConfigurationManager implements ConfigurationManager +{ + private Resource _file; + private Properties _properties = new Properties(); + + public FileConfigurationManager() + { + } + + + public void setFile (String filename) + throws MalformedURLException, IOException + { + _file = Resource.newResource(filename); + } + + + /** + * @see org.eclipse.jetty.deploy.ConfigurationManager#getProperties() + */ + public Map getProperties() + { + try + { + loadProperties(); + return _properties; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + + private void loadProperties () + throws FileNotFoundException, IOException + { + if (_properties.isEmpty()) + _properties.load(_file.getInputStream()); + } +} diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java new file mode 100644 index 00000000000..13c44501be7 --- /dev/null +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java @@ -0,0 +1,253 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.deploy; + +import java.util.ArrayList; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; + +/** + * Web Application Deployer. + * + * The class searches a directory for and deploys standard web application. + * At startup, the directory specified by {@link #setWebAppDir(String)} is searched + * for subdirectories (excluding hidden and CVS) or files ending with ".zip" + * or "*.war". For each webapp discovered is passed to a new instance + * of {@link WebAppContext} (or a subclass specified by {@link #getContexts()}. + * {@link ContextHandlerCollection#getContextClass()} + * + * This deployer does not do hot deployment or undeployment. Nor does + * it support per webapplication configuration. For these features + * see {@link ContextDeployer}. + * + * @see {@link ContextDeployer} + */ +public class WebAppDeployer extends AbstractLifeCycle +{ + private HandlerContainer _contexts; + private String _webAppDir; + private String _defaultsDescriptor; + private String[] _configurationClasses; + private boolean _extract; + private boolean _parentLoaderPriority; + private boolean _allowDuplicates; + private ArrayList _deployed; + + public String[] getConfigurationClasses() + { + return _configurationClasses; + } + + public void setConfigurationClasses(String[] configurationClasses) + { + _configurationClasses=configurationClasses; + } + + public HandlerContainer getContexts() + { + return _contexts; + } + + public void setContexts(HandlerContainer contexts) + { + _contexts=contexts; + } + + public String getDefaultsDescriptor() + { + return _defaultsDescriptor; + } + + public void setDefaultsDescriptor(String defaultsDescriptor) + { + _defaultsDescriptor=defaultsDescriptor; + } + + public boolean isExtract() + { + return _extract; + } + + public void setExtract(boolean extract) + { + _extract=extract; + } + + public boolean isParentLoaderPriority() + { + return _parentLoaderPriority; + } + + public void setParentLoaderPriority(boolean parentPriorityClassLoading) + { + _parentLoaderPriority=parentPriorityClassLoading; + } + + public String getWebAppDir() + { + return _webAppDir; + } + + public void setWebAppDir(String dir) + { + _webAppDir=dir; + } + + public boolean getAllowDuplicates() + { + return _allowDuplicates; + } + + /* ------------------------------------------------------------ */ + /** + * @param allowDuplicates If false, do not deploy webapps that have already been deployed or duplicate context path + */ + public void setAllowDuplicates(boolean allowDuplicates) + { + _allowDuplicates=allowDuplicates; + } + + /* ------------------------------------------------------------ */ + /** + * @throws Exception + */ + public void doStart() throws Exception + { + _deployed=new ArrayList(); + + scan(); + + } + + /* ------------------------------------------------------------ */ + /** Scan for webapplications. + * + * @throws Exception + */ + public void scan() throws Exception + { + if (_contexts==null) + throw new IllegalArgumentException("No HandlerContainer"); + + Resource r=Resource.newResource(_webAppDir); + if (!r.exists()) + throw new IllegalArgumentException("No such webapps resource "+r); + + if (!r.isDirectory()) + throw new IllegalArgumentException("Not directory webapps resource "+r); + + String[] files=r.list(); + + files: for (int f=0; files!=null&&f0) + context=context.substring(0,context.length()-1); + + // Check the context path has not already been added or the webapp itself is not already deployed + if (!_allowDuplicates) + { + Handler[] installed=_contexts.getChildHandlersByClass(ContextHandler.class); + for (int i=0; i0;) + { + ContextHandler wac = (ContextHandler)_deployed.get(i); + wac.stop();// TODO Multi exception + } + } +} diff --git a/jetty-eclipse-codetemplates.xml b/jetty-eclipse-codetemplates.xml new file mode 100644 index 00000000000..60d8fd517e3 --- /dev/null +++ b/jetty-eclipse-codetemplates.xml @@ -0,0 +1,50 @@ + diff --git a/jetty-eclipse-java-format.xml b/jetty-eclipse-java-format.xml new file mode 100644 index 00000000000..18223165684 --- /dev/null +++ b/jetty-eclipse-java-format.xml @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml new file mode 100644 index 00000000000..df29a98400c --- /dev/null +++ b/jetty-http/pom.xml @@ -0,0 +1,70 @@ + + + jetty-project + org.eclipse.jetty + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + org.eclipse.jetty + jetty-http + Jetty :: Http Utility + + + org.eclipse.jetty + jetty-io + ${project.version} + + + junit + junit + test + + + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.http + J2SE-1.5 + http://jetty.eclipse.org + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + artifact-jar + + jar + + + + test-jar + + test-jar + + + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java new file mode 100644 index 00000000000..69cc8c356f7 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java @@ -0,0 +1,491 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** + * Abstract Generator. Builds HTTP Messages. + * + * Currently this class uses a system parameter "jetty.direct.writers" to control + * two optional writer to byte conversions. buffer.writers=true will probably be + * faster, but will consume more memory. This option is just for testing and tuning. + * + * + * + */ +public abstract class AbstractGenerator implements Generator +{ + // states + public final static int STATE_HEADER = 0; + public final static int STATE_CONTENT = 2; + public final static int STATE_FLUSHING = 3; + public final static int STATE_END = 4; + + public static final byte[] NO_BYTES = {}; + public static final int MAX_OUTPUT_CHARS = 512; + + // data + + protected final Buffers _buffers; // source of buffers + protected final EndPoint _endp; + protected final int _headerBufferSize; + protected int _contentBufferSize; + + protected int _state = STATE_HEADER; + + protected int _status = 0; + protected int _version = HttpVersions.HTTP_1_1_ORDINAL; + protected Buffer _reason; + protected Buffer _method; + protected String _uri; + + protected long _contentWritten = 0; + protected long _contentLength = HttpTokens.UNKNOWN_CONTENT; + protected boolean _last = false; + protected boolean _head = false; + protected boolean _noContent = false; + protected boolean _close = false; + + + protected Buffer _header; // Buffer for HTTP header (and maybe small _content) + protected Buffer _buffer; // Buffer for copy of passed _content + protected Buffer _content; // Buffer passed to addContent + + private boolean _sendServerVersion; + + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + * + * @param buffers buffer pool + * @param headerBufferSize Size of the buffer to allocate for HTTP header + * @param contentBufferSize Size of the buffer to allocate for HTTP content + */ + public AbstractGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize) + { + this._buffers = buffers; + this._endp = io; + _headerBufferSize=headerBufferSize; + _contentBufferSize=contentBufferSize; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isOpen() + { + return _endp.isOpen(); + } + + /* ------------------------------------------------------------------------------- */ + public void reset(boolean returnBuffers) + { + _state = STATE_HEADER; + _status = 0; + _version = HttpVersions.HTTP_1_1_ORDINAL; + _reason = null; + _last = false; + _head = false; + _noContent=false; + _close = false; + _contentWritten = 0; + _contentLength = HttpTokens.UNKNOWN_CONTENT; + + synchronized(this) + { + if (returnBuffers) + { + if (_header != null) + _buffers.returnBuffer(_header); + _header = null; + if (_buffer != null) + _buffers.returnBuffer(_buffer); + _buffer = null; + } + else + { + if (_header != null) + _header.clear(); + + if (_buffer != null) + { + _buffers.returnBuffer(_buffer); + _buffer = null; + } + } + } + _content = null; + _method=null; + } + + /* ------------------------------------------------------------------------------- */ + public void resetBuffer() + { + if(_state>=STATE_FLUSHING) + throw new IllegalStateException("Flushed"); + + _last = false; + _close = false; + _contentWritten = 0; + _contentLength = HttpTokens.UNKNOWN_CONTENT; + _content=null; + if (_buffer!=null) + _buffer.clear(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the contentBufferSize. + */ + public int getContentBufferSize() + { + return _contentBufferSize; + } + + /* ------------------------------------------------------------ */ + /** + * @param contentBufferSize The contentBufferSize to set. + */ + public void increaseContentBufferSize(int contentBufferSize) + { + if (contentBufferSize > _contentBufferSize) + { + _contentBufferSize = contentBufferSize; + if (_buffer != null) + { + Buffer nb = _buffers.getBuffer(_contentBufferSize); + nb.put(_buffer); + _buffers.returnBuffer(_buffer); + _buffer = nb; + } + } + } + + /* ------------------------------------------------------------ */ + public Buffer getUncheckedBuffer() + { + return _buffer; + } + + /* ------------------------------------------------------------ */ + public boolean getSendServerVersion () + { + return _sendServerVersion; + } + + /* ------------------------------------------------------------ */ + public void setSendServerVersion (boolean sendServerVersion) + { + _sendServerVersion = sendServerVersion; + } + + /* ------------------------------------------------------------ */ + public int getState() + { + return _state; + } + + /* ------------------------------------------------------------ */ + public boolean isState(int state) + { + return _state == state; + } + + /* ------------------------------------------------------------ */ + public boolean isComplete() + { + return _state == STATE_END; + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return _state == STATE_HEADER && _method==null && _status==0; + } + + /* ------------------------------------------------------------ */ + public boolean isCommitted() + { + return _state != STATE_HEADER; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the head. + */ + public boolean isHead() + { + return _head; + } + + /* ------------------------------------------------------------ */ + public void setContentLength(long value) + { + if (value<0) + _contentLength=HttpTokens.UNKNOWN_CONTENT; + else + _contentLength=value; + } + + /* ------------------------------------------------------------ */ + /** + * @param head The head to set. + */ + public void setHead(boolean head) + { + _head = head; + } + + /* ------------------------------------------------------------ */ + /** + * @return false if the connection should be closed after a request has been read, + * true if it should be used for additional requests. + */ + public boolean isPersistent() + { + return !_close; + } + + /* ------------------------------------------------------------ */ + public void setPersistent(boolean persistent) + { + _close=!persistent; + } + + /* ------------------------------------------------------------ */ + /** + * @param version The version of the client the response is being sent to (NB. Not the version + * in the response, which is the version of the server). + */ + public void setVersion(int version) + { + if (_state != STATE_HEADER) + throw new IllegalStateException("STATE!=START "+_state); + _version = version; + if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null) + _noContent=true; + } + + /* ------------------------------------------------------------ */ + public int getVersion() + { + return _version; + } + + /* ------------------------------------------------------------ */ + /** + */ + public void setRequest(String method, String uri) + { + if (method==null || HttpMethods.GET.equals(method) ) + _method=HttpMethods.GET_BUFFER; + else + _method=HttpMethods.CACHE.lookup(method); + _uri=uri; + if (_version==HttpVersions.HTTP_0_9_ORDINAL) + _noContent=true; + } + + /* ------------------------------------------------------------ */ + /** + * @param status The status code to send. + * @param reason the status message to send. + */ + public void setResponse(int status, String reason) + { + if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START"); + _method=null; + _status = status; + if (reason!=null) + { + int len=reason.length(); + if (len>_headerBufferSize/2) + len=_headerBufferSize/2; + _reason=new ByteArrayBuffer(len); + for (int i=0;i0; + } + + /* ------------------------------------------------------------ */ + public boolean isContentWritten() + { + return _contentLength>=0 && _contentWritten>=_contentLength; + } + + /* ------------------------------------------------------------ */ + public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException; + + /* ------------------------------------------------------------ */ + /** + * Complete the message. + * + * @throws IOException + */ + public void complete() throws IOException + { + if (_state == STATE_HEADER) + { + throw new IllegalStateException("State==HEADER"); + } + + if (_contentLength >= 0 && _contentLength != _contentWritten && !_head) + { + if (Log.isDebugEnabled()) + Log.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength); + _close = true; + } + } + + /* ------------------------------------------------------------ */ + public abstract long flushBuffer() throws IOException; + + + /* ------------------------------------------------------------ */ + public void flush(long maxIdleTime) throws IOException + { + // block until everything is flushed + Buffer content = _content; + Buffer buffer = _buffer; + if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull()) + { + flushBuffer(); + + while ((content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _endp.isOpen()) + blockForOutput(maxIdleTime); + } + } + + /* ------------------------------------------------------------ */ + /** + * Utility method to send an error response. If the builder is not committed, this call is + * equivalent to a setResponse, addcontent and complete call. + * + * @param code + * @param reason + * @param content + * @param close + * @throws IOException + */ + public void sendError(int code, String reason, String content, boolean close) throws IOException + { + if (!isCommitted()) + { + setResponse(code, reason); + _close = close; + completeHeader(null, false); + if (content != null) + addContent(new View(new ByteArrayBuffer(content)), Generator.LAST); + complete(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the contentWritten. + */ + public long getContentWritten() + { + return _contentWritten; + } + + + + /* ------------------------------------------------------------ */ + public void blockForOutput(long maxIdleTime) throws IOException + { + if (_endp.isBlocking()) + { + try + { + flushBuffer(); + } + catch(IOException e) + { + _endp.close(); + throw e; + } + } + else + { + if (!_endp.blockWritable(maxIdleTime)) + { + _endp.close(); + throw new EofException("timeout"); + } + + flushBuffer(); + } + } + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/EncodedHttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/EncodedHttpURI.java new file mode 100644 index 00000000000..8e31e65e4d1 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/EncodedHttpURI.java @@ -0,0 +1,163 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.UnsupportedEncodingException; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.Utf8StringBuffer; + +public class EncodedHttpURI extends HttpURI +{ + private String _encoding; + + public EncodedHttpURI(String encoding) + { + super(); + _encoding = encoding; + } + + + public String getScheme() + { + if (_scheme==_authority) + return null; + int l=_authority-_scheme; + if (l==5 && + _raw[_scheme]=='h' && + _raw[_scheme+1]=='t' && + _raw[_scheme+2]=='t' && + _raw[_scheme+3]=='p' ) + return HttpSchemes.HTTP; + if (l==6 && + _raw[_scheme]=='h' && + _raw[_scheme+1]=='t' && + _raw[_scheme+2]=='t' && + _raw[_scheme+3]=='p' && + _raw[_scheme+4]=='s' ) + return HttpSchemes.HTTPS; + + return StringUtil.toString(_raw,_scheme,_authority-_scheme-1,_encoding); + } + + public String getAuthority() + { + if (_authority==_path) + return null; + return StringUtil.toString(_raw,_authority,_path-_authority,_encoding); + } + + public String getHost() + { + if (_host==_port) + return null; + return StringUtil.toString(_raw,_host,_port-_host,_encoding); + } + + public int getPort() + { + if (_port==_path) + return -1; + return TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10); + } + + public String getPath() + { + if (_path==_param) + return null; + return StringUtil.toString(_raw,_path,_param-_path,_encoding); + } + + public String getDecodedPath() + { + if (_path==_param) + return null; + return URIUtil.decodePath(_raw,_path,_param-_path); + } + + public String getPathAndParam() + { + if (_path==_query) + return null; + return StringUtil.toString(_raw,_path,_query-_path,_encoding); + } + + public String getCompletePath() + { + if (_path==_end) + return null; + return StringUtil.toString(_raw,_path,_end-_path,_encoding); + } + + public String getParam() + { + if (_param==_query) + return null; + return StringUtil.toString(_raw,_param+1,_query-_param-1,_encoding); + } + + public String getQuery() + { + if (_query==_fragment) + return null; + return StringUtil.toString(_raw,_query+1,_fragment-_query-1,_encoding); + } + + public boolean hasQuery() + { + return (_fragment>_query); + } + + public String getFragment() + { + if (_fragment==_end) + return null; + return StringUtil.toString(_raw,_fragment+1,_end-_fragment-1,_encoding); + } + + public void decodeQueryTo(MultiMap parameters) + { + if (_query==_fragment) + return; + UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,_encoding),parameters,_encoding); + } + + public void decodeQueryTo(MultiMap parameters, String encoding) + throws UnsupportedEncodingException + { + if (_query==_fragment) + return; + + if (encoding==null) + encoding=_encoding; + UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding); + } + + public String toString() + { + if (_rawString==null) + _rawString= StringUtil.toString(_raw,_scheme,_end-_scheme,_encoding); + return _rawString; + } + + public void writeTo(Utf8StringBuffer buf) + { + buf.getStringBuffer().append(toString()); + } + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java new file mode 100644 index 00000000000..690c30acdbf --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java @@ -0,0 +1,95 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.http; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; + +public interface Generator +{ + public static final boolean LAST=true; + public static final boolean MORE=false; + + /* ------------------------------------------------------------ */ + /** + * Add content. + * + * @param content + * @param last + * @throws IllegalArgumentException if content is {@link Buffer#isImmutable immutable}. + * @throws IllegalStateException If the request is not expecting any more content, + * or if the buffers are full and cannot be flushed. + * @throws IOException if there is a problem flushing the buffers. + */ + void addContent(Buffer content, boolean last) throws IOException; + + /* ------------------------------------------------------------ */ + /** + * Add content. + * + * @param b byte + * @return true if the buffers are full + * @throws IOException + */ + boolean addContent(byte b) throws IOException; + + void complete() throws IOException; + + void completeHeader(HttpFields responseFields, boolean last) throws IOException; + + long flushBuffer() throws IOException; + + int getContentBufferSize(); + + long getContentWritten(); + + boolean isContentWritten(); + + void increaseContentBufferSize(int size); + + boolean isBufferFull(); + + boolean isCommitted(); + + boolean isComplete(); + + boolean isPersistent(); + + void reset(boolean returnBuffers); + + void resetBuffer(); + + void sendError(int code, String reason, String content, boolean close) throws IOException; + + void setHead(boolean head); + + void setRequest(String method, String uri); + + void setResponse(int status, String reason); + + + void setSendServerVersion(boolean sendServerVersion); + + void setVersion(int version); + + boolean isIdle(); + + void setContentLength(long length); + + void setPersistent(boolean persistent); + + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java new file mode 100644 index 00000000000..93e1e4c1505 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java @@ -0,0 +1,36 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** HttpContent. + * + * + */ +public interface HttpContent +{ + Buffer getContentType(); + Buffer getLastModified(); + Buffer getBuffer(); + Resource getResource(); + long getContentLength(); + InputStream getInputStream() throws IOException; + void release(); +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java new file mode 100644 index 00000000000..7f0d5bd0f4b --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java @@ -0,0 +1,186 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +public class HttpCookie +{ + private final String _name; + private final String _value; + private final String _comment; + private final String _domain; + private final int _maxAge; + private final String _path; + private final boolean _secure; + private final int _version; + private final boolean _httpOnly; + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value) + { + super(); + _name = name; + _value = value; + _comment = null; + _domain = null; + _httpOnly = false; + _maxAge = -1; + _path = null; + _secure = false; + _version = 0; + } + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value, String domain, String path) + { + super(); + _name = name; + _value = value; + _comment = null; + _domain = domain; + _httpOnly = false; + _maxAge = -1; + _path = path; + _secure = false; + _version = 0; + + } + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value, int maxAge) + { + super(); + _name = name; + _value = value; + _comment = null; + _domain = null; + _httpOnly = false; + _maxAge = maxAge; + _path = null; + _secure = false; + _version = 0; + } + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value, String domain, String path, int maxAge, boolean httpOnly, boolean secure) + { + super(); + _comment = null; + _domain = domain; + _httpOnly = httpOnly; + _maxAge = maxAge; + _name = name; + _path = path; + _secure = secure; + _value = value; + _version = 0; + } + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value, String domain, String path, int maxAge, boolean httpOnly, boolean secure, String comment, int version) + { + super(); + _comment = comment; + _domain = domain; + _httpOnly = httpOnly; + _maxAge = maxAge; + _name = name; + _path = path; + _secure = secure; + _value = value; + _version = version; + } + + /* ------------------------------------------------------------ */ + /** Get the name. + * @return the name + */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** Get the value. + * @return the value + */ + public String getValue() + { + return _value; + } + + /* ------------------------------------------------------------ */ + /** Get the comment. + * @return the comment + */ + public String getComment() + { + return _comment; + } + + /* ------------------------------------------------------------ */ + /** Get the domain. + * @return the domain + */ + public String getDomain() + { + return _domain; + } + + /* ------------------------------------------------------------ */ + /** Get the maxAge. + * @return the maxAge + */ + public int getMaxAge() + { + return _maxAge; + } + + /* ------------------------------------------------------------ */ + /** Get the path. + * @return the path + */ + public String getPath() + { + return _path; + } + + /* ------------------------------------------------------------ */ + /** Get the secure. + * @return the secure + */ + public boolean isSecure() + { + return _secure; + } + + /* ------------------------------------------------------------ */ + /** Get the version. + * @return the version + */ + public int getVersion() + { + return _version; + } + + /* ------------------------------------------------------------ */ + /** Get the isHttpOnly. + * @return the isHttpOnly + */ + public boolean isHttpOnly() + { + return _httpOnly; + } + + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java new file mode 100644 index 00000000000..0ed081cafde --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -0,0 +1,1463 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.TimeZone; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; +import org.eclipse.jetty.io.BufferDateCache; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; + +/* ------------------------------------------------------------ */ +/** + * HTTP Fields. A collection of HTTP header and or Trailer fields. + * + *

This class is not synchronized as it is expected that modifications will only be performed by a + * single thread. + * + * + */ +public class HttpFields +{ + /* ------------------------------------------------------------ */ + public static final TimeZone __GMT = TimeZone.getTimeZone("GMT"); + public final static BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); + + /* -------------------------------------------------------------- */ + static + { + __GMT.setID("GMT"); + __dateCache.setTimeZone(__GMT); + } + + /* ------------------------------------------------------------ */ + public final static String __separators = ", \t"; + + /* ------------------------------------------------------------ */ + private static String[] DAYS = + { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + private static String[] MONTHS = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"}; + + + /* ------------------------------------------------------------ */ + private static class DateGenerator + { + private final StringBuilder buf = new StringBuilder(32); + private final GregorianCalendar gc = new GregorianCalendar(__GMT); + + public String formatDate(long date) + { + buf.setLength(0); + gc.setTimeInMillis(date); + + int day_of_week = gc.get(Calendar.DAY_OF_WEEK); + int day_of_month = gc.get(Calendar.DAY_OF_MONTH); + int month = gc.get(Calendar.MONTH); + int year = gc.get(Calendar.YEAR); + int century = year / 100; + year = year % 100; + + int epoch = (int) ((date / 1000) % (60 * 60 * 24)); + int seconds = epoch % 60; + epoch = epoch / 60; + int minutes = epoch % 60; + int hours = epoch / 60; + + buf.append(DAYS[day_of_week]); + buf.append(','); + buf.append(' '); + StringUtil.append2digits(buf, day_of_month); + + buf.append(' '); + buf.append(MONTHS[month]); + buf.append(' '); + StringUtil.append2digits(buf, century); + StringUtil.append2digits(buf, year); + + buf.append(' '); + StringUtil.append2digits(buf, hours); + buf.append(':'); + StringUtil.append2digits(buf, minutes); + buf.append(':'); + StringUtil.append2digits(buf, seconds); + buf.append(" GMT"); + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies + */ + public void formatCookieDate(StringBuilder buf, long date) + { + buf.setLength(0); + gc.setTimeInMillis(date); + + int day_of_week = gc.get(Calendar.DAY_OF_WEEK); + int day_of_month = gc.get(Calendar.DAY_OF_MONTH); + int month = gc.get(Calendar.MONTH); + int year = gc.get(Calendar.YEAR); + year = year % 100; + + int epoch = (int) ((date / 1000) % (60 * 60 * 24)); + int seconds = epoch % 60; + epoch = epoch / 60; + int minutes = epoch % 60; + int hours = epoch / 60; + + buf.append(DAYS[day_of_week]); + buf.append(','); + buf.append(' '); + StringUtil.append2digits(buf, day_of_month); + + buf.append('-'); + buf.append(MONTHS[month]); + buf.append('-'); + StringUtil.append2digits(buf, year); + + buf.append(' '); + StringUtil.append2digits(buf, hours); + buf.append(':'); + StringUtil.append2digits(buf, minutes); + buf.append(':'); + StringUtil.append2digits(buf, seconds); + buf.append(" GMT"); + } + + + } + + /* ------------------------------------------------------------ */ + private static ThreadLocal __dateGenerator =new ThreadLocal() + { + @Override + protected DateGenerator initialValue() + { + return new DateGenerator(); + } + }; + + /* ------------------------------------------------------------ */ + /** + * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" + * cookies + */ + public static String formatDate(long date) + { + return __dateGenerator.get().formatDate(date); + } + + /* ------------------------------------------------------------ */ + /** + * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies + */ + public static void formatCookieDate(StringBuilder buf, long date) + { + __dateGenerator.get().formatCookieDate(buf,date); + } + + + /* ------------------------------------------------------------ */ + private final static String __dateReceiveFmt[] = + { + "EEE, dd MMM yyyy HH:mm:ss zzz", + "EEE, dd-MMM-yy HH:mm:ss", + "EEE MMM dd HH:mm:ss yyyy", + + "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", + "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", + "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", + "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz", + "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz", + "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", + "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss", + }; + + private static class DateParser + { + final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length]; + + long parse(final String dateVal) + { + for (int i = 0; i < _dateReceive.length; i++) + { + if (_dateReceive[i] == null) + { + _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US); + _dateReceive[i].setTimeZone(__GMT); + } + + try + { + Date date = (Date) _dateReceive[i].parseObject(dateVal); + return date.getTime(); + } + catch (java.lang.Exception e) + { + // Log.ignore(e); + } + } + + if (dateVal.endsWith(" GMT")) + { + final String val = dateVal.substring(0, dateVal.length() - 4); + + for (int i = 0; i < _dateReceive.length; i++) + { + try + { + Date date = (Date) _dateReceive[i].parseObject(val); + return date.getTime(); + } + catch (java.lang.Exception e) + { + // Log.ignore(e); + } + } + } + return -1; + } + } + + /* ------------------------------------------------------------ */ + public static long parseDate(String date) + { + return __dateParser.get().parse(date); + } + + /* ------------------------------------------------------------ */ + private static ThreadLocal __dateParser =new ThreadLocal() + { + @Override + protected DateParser initialValue() + { + return new DateParser(); + } + }; + + + + + + + public final static String __01Jan1970 = formatDate(0); + public final static Buffer __01Jan1970_BUFFER = new ByteArrayBuffer(__01Jan1970); + + /* -------------------------------------------------------------- */ + protected final ArrayList _fields = new ArrayList(20); + protected final HashMap _bufferMap = new HashMap(32); + protected int _revision; + + /* ------------------------------------------------------------ */ + /** + * Constructor. + */ + public HttpFields() + { + } + + /* -------------------------------------------------------------- */ + /** + * Get enumeration of header _names. Returns an enumeration of strings representing the header + * _names for this request. + */ + public Enumeration getFieldNames() + { + final int revision=_revision; + return new Enumeration() + { + int i = 0; + Field field = null; + + public boolean hasMoreElements() + { + if (field != null) return true; + while (i < _fields.size()) + { + Field f = (Field) _fields.get(i++); + if (f != null && f._prev == null && f._revision == revision) + { + field = f; + return true; + } + } + return false; + } + + public String nextElement() throws NoSuchElementException + { + if (field != null || hasMoreElements()) + { + String n = BufferUtil.to8859_1_String(field._name); + field = null; + return n; + } + throw new NoSuchElementException(); + } + }; + } + + /* ------------------------------------------------------------ */ + public int size() + { + return _fields.size(); + } + + /* ------------------------------------------------------------ */ + /** + * Get a Field by index. + * @return A Field value or null if the Field value has not been set + * for this revision of the fields. + */ + public Field getField(int i) + { + final Field field = _fields.get(i); + if (field._revision!=_revision) + return null; + return field; + } + + /* ------------------------------------------------------------ */ + private Field getField(String name) + { + return (Field) _bufferMap.get(HttpHeaders.CACHE.lookup(name)); + } + + /* ------------------------------------------------------------ */ + private Field getField(Buffer name) + { + return (Field) _bufferMap.get(name); + } + + /* ------------------------------------------------------------ */ + public boolean containsKey(Buffer name) + { + Field f = getField(name); + return (f != null && f._revision == _revision); + } + + /* ------------------------------------------------------------ */ + public boolean containsKey(String name) + { + Field f = getField(name); + return (f != null && f._revision == _revision); + } + + /* -------------------------------------------------------------- */ + /** + * @return the value of a field, or null if not found. For multiple fields of the same name, + * only the first is returned. + * @param name the case-insensitive field name + */ + public String getStringField(String name) + { + // TODO - really reuse strings from previous requests! + Field field = getField(name); + if (field != null && field._revision == _revision) + return field.getValue(); + return null; + } + + /* -------------------------------------------------------------- */ + /** + * @return the value of a field, or null if not found. For multiple fields of the same name, + * only the first is returned. + * @param name the case-insensitive field name + */ + public String getStringField(Buffer name) + { + // TODO - really reuse strings from previous requests! + Field field = getField(name); + if (field != null && field._revision == _revision) + return BufferUtil.to8859_1_String(field._value); + return null; + } + + /* -------------------------------------------------------------- */ + /** + * @return the value of a field, or null if not found. For multiple fields of the same name, + * only the first is returned. + * @param name the case-insensitive field name + */ + public Buffer get(Buffer name) + { + Field field = getField(name); + if (field != null && field._revision == _revision) + return field._value; + return null; + } + + /* -------------------------------------------------------------- */ + /** + * Get multi headers + * + * @return Enumeration of the values, or null if no such header. + * @param name the case-insensitive field name + */ + public Enumeration getValues(String name) + { + final Field field = getField(name); + if (field == null) + return null; + final int revision=_revision; + + return new Enumeration() + { + Field f = field; + + public boolean hasMoreElements() + { + while (f != null && f._revision != revision) + f = f._next; + return f != null; + } + + public String nextElement() throws NoSuchElementException + { + if (f == null) throw new NoSuchElementException(); + Field n = f; + do + f = f._next; + while (f != null && f._revision != revision); + return n.getValue(); + } + }; + } + + /* -------------------------------------------------------------- */ + /** + * Get multi headers + * + * @return Enumeration of the value Strings, or null if no such header. + * @param name the case-insensitive field name + */ + public Enumeration getValues(Buffer name) + { + final Field field = getField(name); + if (field == null) + return null; + final int revision=_revision; + + return new Enumeration() + { + Field f = field; + + public boolean hasMoreElements() + { + while (f != null && f._revision != revision) + f = f._next; + return f != null; + } + + public String nextElement() throws NoSuchElementException + { + if (f == null) throw new NoSuchElementException(); + Field n = f; + f = f._next; + while (f != null && f._revision != revision) + f = f._next; + return n.getValue(); + } + }; + } + + /* -------------------------------------------------------------- */ + /** + * Get multi field values with separator. The multiple values can be represented as separate + * headers of the same name, or by a single header using the separator(s), or a combination of + * both. Separators may be quoted. + * + * @param name the case-insensitive field name + * @param separators String of separators. + * @return Enumeration of the values, or null if no such header. + */ + public Enumeration getValues(String name, final String separators) + { + final Enumeration e = getValues(name); + if (e == null) + return null; + return new Enumeration() + { + QuotedStringTokenizer tok = null; + + public boolean hasMoreElements() + { + if (tok != null && tok.hasMoreElements()) return true; + while (e.hasMoreElements()) + { + String value = (String) e.nextElement(); + tok = new QuotedStringTokenizer(value, separators, false, false); + if (tok.hasMoreElements()) return true; + } + tok = null; + return false; + } + + public String nextElement() throws NoSuchElementException + { + if (!hasMoreElements()) throw new NoSuchElementException(); + String next = (String) tok.nextElement(); + if (next != null) next = next.trim(); + return next; + } + }; + } + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param value the value of the field. If null the field is cleared. + */ + public void put(String name, String value) + { + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = null; + if (value != null) + v = HttpHeaderValues.CACHE.lookup(value); + put(n, v, -1); + } + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param value the value of the field. If null the field is cleared. + */ + public void put(Buffer name, String value) + { + Buffer v = HttpHeaderValues.CACHE.lookup(value); + put(name, v, -1); + } + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param value the value of the field. If null the field is cleared. + */ + public void put(Buffer name, Buffer value) + { + put(name, value, -1); + } + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param value the value of the field. If null the field is cleared. + * @param numValue the numeric value of the field (must match value) or -1 + */ + public void put(Buffer name, Buffer value, long numValue) + { + if (value == null) + { + remove(name); + return; + } + + if (!(name instanceof BufferCache.CachedBuffer)) name = HttpHeaders.CACHE.lookup(name); + + Field field = (Field) _bufferMap.get(name); + + // Look for value to replace. + if (field != null) + { + field.reset(value, numValue, _revision); + field = field._next; + while (field != null) + { + field.clear(); + field = field._next; + } + return; + } + else + { + // new value; + field = new Field(name, value, numValue, _revision); + _fields.add(field); + _bufferMap.put(field.getNameBuffer(), field); + } + } + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param list the List value of the field. If null the field is cleared. + */ + public void put(String name, List list) + { + if (list == null || list.size() == 0) + { + remove(name); + return; + } + Buffer n = HttpHeaders.CACHE.lookup(name); + + Object v = list.get(0); + if (v != null) + put(n, HttpHeaderValues.CACHE.lookup(v.toString())); + else + remove(n); + + if (list.size() > 1) + { + java.util.Iterator iter = list.iterator(); + iter.next(); + while (iter.hasNext()) + { + v = iter.next(); + if (v != null) put(n, HttpHeaderValues.CACHE.lookup(v.toString())); + } + } + } + + /* -------------------------------------------------------------- */ + /** + * Add to or set a field. If the field is allowed to have multiple values, add will add multiple + * headers of the same name. + * + * @param name the name of the field + * @param value the value of the field. + * @exception IllegalArgumentException If the name is a single valued field and already has a + * value. + */ + public void add(String name, String value) throws IllegalArgumentException + { + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = HttpHeaderValues.CACHE.lookup(value); + add(n, v, -1); + } + + /* -------------------------------------------------------------- */ + /** + * Add to or set a field. If the field is allowed to have multiple values, add will add multiple + * headers of the same name. + * + * @param name the name of the field + * @param value the value of the field. + * @exception IllegalArgumentException If the name is a single valued field and already has a + * value. + */ + public void add(Buffer name, Buffer value) throws IllegalArgumentException + { + add(name, value, -1); + } + + /* -------------------------------------------------------------- */ + /** + * Add to or set a field. If the field is allowed to have multiple values, add will add multiple + * headers of the same name. + * + * @param name the name of the field + * @param value the value of the field. + * @exception IllegalArgumentException If the name is a single valued field and already has a + * value. + */ + private void add(Buffer name, Buffer value, long numValue) throws IllegalArgumentException + { + if (value == null) throw new IllegalArgumentException("null value"); + + if (!(name instanceof BufferCache.CachedBuffer)) name = HttpHeaders.CACHE.lookup(name); + + Field field = (Field) _bufferMap.get(name); + Field last = null; + if (field != null) + { + while (field != null && field._revision == _revision) + { + last = field; + field = field._next; + } + } + + if (field != null) + field.reset(value, numValue, _revision); + else + { + // create the field + field = new Field(name, value, numValue, _revision); + + // look for chain to add too + if (last != null) + { + field._prev = last; + last._next = field; + } + else + _bufferMap.put(field.getNameBuffer(), field); + + _fields.add(field); + } + } + + /* ------------------------------------------------------------ */ + /** + * Remove a field. + * + * @param name + */ + public void remove(String name) + { + remove(HttpHeaders.CACHE.lookup(name)); + } + + /* ------------------------------------------------------------ */ + /** + * Remove a field. + * + * @param name + */ + public void remove(Buffer name) + { + Field field = (Field) _bufferMap.get(name); + + if (field != null) + { + while (field != null) + { + field.clear(); + field = field._next; + } + } + } + + /* -------------------------------------------------------------- */ + /** + * Get a header as an long value. Returns the value of an integer field or -1 if not found. The + * case of the field name is ignored. + * + * @param name the case-insensitive field name + * @exception NumberFormatException If bad long found + */ + public long getLongField(String name) throws NumberFormatException + { + Field field = getField(name); + if (field != null && field._revision == _revision) return field.getLongValue(); + + return -1L; + } + + /* -------------------------------------------------------------- */ + /** + * Get a header as an long value. Returns the value of an integer field or -1 if not found. The + * case of the field name is ignored. + * + * @param name the case-insensitive field name + * @exception NumberFormatException If bad long found + */ + public long getLongField(Buffer name) throws NumberFormatException + { + Field field = getField(name); + if (field != null && field._revision == _revision) return field.getLongValue(); + return -1L; + } + + /* -------------------------------------------------------------- */ + /** + * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case + * of the field name is ignored. + * + * @param name the case-insensitive field name + */ + public long getDateField(String name) + { + Field field = getField(name); + if (field == null || field._revision != _revision) + return -1; + + if (field._numValue != -1) + return field._numValue; + + String val = valueParameters(BufferUtil.to8859_1_String(field._value), null); + if (val == null) + return -1; + + final long date = __dateParser.get().parse(val); + if (date<0) + throw new IllegalArgumentException("Cannot convert date: " + val); + field._numValue=date; + return date; + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of an long field. + * + * @param name the field name + * @param value the field long value + */ + public void putLongField(Buffer name, long value) + { + Buffer v = BufferUtil.toBuffer(value); + put(name, v, value); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of an long field. + * + * @param name the field name + * @param value the field long value + */ + public void putLongField(String name, long value) + { + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = BufferUtil.toBuffer(value); + put(n, v, value); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of an long field. + * + * @param name the field name + * @param value the field long value + */ + public void addLongField(String name, long value) + { + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = BufferUtil.toBuffer(value); + add(n, v, value); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of an long field. + * + * @param name the field name + * @param value the field long value + */ + public void addLongField(Buffer name, long value) + { + Buffer v = BufferUtil.toBuffer(value); + add(name, v, value); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of a date field. + * + * @param name the field name + * @param date the field date value + */ + public void putDateField(Buffer name, long date) + { + String d=formatDate(date); + Buffer v = new ByteArrayBuffer(d); + put(name, v, date); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of a date field. + * + * @param name the field name + * @param date the field date value + */ + public void putDateField(String name, long date) + { + Buffer n = HttpHeaders.CACHE.lookup(name); + putDateField(n,date); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of a date field. + * + * @param name the field name + * @param date the field date value + */ + public void addDateField(String name, long date) + { + String d=formatDate(date); + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = new ByteArrayBuffer(d); + add(n, v, date); + } + + /* ------------------------------------------------------------ */ + /** + * Format a set cookie value + * + * @param cookie The cookie. + * @param cookie2 If true, use the alternate cookie 2 header + */ + public void addSetCookie(HttpCookie cookie) + { + addSetCookie( + cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.getComment(), + cookie.isSecure(), + cookie.isHttpOnly(), + cookie.getVersion()); + } + + /* ------------------------------------------------------------ */ + /** + * Format a set cookie value + * @param cookie The cookie. + * @param cookie2 If true, use the alternate cookie 2 header + */ + public void addSetCookie( + final String name, + final String value, + final String domain, + final String path, + final long maxAge, + final String comment, + final boolean isSecure, + final boolean isHttpOnly, + final int version) + { + // Check arguments + if (name == null || name.length() == 0) throw new IllegalArgumentException("Bad cookie name"); + + // Format value and params + StringBuilder buf = new StringBuilder(128); + String name_value_params = null; + QuotedStringTokenizer.quoteIfNeeded(buf, name); + buf.append('='); + if (value != null && value.length() > 0) + QuotedStringTokenizer.quoteIfNeeded(buf, value); + + if (version > 0) + { + buf.append(";Version="); + buf.append(version); + if (comment != null && comment.length() > 0) + { + buf.append(";Comment="); + QuotedStringTokenizer.quoteIfNeeded(buf, comment); + } + } + if (path != null && path.length() > 0) + { + buf.append(";Path="); + buf.append(URIUtil.encodePath(path)); + } + if (domain != null && domain.length() > 0) + { + buf.append(";Domain="); + buf.append(domain.toLowerCase());// lowercase for IE + } + + if (maxAge >= 0) + { + if (version == 0) + { + buf.append(";Expires="); + if (maxAge == 0) + buf.append(__01Jan1970); + else + formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); + } + else + { + buf.append(";Max-Age="); + buf.append(maxAge); + } + } + else if (version > 0) + { + buf.append(";Discard"); + } + + if (isSecure) + buf.append(";Secure"); + if (isHttpOnly) + buf.append(";HttpOnly"); + + // TODO - straight to Buffer? + name_value_params = buf.toString(); + put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER); + add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params)); + } + + /* -------------------------------------------------------------- */ + public void put(Buffer buffer) throws IOException + { + for (int i = 0; i < _fields.size(); i++) + { + Field field = (Field) _fields.get(i); + if (field != null && field._revision == _revision) field.put(buffer); + } + BufferUtil.putCRLF(buffer); + } + + /* -------------------------------------------------------------- */ + public String toString() + { + try + { + ByteArrayBuffer buffer = new ByteArrayBuffer(4096); + put(buffer); + return BufferUtil.to8859_1_String(buffer); + } + catch (Exception e) + { + e.printStackTrace(); + } + + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Clear the header. + */ + public void clear() + { + _revision++; + if (_revision > 1000000) + { + _revision = 0; + for (int i = _fields.size(); i-- > 0;) + { + Field field = (Field) _fields.get(i); + if (field != null) field.clear(); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Destroy the header. Help the garbage collector by null everything that we can. + */ + public void destroy() + { + if (_fields != null) + { + for (int i = _fields.size(); i-- > 0;) + { + Field field = (Field) _fields.get(i); + if (field != null) field.destroy(); + } + } + _fields.clear(); + } + + /* ------------------------------------------------------------ */ + /** + * Add fields from another HttpFields instance. Single valued fields are replaced, while all + * others are added. + * + * @param fields + */ + public void add(HttpFields fields) + { + if (fields == null) return; + + Enumeration e = fields.getFieldNames(); + while (e.hasMoreElements()) + { + String name = (String) e.nextElement(); + Enumeration values = fields.getValues(name); + while (values.hasMoreElements()) + add(name, (String) values.nextElement()); + } + } + + /* ------------------------------------------------------------ */ + /** + * Get field value parameters. Some field values can have parameters. This method separates the + * value from the parameters and optionally populates a map with the parameters. For example: + * + *

+     * 
+     * FieldName : Value ; param1=val1 ; param2=val2
+     * 
+     * 
+ * + * @param value The Field value, possibly with parameteres. + * @param parameters A map to populate with the parameters, or null + * @return The value. + */ + public static String valueParameters(String value, Map parameters) + { + if (value == null) return null; + + int i = value.indexOf(';'); + if (i < 0) return value; + if (parameters == null) return value.substring(0, i).trim(); + + StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true); + while (tok1.hasMoreTokens()) + { + String token = tok1.nextToken(); + StringTokenizer tok2 = new QuotedStringTokenizer(token, "= "); + if (tok2.hasMoreTokens()) + { + String paramName = tok2.nextToken(); + String paramVal = null; + if (tok2.hasMoreTokens()) paramVal = tok2.nextToken(); + parameters.put(paramName, paramVal); + } + } + + return value.substring(0, i).trim(); + } + + /* ------------------------------------------------------------ */ + private static Float __one = new Float("1.0"); + private static Float __zero = new Float("0.0"); + private static StringMap __qualities = new StringMap(); + static + { + __qualities.put(null, __one); + __qualities.put("1.0", __one); + __qualities.put("1", __one); + __qualities.put("0.9", new Float("0.9")); + __qualities.put("0.8", new Float("0.8")); + __qualities.put("0.7", new Float("0.7")); + __qualities.put("0.66", new Float("0.66")); + __qualities.put("0.6", new Float("0.6")); + __qualities.put("0.5", new Float("0.5")); + __qualities.put("0.4", new Float("0.4")); + __qualities.put("0.33", new Float("0.33")); + __qualities.put("0.3", new Float("0.3")); + __qualities.put("0.2", new Float("0.2")); + __qualities.put("0.1", new Float("0.1")); + __qualities.put("0", __zero); + __qualities.put("0.0", __zero); + } + + /* ------------------------------------------------------------ */ + public static Float getQuality(String value) + { + if (value == null) return __zero; + + int qe = value.indexOf(";"); + if (qe++ < 0 || qe == value.length()) return __one; + + if (value.charAt(qe++) == 'q') + { + qe++; + Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe); + if (entry != null) return (Float) entry.getValue(); + } + + HashMap params = new HashMap(3); + valueParameters(value, params); + String qs = (String) params.get("q"); + Float q = (Float) __qualities.get(qs); + if (q == null) + { + try + { + q = new Float(qs); + } + catch (Exception e) + { + q = __one; + } + } + return q; + } + + /* ------------------------------------------------------------ */ + /** + * List values in quality order. + * + * @param enum Enumeration of values with quality parameters + * @return values in quality order. + */ + public static List qualityList(Enumeration e) + { + if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST; + + Object list = null; + Object qual = null; + + // Assume list will be well ordered and just add nonzero + while (e.hasMoreElements()) + { + String v = e.nextElement().toString(); + Float q = getQuality(v); + + if (q.floatValue() >= 0.001) + { + list = LazyList.add(list, v); + qual = LazyList.add(qual, q); + } + } + + List vl = LazyList.getList(list, false); + if (vl.size() < 2) return vl; + + List ql = LazyList.getList(qual, false); + + // sort list with swaps + Float last = __zero; + for (int i = vl.size(); i-- > 0;) + { + Float q = (Float) ql.get(i); + if (last.compareTo(q) > 0) + { + Object tmp = vl.get(i); + vl.set(i, vl.get(i + 1)); + vl.set(i + 1, tmp); + ql.set(i, ql.get(i + 1)); + ql.set(i + 1, q); + last = __zero; + i = vl.size(); + continue; + } + last = q; + } + ql.clear(); + return vl; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static final class Field + { + private Buffer _name; + private Buffer _value; + private String _stringValue; + private long _numValue; + private Field _next; + private Field _prev; + private int _revision; + + /* ------------------------------------------------------------ */ + private Field(Buffer name, Buffer value, long numValue, int revision) + { + _name = name.asImmutableBuffer(); + _value = value.isImmutable() ? value : new View(value); + _next = null; + _prev = null; + _revision = revision; + _numValue = numValue; + _stringValue=null; + } + + /* ------------------------------------------------------------ */ + private void clear() + { + _revision = -1; + } + + /* ------------------------------------------------------------ */ + private void destroy() + { + _name = null; + _value = null; + _next = null; + _prev = null; + _stringValue=null; + } + + /* ------------------------------------------------------------ */ + /** + * Reassign a value to this field. Checks if the string value is the same as that in the char + * array, if so then just reuse existing value. + */ + private void reset(Buffer value, long numValue, int revision) + { + _revision = revision; + if (_value == null) + { + _value = value.isImmutable() ? value : new View(value); + _numValue = numValue; + _stringValue=null; + } + else if (value.isImmutable()) + { + _value = value; + _numValue = numValue; + _stringValue=null; + } + else + { + if (_value instanceof View) + ((View) _value).update(value); + else + _value = new View(value); + _numValue = numValue; + + // check to see if string value is still valid. + if (_stringValue!=null) + { + if (_stringValue.length()!=value.length()) + _stringValue=null; + else + { + for (int i=value.length();i-->0;) + { + if (value.peek(value.getIndex()+i)!=_stringValue.charAt(i)) + { + _stringValue=null; + break; + } + } + } + } + } + } + + + + /* ------------------------------------------------------------ */ + public void put(Buffer buffer) throws IOException + { + int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1; + if (o>=0) + buffer.put(_name); + else + { + int s=_name.getIndex(); + int e=_name.putIndex(); + while (s=0 || _numValue>=0) + buffer.put(_value); + else + { + int s=_value.getIndex(); + int e=_value.putIndex(); + while (s") + "]"); + } + } + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java new file mode 100644 index 00000000000..f3ab557a541 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -0,0 +1,947 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** + * HttpGenerator. Builds HTTP Messages. + * + * + * + */ +public class HttpGenerator extends AbstractGenerator +{ + // Build cache of response lines for status + private static class Status + { + Buffer _reason; + Buffer _schemeCode; + Buffer _responseLine; + } + private static Status[] __status = new Status[HttpStatus.MAX_CODE+1]; + static + { + int versionLength=HttpVersions.HTTP_1_1_BUFFER.length(); + + for (int i=0;i<__status.length;i++) + { + HttpStatus.Code code = HttpStatus.getCode(i); + if (code==null) + continue; + String reason=code.getMessage(); + byte[] bytes=new byte[versionLength+5+reason.length()+2]; + HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength); + bytes[versionLength+0]=' '; + bytes[versionLength+1]=(byte)('0'+i/100); + bytes[versionLength+2]=(byte)('0'+(i%100)/10); + bytes[versionLength+3]=(byte)('0'+(i%10)); + bytes[versionLength+4]=' '; + for (int j=0;jcontent is {@link Buffer#isImmutable immutable}. + * @throws IllegalStateException If the request is not expecting any more content, + * or if the buffers are full and cannot be flushed. + * @throws IOException if there is a problem flushing the buffers. + */ + public void addContent(Buffer content, boolean last) throws IOException + { + if (_noContent) + throw new IllegalStateException("NO CONTENT"); + + if (_last || _state==STATE_END) + { + Log.debug("Ignoring extra content {}",content); + content.clear(); + return; + } + _last = last; + + // Handle any unfinished business? + if (_content!=null && _content.length()>0 || _bufferChunked) + { + if (!_endp.isOpen()) + throw new EofException(); + flushBuffer(); + if (_content != null && _content.length()>0 || _bufferChunked) + throw new IllegalStateException("FULL"); + } + + _content = content; + _contentWritten += content.length(); + + // Handle the _content + if (_head) + { + content.clear(); + _content=null; + } + else if (_endp != null && _buffer == null && content.length() > 0 && _last) + { + // TODO - use bypass in more cases. + // Make _content a direct buffer + _bypass = true; + } + else + { + // Yes - so we better check we have a buffer + if (_buffer == null) + _buffer = _buffers.getBuffer(_contentBufferSize); + + // Copy _content to buffer; + int len=_buffer.put(_content); + _content.skip(len); + if (_content.length() == 0) + _content = null; + } + } + + /* ------------------------------------------------------------ */ + /** + * send complete response. + * + * @param response + */ + public void sendResponse(Buffer response) throws IOException + { + if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head ) + throw new IllegalStateException(); + + _last = true; + + _content = response; + _bypass = true; + _state = STATE_FLUSHING; + + // TODO this is not exactly right, but should do. + _contentLength =_contentWritten = response.length(); + + } + + /* ------------------------------------------------------------ */ + /** + * Add content. + * + * @param b byte + * @return true if the buffers are full + * @throws IOException + */ + public boolean addContent(byte b) throws IOException + { + if (_noContent) + throw new IllegalStateException("NO CONTENT"); + + if (_last || _state==STATE_END) + { + Log.debug("Ignoring extra content {}",new Byte(b)); + return false; + } + + // Handle any unfinished business? + if (_content != null && _content.length()>0 || _bufferChunked) + { + flushBuffer(); + if (_content != null && _content.length()>0 || _bufferChunked) + throw new IllegalStateException("FULL"); + } + + _contentWritten++; + + // Handle the _content + if (_head) + return false; + + // we better check we have a buffer + if (_buffer == null) + _buffer = _buffers.getBuffer(_contentBufferSize); + + // Copy _content to buffer; + _buffer.put(b); + + return _buffer.space()<=(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); + } + + /* ------------------------------------------------------------ */ + /** Prepare buffer for unchecked writes. + * Prepare the generator buffer to receive unchecked writes + * @return the available space in the buffer. + * @throws IOException + */ + public int prepareUncheckedAddContent() throws IOException + { + if (_noContent) + return -1; + + if (_last || _state==STATE_END) + return -1; + + // Handle any unfinished business? + Buffer content = _content; + if (content != null && content.length()>0 || _bufferChunked) + { + flushBuffer(); + if (content != null && content.length()>0 || _bufferChunked) + throw new IllegalStateException("FULL"); + } + + // we better check we have a buffer + if (_buffer == null) + _buffer = _buffers.getBuffer(_contentBufferSize); + + _contentWritten-=_buffer.length(); + + // Handle the _content + if (_head) + return Integer.MAX_VALUE; + + return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); + } + + /* ------------------------------------------------------------ */ + public boolean isBufferFull() + { + // Should we flush the buffers? + boolean full = super.isBufferFull() || _bufferChunked || _bypass || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE); + return full; + } + + /* ------------------------------------------------------------ */ + public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException + { + if (_state != STATE_HEADER) + return; + + // handle a reset + if (_method==null && _status==0) + throw new EofException(); + + if (_last && !allContentAdded) + throw new IllegalStateException("last?"); + _last = _last | allContentAdded; + + // get a header buffer + if (_header == null) + _header = _buffers.getBuffer(_headerBufferSize); + + boolean has_server = false; + + if (_method!=null) + { + _close = false; + // Request + if (_version == HttpVersions.HTTP_0_9_ORDINAL) + { + _contentLength = HttpTokens.NO_CONTENT; + _header.put(_method); + _header.put((byte)' '); + _header.put(_uri.getBytes("utf-8")); // TODO WRONG! + _header.put(HttpTokens.CRLF); + _state = STATE_FLUSHING; + _noContent=true; + return; + } + else + { + _header.put(_method); + _header.put((byte)' '); + _header.put(_uri.getBytes("utf-8")); // TODO WRONG! + _header.put((byte)' '); + _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER); + _header.put(HttpTokens.CRLF); + } + } + else + { + // Response + if (_version == HttpVersions.HTTP_0_9_ORDINAL) + { + _close = true; + _contentLength = HttpTokens.EOF_CONTENT; + _state = STATE_CONTENT; + return; + } + else + { + if (_version == HttpVersions.HTTP_1_0_ORDINAL) + _close = true; + + // add response line + Status status = _status<__status.length?__status[_status]:null; + + if (status==null) + { + _header.put(HttpVersions.HTTP_1_1_BUFFER); + _header.put((byte) ' '); + _header.put((byte) ('0' + _status / 100)); + _header.put((byte) ('0' + (_status % 100) / 10)); + _header.put((byte) ('0' + (_status % 10))); + _header.put((byte) ' '); + if (_reason==null) + { + _header.put((byte) ('0' + _status / 100)); + _header.put((byte) ('0' + (_status % 100) / 10)); + _header.put((byte) ('0' + (_status % 10))); + } + else + _header.put(_reason); + _header.put(HttpTokens.CRLF); + } + else + { + if (_reason==null) + _header.put(status._responseLine); + else + { + _header.put(status._schemeCode); + _header.put(_reason); + _header.put(HttpTokens.CRLF); + } + } + + if (_status<200 && _status>=100 ) + { + _noContent=true; + _content=null; + if (_buffer!=null) + _buffer.clear(); + // end the header. + _header.put(HttpTokens.CRLF); + _state = STATE_CONTENT; + return; + } + + if (_status==204 || _status==304) + { + _noContent=true; + _content=null; + if (_buffer!=null) + _buffer.clear(); + } + } + } + + // Add headers + + // key field values + HttpFields.Field content_length = null; + HttpFields.Field transfer_encoding = null; + boolean keep_alive = false; + boolean close=false; + StringBuilder connection = null; + + if (fields != null) + { + int s=fields.size(); + for (int f=0;f= 200 && _status != 204 && _status != 304) + _header.put(CONTENT_LENGTH_0); + break; + + case HttpTokens.EOF_CONTENT: + _close = _method==null; + break; + + case HttpTokens.CHUNKED_CONTENT: + break; + + default: + // TODO - maybe allow forced chunking by setting te ??? + break; + } + + // Add transfer_encoding if needed + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + { + // try to use user supplied encoding as it may have other values. + if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal()) + { + String c = transfer_encoding.getValue(); + if (c.endsWith(HttpHeaderValues.CHUNKED)) + transfer_encoding.put(_header); + else + throw new IllegalArgumentException("BAD TE"); + } + else + _header.put(TRANSFER_ENCODING_CHUNKED); + } + + // Handle connection if need be + if (_contentLength==HttpTokens.EOF_CONTENT) + { + keep_alive=false; + _close=true; + } + + if (_method==null) + { + if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL)) + { + _header.put(CONNECTION_CLOSE); + if (connection!=null) + { + _header.setPutIndex(_header.putIndex()-2); + _header.put((byte)','); + _header.put(connection.toString().getBytes()); + _header.put(CRLF); + } + } + else if (keep_alive) + { + _header.put(CONNECTION_KEEP_ALIVE); + if (connection!=null) + { + _header.setPutIndex(_header.putIndex()-2); + _header.put((byte)','); + _header.put(connection.toString().getBytes()); + _header.put(CRLF); + } + } + else if (connection!=null) + { + _header.put(CONNECTION_); + _header.put(connection.toString().getBytes()); + _header.put(CRLF); + } + } + + if (!has_server && _status>100 && getSendServerVersion()) + _header.put(SERVER); + + // end the header. + _header.put(HttpTokens.CRLF); + + _state = STATE_CONTENT; + + } + + /* ------------------------------------------------------------ */ + /** + * Complete the message. + * + * @throws IOException + */ + public void complete() throws IOException + { + if (_state == STATE_END) + return; + + super.complete(); + + if (_state < STATE_FLUSHING) + { + _state = STATE_FLUSHING; + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + _needEOC = true; + } + + flushBuffer(); + } + + /* ------------------------------------------------------------ */ + public long flushBuffer() throws IOException + { + try + { + if (_state == STATE_HEADER) + throw new IllegalStateException("State==HEADER"); + + prepareBuffers(); + + if (_endp == null) + { + if (_needCRLF && _buffer!=null) + _buffer.put(HttpTokens.CRLF); + if (_needEOC && _buffer!=null && !_head) + _buffer.put(LAST_CHUNK); + _needCRLF=false; + _needEOC=false; + return 0; + } + + int total= 0; + + int len = -1; + int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0) | ((_bypass && _content != null && _content.length() > 0)?1:0); + switch (to_flush) + { + case 7: + throw new IllegalStateException(); // should never happen! + case 6: + len = _endp.flush(_header, _buffer, null); + break; + case 5: + len = _endp.flush(_header, _content, null); + break; + case 4: + len = _endp.flush(_header); + break; + case 3: + throw new IllegalStateException(); // should never happen! + case 2: + len = _endp.flush(_buffer); + break; + case 1: + len = _endp.flush(_content); + break; + case 0: + { + // Nothing more we can write now. + if (_header != null) + _header.clear(); + + _bypass = false; + _bufferChunked = false; + + if (_buffer != null) + { + _buffer.clear(); + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + { + // reserve some space for the chunk header + _buffer.setPutIndex(CHUNK_SPACE); + _buffer.setGetIndex(CHUNK_SPACE); + + // Special case handling for small left over buffer from + // an addContent that caused a buffer flush. + if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + { + _buffer.put(_content); + _content.clear(); + _content = null; + } + } + } + + // Are we completely finished for now? + if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0)) + { + if (_state == STATE_FLUSHING) + _state = STATE_END; + if (_state==STATE_END && _close && _status!=100) + _endp.close(); + } + else + // Try to prepare more to write. + prepareBuffers(); + } + } + + if (len > 0) + total+=len; + + return total; + } + catch (IOException e) + { + Log.ignore(e); + throw (e instanceof EofException) ? e:new EofException(e); + } + } + + /* ------------------------------------------------------------ */ + private void prepareBuffers() + { + // if we are not flushing an existing chunk + if (!_bufferChunked) + { + // Refill buffer if possible + if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0) + { + int len = _buffer.put(_content); + _content.skip(len); + if (_content.length() == 0) + _content = null; + } + + // Chunk buffer if need be + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + { + int size = _buffer == null ? 0 : _buffer.length(); + if (size > 0) + { + // Prepare a chunk! + _bufferChunked = true; + + // Did we leave space at the start of the buffer. + if (_buffer.getIndex() == CHUNK_SPACE) + { + // Oh yes, goodie! let's use it then! + _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2); + _buffer.setGetIndex(_buffer.getIndex() - 2); + BufferUtil.prependHexInt(_buffer, size); + + if (_needCRLF) + { + _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2); + _buffer.setGetIndex(_buffer.getIndex() - 2); + _needCRLF = false; + } + } + else + { + // No space so lets use the header buffer. + if (_needCRLF) + { + if (_header.length() > 0) throw new IllegalStateException("EOC"); + _header.put(HttpTokens.CRLF); + _needCRLF = false; + } + BufferUtil.putHexInt(_header, size); + _header.put(HttpTokens.CRLF); + } + + // Add end chunk trailer. + if (_buffer.space() >= 2) + _buffer.put(HttpTokens.CRLF); + else + _needCRLF = true; + } + + // If we need EOC and everything written + if (_needEOC && (_content == null || _content.length() == 0)) + { + if (_needCRLF) + { + if (_buffer == null && _header.space() >= 2) + { + _header.put(HttpTokens.CRLF); + _needCRLF = false; + } + else if (_buffer!=null && _buffer.space() >= 2) + { + _buffer.put(HttpTokens.CRLF); + _needCRLF = false; + } + } + + if (!_needCRLF && _needEOC) + { + if (_buffer == null && _header.space() >= LAST_CHUNK.length) + { + if (!_head) + { + _header.put(LAST_CHUNK); + _bufferChunked=true; + } + _needEOC = false; + } + else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length) + { + if (!_head) + { + _buffer.put(LAST_CHUNK); + _bufferChunked=true; + } + _needEOC = false; + } + } + } + } + } + + if (_content != null && _content.length() == 0) + _content = null; + + } + + public int getBytesBuffered() + { + return(_header==null?0:_header.length())+ + (_buffer==null?0:_buffer.length())+ + (_content==null?0:_content.length()); + } + + public boolean isEmpty() + { + return (_header==null||_header.length()==0) && + (_buffer==null||_buffer.length()==0) && + (_content==null||_content.length()==0); + } + + public String toString() + { + return "HttpGenerator s="+_state+ + " h="+(_header==null?"null":_header.length())+ + " b="+(_buffer==null?"null":_buffer.length())+ + " c="+(_content==null?"null":_content.length()); + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java new file mode 100644 index 00000000000..5f242250eed --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java @@ -0,0 +1,100 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.log.Log; + +/** + * Cached HTTP Header values. + * This class caches the conversion of common HTTP Header values to and from {@link ByteArrayBuffer} instances. + * The resource "/org/eclipse/jetty/useragents" is checked for a list of common user agents, so that repeated + * creation of strings for these agents can be avoided. + * + * + */ +public class HttpHeaderValues extends BufferCache +{ + public final static String + CLOSE="close", + CHUNKED="chunked", + GZIP="gzip", + IDENTITY="identity", + KEEP_ALIVE="keep-alive", + CONTINUE="100-continue", + PROCESSING="102-processing", + TE="TE", + BYTES="bytes", + NO_CACHE="no-cache"; + + public final static int + CLOSE_ORDINAL=1, + CHUNKED_ORDINAL=2, + GZIP_ORDINAL=3, + IDENTITY_ORDINAL=4, + KEEP_ALIVE_ORDINAL=5, + CONTINUE_ORDINAL=6, + PROCESSING_ORDINAL=7, + TE_ORDINAL=8, + BYTES_ORDINAL=9, + NO_CACHE_ORDINAL=10; + + public final static HttpHeaderValues CACHE= new HttpHeaderValues(); + + public final static Buffer + CLOSE_BUFFER=CACHE.add(CLOSE,CLOSE_ORDINAL), + CHUNKED_BUFFER=CACHE.add(CHUNKED,CHUNKED_ORDINAL), + GZIP_BUFFER=CACHE.add(GZIP,GZIP_ORDINAL), + IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL), + KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL), + CONTINUE_BUFFER=CACHE.add(CONTINUE, CONTINUE_ORDINAL), + PROCESSING_BUFFER=CACHE.add(PROCESSING, PROCESSING_ORDINAL), + TE_BUFFER=CACHE.add(TE,TE_ORDINAL), + BYTES_BUFFER=CACHE.add(BYTES,BYTES_ORDINAL), + NO_CACHE_BUFFER=CACHE.add(NO_CACHE,NO_CACHE_ORDINAL); + + static + { + int index=100; + CACHE.add("gzip",index++); + CACHE.add("gzip,deflate",index++); + CACHE.add("deflate",index++); + + try + { + InputStream ua = HttpHeaderValues.class.getResourceAsStream("/org/eclipse/jetty/http/useragents"); + if (ua!=null) + { + LineNumberReader in = new LineNumberReader(new InputStreamReader(ua)); + String line = in.readLine(); + while (line!=null) + { + CACHE.add(line,index++); + line = in.readLine(); + } + } + } + catch(Exception e) + { + Log.warn(e.toString()); + Log.debug(e); + } + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaders.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaders.java new file mode 100644 index 00000000000..b29f40a4910 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaders.java @@ -0,0 +1,226 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class HttpHeaders extends BufferCache +{ + /* ------------------------------------------------------------ */ + /** General Fields. + */ + public final static String + CONNECTION= "Connection", + CACHE_CONTROL= "Cache-Control", + DATE= "Date", + PRAGMA= "Pragma", + PROXY_CONNECTION = "Proxy-Connection", + TRAILER= "Trailer", + TRANSFER_ENCODING= "Transfer-Encoding", + UPGRADE= "Upgrade", + VIA= "Via", + WARNING= "Warning"; + + /* ------------------------------------------------------------ */ + /** Entity Fields. + */ + public final static String ALLOW= "Allow", + CONTENT_ENCODING= "Content-Encoding", + CONTENT_LANGUAGE= "Content-Language", + CONTENT_LENGTH= "Content-Length", + CONTENT_LOCATION= "Content-Location", + CONTENT_MD5= "Content-MD5", + CONTENT_RANGE= "Content-Range", + CONTENT_TYPE= "Content-Type", + EXPIRES= "Expires", + LAST_MODIFIED= "Last-Modified"; + + /* ------------------------------------------------------------ */ + /** Request Fields. + */ + public final static String ACCEPT= "Accept", + ACCEPT_CHARSET= "Accept-Charset", + ACCEPT_ENCODING= "Accept-Encoding", + ACCEPT_LANGUAGE= "Accept-Language", + AUTHORIZATION= "Authorization", + EXPECT= "Expect", + FORWARDED= "Forwarded", + FROM= "From", + HOST= "Host", + IF_MATCH= "If-Match", + IF_MODIFIED_SINCE= "If-Modified-Since", + IF_NONE_MATCH= "If-None-Match", + IF_RANGE= "If-Range", + IF_UNMODIFIED_SINCE= "If-Unmodified-Since", + KEEP_ALIVE= "Keep-Alive", + MAX_FORWARDS= "Max-Forwards", + PROXY_AUTHORIZATION= "Proxy-Authorization", + RANGE= "Range", + REQUEST_RANGE= "Request-Range", + REFERER= "Referer", + TE= "TE", + USER_AGENT= "User-Agent", + X_FORWARDED_FOR= "X-Forwarded-For"; + + /* ------------------------------------------------------------ */ + /** Response Fields. + */ + public final static String ACCEPT_RANGES= "Accept-Ranges", + AGE= "Age", + ETAG= "ETag", + LOCATION= "Location", + PROXY_AUTHENTICATE= "Proxy-Authenticate", + RETRY_AFTER= "Retry-After", + SERVER= "Server", + SERVLET_ENGINE= "Servlet-Engine", + VARY= "Vary", + WWW_AUTHENTICATE= "WWW-Authenticate"; + + /* ------------------------------------------------------------ */ + /** Other Fields. + */ + public final static String COOKIE= "Cookie", + SET_COOKIE= "Set-Cookie", + SET_COOKIE2= "Set-Cookie2", + MIME_VERSION= "MIME-Version", + IDENTITY= "identity"; + + public final static int CONNECTION_ORDINAL= 1, + DATE_ORDINAL= 2, + PRAGMA_ORDINAL= 3, + TRAILER_ORDINAL= 4, + TRANSFER_ENCODING_ORDINAL= 5, + UPGRADE_ORDINAL= 6, + VIA_ORDINAL= 7, + WARNING_ORDINAL= 8, + ALLOW_ORDINAL= 9, + CONTENT_ENCODING_ORDINAL= 10, + CONTENT_LANGUAGE_ORDINAL= 11, + CONTENT_LENGTH_ORDINAL= 12, + CONTENT_LOCATION_ORDINAL= 13, + CONTENT_MD5_ORDINAL= 14, + CONTENT_RANGE_ORDINAL= 15, + CONTENT_TYPE_ORDINAL= 16, + EXPIRES_ORDINAL= 17, + LAST_MODIFIED_ORDINAL= 18, + ACCEPT_ORDINAL= 19, + ACCEPT_CHARSET_ORDINAL= 20, + ACCEPT_ENCODING_ORDINAL= 21, + ACCEPT_LANGUAGE_ORDINAL= 22, + AUTHORIZATION_ORDINAL= 23, + EXPECT_ORDINAL= 24, + FORWARDED_ORDINAL= 25, + FROM_ORDINAL= 26, + HOST_ORDINAL= 27, + IF_MATCH_ORDINAL= 28, + IF_MODIFIED_SINCE_ORDINAL= 29, + IF_NONE_MATCH_ORDINAL= 30, + IF_RANGE_ORDINAL= 31, + IF_UNMODIFIED_SINCE_ORDINAL= 32, + KEEP_ALIVE_ORDINAL= 33, + MAX_FORWARDS_ORDINAL= 34, + PROXY_AUTHORIZATION_ORDINAL= 35, + RANGE_ORDINAL= 36, + REQUEST_RANGE_ORDINAL= 37, + REFERER_ORDINAL= 38, + TE_ORDINAL= 39, + USER_AGENT_ORDINAL= 40, + X_FORWARDED_FOR_ORDINAL= 41, + ACCEPT_RANGES_ORDINAL= 42, + AGE_ORDINAL= 43, + ETAG_ORDINAL= 44, + LOCATION_ORDINAL= 45, + PROXY_AUTHENTICATE_ORDINAL= 46, + RETRY_AFTER_ORDINAL= 47, + SERVER_ORDINAL= 48, + SERVLET_ENGINE_ORDINAL= 49, + VARY_ORDINAL= 50, + WWW_AUTHENTICATE_ORDINAL= 51, + COOKIE_ORDINAL= 52, + SET_COOKIE_ORDINAL= 53, + SET_COOKIE2_ORDINAL= 54, + MIME_VERSION_ORDINAL= 55, + IDENTITY_ORDINAL= 56, + CACHE_CONTROL_ORDINAL=57, + PROXY_CONNECTION_ORDINAL=58; + + public final static HttpHeaders CACHE= new HttpHeaders(); + + public final static Buffer + HOST_BUFFER=CACHE.add(HOST,HOST_ORDINAL), + ACCEPT_BUFFER=CACHE.add(ACCEPT,ACCEPT_ORDINAL), + ACCEPT_CHARSET_BUFFER=CACHE.add(ACCEPT_CHARSET,ACCEPT_CHARSET_ORDINAL), + ACCEPT_ENCODING_BUFFER=CACHE.add(ACCEPT_ENCODING,ACCEPT_ENCODING_ORDINAL), + ACCEPT_LANGUAGE_BUFFER=CACHE.add(ACCEPT_LANGUAGE,ACCEPT_LANGUAGE_ORDINAL), + + CONTENT_LENGTH_BUFFER=CACHE.add(CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL), + CONNECTION_BUFFER=CACHE.add(CONNECTION,CONNECTION_ORDINAL), + CACHE_CONTROL_BUFFER=CACHE.add(CACHE_CONTROL,CACHE_CONTROL_ORDINAL), + DATE_BUFFER=CACHE.add(DATE,DATE_ORDINAL), + PRAGMA_BUFFER=CACHE.add(PRAGMA,PRAGMA_ORDINAL), + TRAILER_BUFFER=CACHE.add(TRAILER,TRAILER_ORDINAL), + TRANSFER_ENCODING_BUFFER=CACHE.add(TRANSFER_ENCODING,TRANSFER_ENCODING_ORDINAL), + UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL), + VIA_BUFFER=CACHE.add(VIA,VIA_ORDINAL), + WARNING_BUFFER=CACHE.add(WARNING,WARNING_ORDINAL), + ALLOW_BUFFER=CACHE.add(ALLOW,ALLOW_ORDINAL), + CONTENT_ENCODING_BUFFER=CACHE.add(CONTENT_ENCODING,CONTENT_ENCODING_ORDINAL), + CONTENT_LANGUAGE_BUFFER=CACHE.add(CONTENT_LANGUAGE,CONTENT_LANGUAGE_ORDINAL), + CONTENT_LOCATION_BUFFER=CACHE.add(CONTENT_LOCATION,CONTENT_LOCATION_ORDINAL), + CONTENT_MD5_BUFFER=CACHE.add(CONTENT_MD5,CONTENT_MD5_ORDINAL), + CONTENT_RANGE_BUFFER=CACHE.add(CONTENT_RANGE,CONTENT_RANGE_ORDINAL), + CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL), + EXPIRES_BUFFER=CACHE.add(EXPIRES,EXPIRES_ORDINAL), + LAST_MODIFIED_BUFFER=CACHE.add(LAST_MODIFIED,LAST_MODIFIED_ORDINAL), + AUTHORIZATION_BUFFER=CACHE.add(AUTHORIZATION,AUTHORIZATION_ORDINAL), + EXPECT_BUFFER=CACHE.add(EXPECT,EXPECT_ORDINAL), + FORWARDED_BUFFER=CACHE.add(FORWARDED,FORWARDED_ORDINAL), + FROM_BUFFER=CACHE.add(FROM,FROM_ORDINAL), + IF_MATCH_BUFFER=CACHE.add(IF_MATCH,IF_MATCH_ORDINAL), + IF_MODIFIED_SINCE_BUFFER=CACHE.add(IF_MODIFIED_SINCE,IF_MODIFIED_SINCE_ORDINAL), + IF_NONE_MATCH_BUFFER=CACHE.add(IF_NONE_MATCH,IF_NONE_MATCH_ORDINAL), + IF_RANGE_BUFFER=CACHE.add(IF_RANGE,IF_RANGE_ORDINAL), + IF_UNMODIFIED_SINCE_BUFFER=CACHE.add(IF_UNMODIFIED_SINCE,IF_UNMODIFIED_SINCE_ORDINAL), + KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL), + MAX_FORWARDS_BUFFER=CACHE.add(MAX_FORWARDS,MAX_FORWARDS_ORDINAL), + PROXY_AUTHORIZATION_BUFFER=CACHE.add(PROXY_AUTHORIZATION,PROXY_AUTHORIZATION_ORDINAL), + RANGE_BUFFER=CACHE.add(RANGE,RANGE_ORDINAL), + REQUEST_RANGE_BUFFER=CACHE.add(REQUEST_RANGE,REQUEST_RANGE_ORDINAL), + REFERER_BUFFER=CACHE.add(REFERER,REFERER_ORDINAL), + TE_BUFFER=CACHE.add(TE,TE_ORDINAL), + USER_AGENT_BUFFER=CACHE.add(USER_AGENT,USER_AGENT_ORDINAL), + X_FORWARDED_FOR_BUFFER=CACHE.add(X_FORWARDED_FOR,X_FORWARDED_FOR_ORDINAL), + ACCEPT_RANGES_BUFFER=CACHE.add(ACCEPT_RANGES,ACCEPT_RANGES_ORDINAL), + AGE_BUFFER=CACHE.add(AGE,AGE_ORDINAL), + ETAG_BUFFER=CACHE.add(ETAG,ETAG_ORDINAL), + LOCATION_BUFFER=CACHE.add(LOCATION,LOCATION_ORDINAL), + PROXY_AUTHENTICATE_BUFFER=CACHE.add(PROXY_AUTHENTICATE,PROXY_AUTHENTICATE_ORDINAL), + RETRY_AFTER_BUFFER=CACHE.add(RETRY_AFTER,RETRY_AFTER_ORDINAL), + SERVER_BUFFER=CACHE.add(SERVER,SERVER_ORDINAL), + SERVLET_ENGINE_BUFFER=CACHE.add(SERVLET_ENGINE,SERVLET_ENGINE_ORDINAL), + VARY_BUFFER=CACHE.add(VARY,VARY_ORDINAL), + WWW_AUTHENTICATE_BUFFER=CACHE.add(WWW_AUTHENTICATE,WWW_AUTHENTICATE_ORDINAL), + COOKIE_BUFFER=CACHE.add(COOKIE,COOKIE_ORDINAL), + SET_COOKIE_BUFFER=CACHE.add(SET_COOKIE,SET_COOKIE_ORDINAL), + SET_COOKIE2_BUFFER=CACHE.add(SET_COOKIE2,SET_COOKIE2_ORDINAL), + MIME_VERSION_BUFFER=CACHE.add(MIME_VERSION,MIME_VERSION_ORDINAL), + IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL), + PROXY_CONNECTION_BUFFER=CACHE.add(PROXY_CONNECTION,PROXY_CONNECTION_ORDINAL); +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethods.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethods.java new file mode 100644 index 00000000000..82097ca0b69 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethods.java @@ -0,0 +1,59 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class HttpMethods +{ + public final static String GET= "GET", + POST= "POST", + HEAD= "HEAD", + PUT= "PUT", + OPTIONS= "OPTIONS", + DELETE= "DELETE", + TRACE= "TRACE", + CONNECT= "CONNECT", + MOVE= "MOVE"; + + public final static int GET_ORDINAL= 1, + POST_ORDINAL= 2, + HEAD_ORDINAL= 3, + PUT_ORDINAL= 4, + OPTIONS_ORDINAL= 5, + DELETE_ORDINAL= 6, + TRACE_ORDINAL= 7, + CONNECT_ORDINAL= 8, + MOVE_ORDINAL= 9; + + public final static BufferCache CACHE= new BufferCache(); + + public final static Buffer + GET_BUFFER= CACHE.add(GET, GET_ORDINAL), + POST_BUFFER= CACHE.add(POST, POST_ORDINAL), + HEAD_BUFFER= CACHE.add(HEAD, HEAD_ORDINAL), + PUT_BUFFER= CACHE.add(PUT, PUT_ORDINAL), + OPTIONS_BUFFER= CACHE.add(OPTIONS, OPTIONS_ORDINAL), + DELETE_BUFFER= CACHE.add(DELETE, DELETE_ORDINAL), + TRACE_BUFFER= CACHE.add(TRACE, TRACE_ORDINAL), + CONNECT_BUFFER= CACHE.add(CONNECT, CONNECT_ORDINAL), + MOVE_BUFFER= CACHE.add(MOVE, MOVE_ORDINAL); + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java new file mode 100644 index 00000000000..b99a739eacd --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -0,0 +1,1137 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------------------------- */ +/** + * + */ +public class HttpParser implements Parser +{ + // States + public static final int STATE_START=-13; + public static final int STATE_FIELD0=-12; + public static final int STATE_SPACE1=-11; + public static final int STATE_FIELD1=-10; + public static final int STATE_SPACE2=-9; + public static final int STATE_END0=-8; + public static final int STATE_END1=-7; + public static final int STATE_FIELD2=-6; + public static final int STATE_HEADER=-5; + public static final int STATE_HEADER_NAME=-4; + public static final int STATE_HEADER_IN_NAME=-3; + public static final int STATE_HEADER_VALUE=-2; + public static final int STATE_HEADER_IN_VALUE=-1; + public static final int STATE_END=0; + public static final int STATE_EOF_CONTENT=1; + public static final int STATE_CONTENT=2; + public static final int STATE_CHUNKED_CONTENT=3; + public static final int STATE_CHUNK_SIZE=4; + public static final int STATE_CHUNK_PARAMS=5; + public static final int STATE_CHUNK=6; + + private final EventHandler _handler; + private final Buffers _buffers; // source of buffers + private final EndPoint _endp; + private Buffer _header; // Buffer for header data (and small _content) + private Buffer _body; // Buffer for large content + private Buffer _buffer; // The current buffer in use (either _header or _content) + private final View _contentView=new View(); // View of the content in the buffer for {@link Input} + private int _headerBufferSize; + + private int _contentBufferSize; + private CachedBuffer _cached; + private View.CaseInsensitive _tok0; // Saved token: header name, request method or response version + private View.CaseInsensitive _tok1; // Saved token: header value, request URI or response code + private String _multiLineValue; + private int _responseStatus; // If >0 then we are parsing a response + private boolean _forceContentBuffer; + + /* ------------------------------------------------------------------------------- */ + protected int _state=STATE_START; + protected byte _eol; + protected int _length; + protected long _contentLength; + protected long _contentPosition; + protected int _chunkLength; + protected int _chunkPosition; + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + */ + public HttpParser(Buffer buffer, EventHandler handler) + { + _endp=null; + _buffers=null; + _header=buffer; + _buffer=buffer; + _handler=handler; + + if (buffer != null) + { + _tok0=new View.CaseInsensitive(buffer); + _tok1=new View.CaseInsensitive(buffer); + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + } + } + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + * @param headerBufferSize size in bytes of header buffer + * @param contentBufferSize size in bytes of content buffer + */ + public HttpParser(Buffers buffers, EndPoint endp, EventHandler handler, int headerBufferSize, int contentBufferSize) + { + _buffers=buffers; + _endp=endp; + _handler=handler; + _headerBufferSize=headerBufferSize; + _contentBufferSize=contentBufferSize; + } + + /* ------------------------------------------------------------------------------- */ + public long getContentLength() + { + return _contentLength; + } + + public long getContentRead() + { + return _contentPosition; + } + + /* ------------------------------------------------------------------------------- */ + public int getState() + { + return _state; + } + + /* ------------------------------------------------------------------------------- */ + public boolean inContentState() + { + return _state > 0; + } + + /* ------------------------------------------------------------------------------- */ + public boolean inHeaderState() + { + return _state < 0; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isChunking() + { + return _contentLength==HttpTokens.CHUNKED_CONTENT; + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return isState(STATE_START); + } + + /* ------------------------------------------------------------ */ + public boolean isComplete() + { + return isState(STATE_END); + } + + /* ------------------------------------------------------------ */ + public boolean isMoreInBuffer() + throws IOException + { + if ( _header!=null && _header.hasContent() || + _body!=null && _body.hasContent()) + return true; + + return false; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isState(int state) + { + return _state == state; + } + + /* ------------------------------------------------------------------------------- */ + /** + * Parse until {@link #STATE_END END} state. + * If the parser is already in the END state, then it is {@link #reset reset} and re-parsed. + * @throws IllegalStateException If the buffers have already been partially parsed. + */ + public void parse() throws IOException + { + if (_state==STATE_END) + reset(false); + if (_state!=STATE_START) + throw new IllegalStateException("!START"); + + // continue parsing + while (_state != STATE_END) + parseNext(); + } + + /* ------------------------------------------------------------------------------- */ + /** + * Parse until END state. + * This method will parse any remaining content in the current buffer. It does not care about the + * {@link #getState current state} of the parser. + * @see #parse + * @see #parseNext + */ + public long parseAvailable() throws IOException + { + long len = parseNext(); + long total=len>0?len:0; + + // continue parsing + while (!isComplete() && _buffer!=null && _buffer.length()>0) + { + len = parseNext(); + if (len>0) + total+=len; + } + return total; + } + + + + /* ------------------------------------------------------------------------------- */ + /** + * Parse until next Event. + * @returns number of bytes filled from endpoint or -1 if fill never called. + */ + public long parseNext() throws IOException + { + long total_filled=-1; + + if (_state == STATE_END) + return -1; + + if (_buffer==null) + { + if (_header == null) + { + _header=_buffers.getBuffer(_headerBufferSize); + } + _buffer=_header; + _tok0=new View.CaseInsensitive(_header); + _tok1=new View.CaseInsensitive(_header); + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + } + + + if (_state == STATE_CONTENT && _contentPosition == _contentLength) + { + _state=STATE_END; + _handler.messageComplete(_contentPosition); + return total_filled; + } + + int length=_buffer.length(); + + // Fill buffer if we can + if (length == 0) + { + int filled=-1; + if (_body!=null && _buffer!=_body) + { + _buffer=_body; + filled=_buffer.length(); + } + + if (_buffer.markIndex() == 0 && _buffer.putIndex() == _buffer.capacity()) + throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL"); + + IOException ioex=null; + + if (_endp != null && filled<=0) + { + // Compress buffer if handling _content buffer + // TODO check this is not moving data too much + if (_buffer == _body) + _buffer.compact(); + + if (_buffer.space() == 0) + throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head")); + try + { + if (total_filled<0) + total_filled=0; + filled=_endp.fill(_buffer); + if (filled>0) + total_filled+=filled; + } + catch(IOException e) + { + Log.debug(e); + ioex=e; + filled=-1; + } + } + + if (filled < 0) + { + if ( _state == STATE_EOF_CONTENT) + { + if (_buffer.length()>0) + { + // TODO should we do this here or fall down to main loop? + Buffer chunk=_buffer.get(_buffer.length()); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + } + _state=STATE_END; + _handler.messageComplete(_contentPosition); + return total_filled; + } + reset(true); + throw new EofException(ioex); + } + length=_buffer.length(); + } + + + // EventHandler header + byte ch; + byte[] array=_buffer.array(); + + while (_state0) + { + ch=_buffer.get(); + + if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED) + { + _eol=HttpTokens.LINE_FEED; + continue; + } + _eol=0; + + switch (_state) + { + case STATE_START: + _contentLength=HttpTokens.UNKNOWN_CONTENT; + _cached=null; + if (ch > HttpTokens.SPACE || ch<0) + { + _buffer.mark(); + _state=STATE_FIELD0; + } + break; + + case STATE_FIELD0: + if (ch == HttpTokens.SPACE) + { + _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1); + _state=STATE_SPACE1; + continue; + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + break; + + case STATE_SPACE1: + if (ch > HttpTokens.SPACE || ch<0) + { + _buffer.mark(); + _state=STATE_FIELD1; + } + else if (ch < HttpTokens.SPACE) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + break; + + case STATE_FIELD1: + if (ch == HttpTokens.SPACE) + { + _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); + _state=STATE_SPACE2; + continue; + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + // HTTP/0.9 + _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer + .sliceFromMark(), null); + _state=STATE_END; + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + return total_filled; + } + break; + + case STATE_SPACE2: + if (ch > HttpTokens.SPACE || ch<0) + { + _buffer.mark(); + _state=STATE_FIELD2; + } + else if (ch < HttpTokens.SPACE) + { + // HTTP/0.9 + _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null); + _state=STATE_END; + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + return total_filled; + } + break; + + case STATE_FIELD2: + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + + // TODO - we really should know if we are parsing request or response! + final Buffer method = HttpMethods.CACHE.lookup(_tok0); + if (method==_tok0 && _tok1.length()==3 && Character.isDigit(_tok1.peek())) + { + _responseStatus = BufferUtil.toInt(_tok1); + _handler.startResponse(HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark()); + } + else + _handler.startRequest(method, _tok1,HttpVersions.CACHE.lookup(_buffer.sliceFromMark())); + _eol=ch; + _state=STATE_HEADER; + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + _multiLineValue=null; + continue; + } + break; + + case STATE_HEADER: + switch(ch) + { + case HttpTokens.COLON: + case HttpTokens.SPACE: + case HttpTokens.TAB: + { + // header value without name - continuation? + _length=-1; + _state=STATE_HEADER_VALUE; + break; + } + + default: + { + // handler last header if any + if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null) + { + + Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0); + _cached=null; + Buffer value=_multiLineValue == null ? (Buffer) _tok1 : (Buffer) new ByteArrayBuffer(_multiLineValue); + + int ho=HttpHeaders.CACHE.getOrdinal(header); + if (ho >= 0) + { + int vo=-1; + + switch (ho) + { + case HttpHeaders.CONTENT_LENGTH_ORDINAL: + if (_contentLength != HttpTokens.CHUNKED_CONTENT) + { + _contentLength=BufferUtil.toLong(value); + if (_contentLength <= 0) + _contentLength=HttpTokens.NO_CONTENT; + } + break; + + case HttpHeaders.TRANSFER_ENCODING_ORDINAL: + value=HttpHeaderValues.CACHE.lookup(value); + vo=HttpHeaderValues.CACHE.getOrdinal(value); + if (HttpHeaderValues.CHUNKED_ORDINAL == vo) + _contentLength=HttpTokens.CHUNKED_CONTENT; + else + { + String c=value.toString(StringUtil.__ISO_8859_1); + if (c.endsWith(HttpHeaderValues.CHUNKED)) + _contentLength=HttpTokens.CHUNKED_CONTENT; + + else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0) + throw new HttpException(400,null); + } + break; + } + } + + _handler.parsedHeader(header, value); + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + _multiLineValue=null; + } + + + // now handle ch + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + // End of header + + // work out the _content demarcation + if (_contentLength == HttpTokens.UNKNOWN_CONTENT) + { + if (_responseStatus == 0 // request + || _responseStatus == 304 // not-modified response + || _responseStatus == 204 // no-content response + || _responseStatus < 200) // 1xx response + _contentLength=HttpTokens.NO_CONTENT; + else + _contentLength=HttpTokens.EOF_CONTENT; + } + + _contentPosition=0; + _eol=ch; + // We convert _contentLength to an int for this switch statement because + // we don't care about the amount of data available just whether there is some. + switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength) + { + case HttpTokens.EOF_CONTENT: + _state=STATE_EOF_CONTENT; + if(_body==null && _buffers!=null) + _body=_buffers.getBuffer(_contentBufferSize); + + _handler.headerComplete(); // May recurse here ! + break; + + case HttpTokens.CHUNKED_CONTENT: + _state=STATE_CHUNKED_CONTENT; + if (_body==null && _buffers!=null) + _body=_buffers.getBuffer(_contentBufferSize); + _handler.headerComplete(); // May recurse here ! + break; + + case HttpTokens.NO_CONTENT: + _state=STATE_END; + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + break; + + default: + _state=STATE_CONTENT; + if(_forceContentBuffer || + (_buffers!=null && _body==null && _buffer==_header && _contentLength>=(_header.capacity()-_header.getIndex()))) + _body=_buffers.getBuffer(_contentBufferSize); + _handler.headerComplete(); // May recurse here ! + break; + } + return total_filled; + } + else + { + // New header + _length=1; + _buffer.mark(); + _state=STATE_HEADER_NAME; + + // try cached name! + if (array!=null) + { + _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1); + + if (_cached!=null) + { + _length=_cached.length(); + _buffer.setGetIndex(_buffer.markIndex()+_length); + length=_buffer.length(); + } + } + } + } + } + + break; + + case STATE_HEADER_NAME: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.COLON: + if (_length > 0 && _cached==null) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _length=-1; + _state=STATE_HEADER_VALUE; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + break; + default: + { + _cached=null; + if (_length == -1) + _buffer.mark(); + _length=_buffer.getIndex() - _buffer.markIndex(); + _state=STATE_HEADER_IN_NAME; + } + } + + break; + + case STATE_HEADER_IN_NAME: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.COLON: + if (_length > 0 && _cached==null) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _length=-1; + _state=STATE_HEADER_VALUE; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _state=STATE_HEADER_NAME; + break; + default: + { + _cached=null; + _length++; + } + } + break; + + case STATE_HEADER_VALUE: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + { + if (_tok1.length() == 0) + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + else + { + // Continuation line! + if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); + } + } + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + break; + default: + { + if (_length == -1) + _buffer.mark(); + _length=_buffer.getIndex() - _buffer.markIndex(); + _state=STATE_HEADER_IN_VALUE; + } + } + break; + + case STATE_HEADER_IN_VALUE: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + { + if (_tok1.length() == 0) + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + else + { + // Continuation line! + if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); + } + } + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _state=STATE_HEADER_VALUE; + break; + default: + _length++; + } + break; + } + } // end of HEADER states loop + + // ========================== + + // Handle _content + length=_buffer.length(); + Buffer chunk; + while (_state > STATE_END && length > 0) + { + if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED) + { + _eol=_buffer.get(); + length=_buffer.length(); + continue; + } + _eol=0; + switch (_state) + { + case STATE_EOF_CONTENT: + chunk=_buffer.get(_buffer.length()); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + // TODO adjust the _buffer to keep unconsumed content + return total_filled; + + case STATE_CONTENT: + { + long remaining=_contentLength - _contentPosition; + if (remaining == 0) + { + _state=STATE_END; + _handler.messageComplete(_contentPosition); + return total_filled; + } + + if (length > remaining) + { + // We can cast reamining to an int as we know that it is smaller than + // or equal to length which is already an int. + length=(int)remaining; + } + + chunk=_buffer.get(length); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + + if(_contentPosition == _contentLength) + { + _state=STATE_END; + _handler.messageComplete(_contentPosition); + } + // TODO adjust the _buffer to keep unconsumed content + return total_filled; + } + + case STATE_CHUNKED_CONTENT: + { + ch=_buffer.peek(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + _eol=_buffer.get(); + else if (ch <= HttpTokens.SPACE) + _buffer.get(); + else + { + _chunkLength=0; + _chunkPosition=0; + _state=STATE_CHUNK_SIZE; + } + break; + } + + case STATE_CHUNK_SIZE: + { + ch=_buffer.get(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + _eol=ch; + if (_chunkLength == 0) + { + _state=STATE_END; + _handler.messageComplete(_contentPosition); + return total_filled; + } + else + _state=STATE_CHUNK; + } + else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON) + _state=STATE_CHUNK_PARAMS; + else if (ch >= '0' && ch <= '9') + _chunkLength=_chunkLength * 16 + (ch - '0'); + else if (ch >= 'a' && ch <= 'f') + _chunkLength=_chunkLength * 16 + (10 + ch - 'a'); + else if (ch >= 'A' && ch <= 'F') + _chunkLength=_chunkLength * 16 + (10 + ch - 'A'); + else + throw new IOException("bad chunk char: " + ch); + break; + } + + case STATE_CHUNK_PARAMS: + { + ch=_buffer.get(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + _eol=ch; + if (_chunkLength == 0) + { + _state=STATE_END; + _handler.messageComplete(_contentPosition); + return total_filled; + } + else + _state=STATE_CHUNK; + } + break; + } + + case STATE_CHUNK: + { + int remaining=_chunkLength - _chunkPosition; + if (remaining == 0) + { + _state=STATE_CHUNKED_CONTENT; + break; + } + else if (length > remaining) + length=remaining; + chunk=_buffer.get(length); + _contentPosition += chunk.length(); + _chunkPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + // TODO adjust the _buffer to keep unconsumed content + return total_filled; + } + } + + length=_buffer.length(); + } + return total_filled; + } + + /* ------------------------------------------------------------------------------- */ + /** fill the buffers from the endpoint + * + */ + public long fill() throws IOException + { + if (_buffer==null) + { + _buffer=_header=getHeaderBuffer(); + _tok0=new View.CaseInsensitive(_buffer); + _tok1=new View.CaseInsensitive(_buffer); + } + if (_body!=null && _buffer!=_body) + _buffer=_body; + if (_buffer == _body) + _buffer.compact(); + + int space=_buffer.space(); + + // Fill buffer if we can + if (space == 0) + throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head")); + else + { + int filled=-1; + + if (_endp != null ) + { + try + { + filled=_endp.fill(_buffer); + } + catch(IOException e) + { + Log.debug(e); + reset(true); + throw (e instanceof EofException) ? e:new EofException(e); + } + } + + return filled; + } + } + + /* ------------------------------------------------------------------------------- */ + /** Skip any CRLFs in buffers + * + */ + public void skipCRLF() + { + + while (_header!=null && _header.length()>0) + { + byte ch = _header.peek(); + if (ch==HttpTokens.CARRIAGE_RETURN || ch==HttpTokens.LINE_FEED) + { + _eol=ch; + _header.skip(1); + } + else + break; + } + + while (_body!=null && _body.length()>0) + { + byte ch = _body.peek(); + if (ch==HttpTokens.CARRIAGE_RETURN || ch==HttpTokens.LINE_FEED) + { + _eol=ch; + _body.skip(1); + } + else + break; + } + + } + /* ------------------------------------------------------------------------------- */ + public void reset(boolean returnBuffers) + { + synchronized (this) + { + _contentView.setGetIndex(_contentView.putIndex()); + + _state=STATE_START; + _contentLength=HttpTokens.UNKNOWN_CONTENT; + _contentPosition=0; + _length=0; + _responseStatus=0; + + if (_buffer!=null && _buffer.length()>0 && _eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED) + { + _buffer.skip(1); + _eol=HttpTokens.LINE_FEED; + } + + if (_body!=null) + { + if (_body.hasContent()) + { + _header.setMarkIndex(-1); + _header.compact(); + // TODO if pipelined requests received after big input - maybe this is not good?. + _body.skip(_header.put(_body)); + + } + + if (_body.length()==0) + { + if (_buffers!=null && returnBuffers) + _buffers.returnBuffer(_body); + _body=null; + } + else + { + _body.setMarkIndex(-1); + _body.compact(); + } + } + + + if (_header!=null) + { + _header.setMarkIndex(-1); + if (!_header.hasContent() && _buffers!=null && returnBuffers) + { + _buffers.returnBuffer(_header); + _header=null; + _buffer=null; + } + else + { + _header.compact(); + _tok0.update(_header); + _tok0.update(0,0); + _tok1.update(_header); + _tok1.update(0,0); + } + } + + _buffer=_header; + } + } + + /* ------------------------------------------------------------------------------- */ + public void setState(int state) + { + this._state=state; + _contentLength=HttpTokens.UNKNOWN_CONTENT; + } + + /* ------------------------------------------------------------------------------- */ + public String toString(Buffer buf) + { + return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode(); + } + + /* ------------------------------------------------------------------------------- */ + public String toString() + { + return "state=" + _state + " length=" + _length + " len=" + _contentLength; + } + + /* ------------------------------------------------------------ */ + public Buffer getHeaderBuffer() + { + if (_header == null) + { + _header=_buffers.getBuffer(_headerBufferSize); + } + return _header; + } + + /* ------------------------------------------------------------ */ + public Buffer getBodyBuffer() + { + return _body; + } + + /* ------------------------------------------------------------ */ + /** + * @param force True if a new buffer will be forced to be used for content and the header buffer will not be used. + */ + public void setForceContentBuffer(boolean force) + { + _forceContentBuffer=force; + } + + + + /* ------------------------------------------------------------ */ + public Buffer blockForContent(long maxIdleTime) throws IOException + { + if (_contentView.length()>0) + return _contentView; + if (getState() <= HttpParser.STATE_END) + return null; + + // Handle simple end points. + if (_endp==null) + parseNext(); + + // Handle blocking end points + else if (_endp.isBlocking()) + { + try + { + parseNext(); + + // parse until some progress is made (or IOException thrown for timeout) + while(_contentView.length() == 0 && !isState(HttpParser.STATE_END)) + { + // Try to get more _parser._content + parseNext(); + } + } + catch(IOException e) + { + _endp.close(); + throw e; + } + } + else // Handle non-blocking end point + { + parseNext(); + + // parse until some progress is made (or IOException thrown for timeout) + while(_contentView.length() == 0 && !isState(HttpParser.STATE_END)) + { + if (!_endp.blockReadable(maxIdleTime)) + { + _endp.close(); + throw new EofException("timeout"); + } + + // Try to get more _parser._content + parseNext(); + } + } + + return _contentView.length()>0?_contentView:null; + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see java.io.InputStream#available() + */ + public int available() throws IOException + { + if (_contentView!=null && _contentView.length()>0) + return _contentView.length(); + if (!_endp.isBlocking()) + parseNext(); + + return _contentView==null?0:_contentView.length(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static abstract class EventHandler + { + public abstract void content(Buffer ref) throws IOException; + + public void headerComplete() throws IOException + { + } + + public void messageComplete(long contentLength) throws IOException + { + } + + /** + * This is the method called by parser when a HTTP Header name and value is found + */ + public void parsedHeader(Buffer name, Buffer value) throws IOException + { + } + + /** + * This is the method called by parser when the HTTP request line is parsed + */ + public abstract void startRequest(Buffer method, Buffer url, Buffer version) + throws IOException; + + /** + * This is the method called by parser when the HTTP request line is parsed + */ + public abstract void startResponse(Buffer version, int status, Buffer reason) + throws IOException; + } + + + + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpSchemes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpSchemes.java new file mode 100644 index 00000000000..1046c78271f --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpSchemes.java @@ -0,0 +1,33 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class HttpSchemes +{ + public final static String + HTTP ="http", + HTTPS="https"; + + public final static Buffer + HTTP_BUFFER = new ByteArrayBuffer(HTTP), + HTTPS_BUFFER = new ByteArrayBuffer(HTTPS); +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java new file mode 100644 index 00000000000..03a1f37af54 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java @@ -0,0 +1,1033 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import org.eclipse.jetty.util.TypeUtil; + +/** + *

+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see + * table below) + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
EnumCodeMessage + * RFC 1945 - HTTP/1.0 + * RFC 2616 - HTTP/1.1 + * RFC 2518 - WEBDAV
Informational - 1xx{@link #isInformational(int)}
{@link #CONTINUE}100Continue  + * Sec. 10.1.1 
{@link #SWITCHING_PROTOCOLS}101Switching Protocols  + * Sec. 10.1.2 
{@link #PROCESSING}102Processing   + * Sec. 10.1
Success - 2xx{@link #isSuccess(int)}
{@link #OK}200OK + * Sec. 9.2 + * Sec. 10.2.1 
{@link #CREATED}201Created + * Sec. 9.2 + * Sec. 10.2.2 
{@link #ACCEPTED}202Accepted + * Sec. 9.2 + * Sec. 10.2.3 
{@link #NON_AUTHORITATIVE_INFORMATION}203Non Authoritative Information  + * Sec. 10.2.4 
{@link #NO_CONTENT}204No Content + * Sec. 9.2 + * Sec. 10.2.5 
{@link #RESET_CONTENT}205Reset Content  + * Sec. 10.2.6 
{@link #PARTIAL_CONTENT}206Partial Content  + * Sec. 10.2.7 
{@link #MULTI_STATUS}207Multi-Status   + * Sec. 10.2
 207Partial Update OK  + * draft/01 
Redirection - 3xx{@link #isRedirection(int)}
{@link #MULTIPLE_CHOICES}300Multiple Choices + * Sec. 9.3 + * Sec. 10.3.1 
{@link #MOVED_PERMANENTLY}301Moved Permanently + * Sec. 9.3 + * Sec. 10.3.2 
{@link #MOVED_TEMPORARILY}302Moved Temporarily + * Sec. 9.3(now "302 Found") 
{@link #FOUND}302Found(was "302 Moved Temporarily") + * Sec. 10.3.3 
{@link #SEE_OTHER}303See Other  + * Sec. 10.3.4 
{@link #NOT_MODIFIED}304Not Modified + * Sec. 9.3 + * Sec. 10.3.5 
{@link #USE_PROXY}305Use Proxy  + * Sec. 10.3.6 
 306(Unused)  + * Sec. 10.3.7 
{@link #TEMPORARY_REDIRECT}307Temporary Redirect  + * Sec. 10.3.8 
Client Error - 4xx{@link #isClientError(int)}
{@link #BAD_REQUEST}400Bad Request + * Sec. 9.4 + * Sec. 10.4.1 
{@link #UNAUTHORIZED}401Unauthorized + * Sec. 9.4 + * Sec. 10.4.2 
{@link #PAYMENT_REQUIRED}402Payment Required + * Sec. 9.4 + * Sec. 10.4.3 
{@link #FORBIDDEN}403Forbidden + * Sec. 9.4 + * Sec. 10.4.4 
{@link #NOT_FOUND}404Not Found + * Sec. 9.4 + * Sec. 10.4.5 
{@link #METHOD_NOT_ALLOWED}405Method Not Allowed  + * Sec. 10.4.6 
{@link #NOT_ACCEPTABLE}406Not Acceptable  + * Sec. 10.4.7 
{@link #PROXY_AUTHENTICATION_REQUIRED}407Proxy Authentication Required  + * Sec. 10.4.8 
{@link #REQUEST_TIMEOUT}408Request Timeout  + * Sec. 10.4.9 
{@link #CONFLICT}409Conflict  + * Sec. 10.4.10 + *  
{@link #GONE}410Gone  + * Sec. 10.4.11 + *  
{@link #LENGTH_REQUIRED}411Length Required  + * Sec. 10.4.12 + *  
{@link #PRECONDITION_FAILED}412Precondition Failed  + * Sec. 10.4.13 + *  
{@link #REQUEST_ENTITY_TOO_LARGE}413Request Entity Too Large  + * Sec. 10.4.14 + *  
{@link #REQUEST_URI_TOO_LONG}414Request-URI Too Long  + * Sec. 10.4.15 + *  
{@link #UNSUPPORTED_MEDIA_TYPE}415Unsupported Media Type  + * Sec. 10.4.16 + *  
{@link #REQUESTED_RANGE_NOT_SATISFIABLE}416Requested Range Not Satisfiable  + * Sec. 10.4.17 + *  
{@link #EXPECTATION_FAILED}417Expectation Failed  + * Sec. 10.4.18 + *  
 418Reauthentication Required  + * draft/01 
 418Unprocessable Entity   + * draft/05
 419Proxy Reauthentication Required  + * draft/01 
 419Insufficient Space on Resource   + * draft/05
 420Method Failure   + * draft/05
 421(Unused)   
{@link #UNPROCESSABLE_ENTITY}422Unprocessable Entity   + * Sec. 10.3
{@link #LOCKED}423Locked   + * Sec. 10.4
{@link #FAILED_DEPENDENCY}424Failed Dependency   + * Sec. 10.5
Server Error - 5xx{@link #isServerError(int)}
{@link #INTERNAL_SERVER_ERROR}500Internal Server Error + * Sec. 9.5 + * Sec. 10.5.1 
{@link #NOT_IMPLEMENTED}501Not Implemented + * Sec. 9.5 + * Sec. 10.5.2 
{@link #BAD_GATEWAY}502Bad Gateway + * Sec. 9.5 + * Sec. 10.5.3 
{@link #SERVICE_UNAVAILABLE}503Service Unavailable + * Sec. 9.5 + * Sec. 10.5.4 
{@link #GATEWAY_TIMEOUT}504Gateway Timeout  + * Sec. 10.5.5 
{@link #HTTP_VERSION_NOT_SUPPORTED}505HTTP Version Not Supported  + * Sec. 10.5.6 
 506(Unused)   
{@link #INSUFFICIENT_STORAGE}507Insufficient Storage   + * Sec. 10.6
+ * + * @version $Id$ + */ +public class HttpStatus +{ + public final static int CONTINUE_100 = 100; + public final static int SWITCHING_PROTOCOLS_101 = 101; + public final static int PROCESSING_102 = 102; + + public final static int OK_200 = 200; + public final static int CREATED_201 = 201; + public final static int ACCEPTED_202 = 202; + public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203; + public final static int NO_CONTENT_204 = 204; + public final static int RESET_CONTENT_205 = 205; + public final static int PARTIAL_CONTENT_206 = 206; + public final static int MULTI_STATUS_207 = 207; + + public final static int MULTIPLE_CHOICES_300 = 300; + public final static int MOVED_PERMANENTLY_301 = 301; + public final static int MOVED_TEMPORARILY_302 = 302; + public final static int FOUND_302 = 302; + public final static int SEE_OTHER_303 = 303; + public final static int NOT_MODIFIED_304 = 304; + public final static int USE_PROXY_305 = 305; + public final static int TEMPORARY_REDIRECT_307 = 307; + + public final static int BAD_REQUEST_400 = 400; + public final static int UNAUTHORIZED_401 = 401; + public final static int PAYMENT_REQUIRED_402 = 402; + public final static int FORBIDDEN_403 = 403; + public final static int NOT_FOUND_404 = 404; + public final static int METHOD_NOT_ALLOWED_405 = 405; + public final static int NOT_ACCEPTABLE_406 = 406; + public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407; + public final static int REQUEST_TIMEOUT_408 = 408; + public final static int CONFLICT_409 = 409; + public final static int GONE_410 = 410; + public final static int LENGTH_REQUIRED_411 = 411; + public final static int PRECONDITION_FAILED_412 = 412; + public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413; + public final static int REQUEST_URI_TOO_LONG_414 = 414; + public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415; + public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416; + public final static int EXPECTATION_FAILED_417 = 417; + public final static int UNPROCESSABLE_ENTITY_422 = 422; + public final static int LOCKED_423 = 423; + public final static int FAILED_DEPENDENCY_424 = 424; + + public final static int INTERNAL_SERVER_ERROR_500 = 500; + public final static int NOT_IMPLEMENTED_501 = 501; + public final static int BAD_GATEWAY_502 = 502; + public final static int SERVICE_UNAVAILABLE_503 = 503; + public final static int GATEWAY_TIMEOUT_504 = 504; + public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505; + public final static int INSUFFICIENT_STORAGE_507 = 507; + + public static final int MAX_CODE = 507; + + + private static Code codeMap[] = new Code[MAX_CODE+1]; + + static + { + for (Code code : Code.values()) + { + codeMap[code._code] = code; + } + } + + + public enum Code + { + /* + * -------------------------------------------------------------------- + * Informational messages in 1xx series. As defined by ... RFC 1945 - + * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV + */ + + /** 100 Continue */ + CONTINUE(CONTINUE_100, "Continue"), + /** 101 Switching Protocols */ + SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"), + /** 102 Processing */ + PROCESSING(PROCESSING_102, "Processing"), + + /* + * -------------------------------------------------------------------- + * Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0 + * RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV + */ + + /** 200 OK */ + OK(OK_200, "OK"), + /** 201 Created */ + CREATED(CREATED_201, "Created"), + /** 202 Accepted */ + ACCEPTED(ACCEPTED_202, "Accepted"), + /** 203 Non Authoritative Information */ + NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"), + /** 204 No Content */ + NO_CONTENT(NO_CONTENT_204, "No Content"), + /** 205 Reset Content */ + RESET_CONTENT(RESET_CONTENT_205, "Reset Content"), + /** 206 Partial Content */ + PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"), + /** 207 Multi-Status */ + MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"), + + /* + * -------------------------------------------------------------------- + * Redirection messages in 3xx series. As defined by ... RFC 1945 - + * HTTP/1.0 RFC 2616 - HTTP/1.1 + */ + + /** 300 Mutliple Choices */ + MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"), + /** 301 Moved Permanently */ + MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"), + /** 302 Moved Temporarily */ + MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"), + /** 302 Found */ + FOUND(FOUND_302, "Found"), + /** 303 See Other */ + SEE_OTHER(SEE_OTHER_303, "See Other"), + /** 304 Not Modified */ + NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"), + /** 305 Use Proxy */ + USE_PROXY(USE_PROXY_305, "Use Proxy"), + /** 307 Temporary Redirect */ + TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"), + + /* + * -------------------------------------------------------------------- + * Client Error messages in 4xx series. As defined by ... RFC 1945 - + * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV + */ + + /** 400 Bad Request */ + BAD_REQUEST(BAD_REQUEST_400, "Bad Request"), + /** 401 Unauthorized */ + UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"), + /** 402 Payment Required */ + PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"), + /** 403 Forbidden */ + FORBIDDEN(FORBIDDEN_403, "Forbidden"), + /** 404 Not Found */ + NOT_FOUND(NOT_FOUND_404, "Not Found"), + /** 405 Method Not Allowed */ + METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"), + /** 406 Not Acceptable */ + NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"), + /** 407 Proxy Authentication Required */ + PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"), + /** 408 Request Timeout */ + REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"), + /** 409 Conflict */ + CONFLICT(CONFLICT_409, "Conflict"), + /** 410 Gone */ + GONE(GONE_410, "Gone"), + /** 411 Length Required */ + LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"), + /** 412 Precondition Failed */ + PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"), + /** 413 Request Entity Too Large */ + REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"), + /** 414 Request-URI Too Long */ + REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"), + /** 415 Unsupported Media Type */ + UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"), + /** 416 Requested Range Not Satisfiable */ + REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"), + /** 417 Expectation Failed */ + EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"), + /** 422 Unprocessable Entity */ + UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"), + /** 423 Locked */ + LOCKED(LOCKED_423, "Locked"), + /** 424 Failed Dependency */ + FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"), + + /* + * -------------------------------------------------------------------- + * Server Error messages in 5xx series. As defined by ... RFC 1945 - + * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV + */ + + /** 500 Server Error */ + INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"), + /** 501 Not Implemented */ + NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"), + /** 502 Bad Gateway */ + BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"), + /** 503 Service Unavailable */ + SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"), + /** 504 Gateway Timeout */ + GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"), + /** 505 HTTP Version Not Supported */ + HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"), + /** 507 Insufficient Storage */ + INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage"); + + private final int _code; + private final String _message; + + private Code(int code, String message) + { + this._code = code; + _message=message; + } + + public int getCode() + { + return _code; + } + + public String getMessage() + { + return _message; + } + + + public boolean equals(int code) + { + return (this._code == code); + } + + @Override + public String toString() + { + return String.format("[%03d %s]",this._code,this.getMessage()); + } + + /** + * Simple test against an code to determine if it falls into the + * Informational message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Informational messages. + */ + public boolean isInformational() + { + return HttpStatus.isInformational(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * Success message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Success messages. + */ + public boolean isSuccess() + { + return HttpStatus.isSuccess(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * Redirection message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Redirection messages. + */ + public boolean isRedirection() + { + return HttpStatus.isRedirection(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * Client Error message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Client Error messages. + */ + public boolean isClientError() + { + return HttpStatus.isClientError(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * Server Error message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Server Error messages. + */ + public boolean isServerError() + { + return HttpStatus.isServerError(this._code); + } + }; + + + /** + * Get the HttpStatusCode for a specific code + * + * @param code + * the code to lookup. + * @return the {@link HttpStatus} if found, or null if not found. + */ + public static Code getCode(int code) + { + if (code <= MAX_CODE) + { + return codeMap[code]; + } + return null; + } + + /** + * Get the status message for a specific code. + * + * @param code + * the code to look up + * @return the specific message, or the code number itself if code + * does not match known list. + */ + public static String getMessage(int code) + { + Code codeEnum = getCode(code); + if (codeEnum != null) + { + return codeEnum.getMessage(); + } + else + { + return TypeUtil.toString(code); + } + } + + /** + * Simple test against an code to determine if it falls into the + * Informational message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Informational messages. + */ + public static boolean isInformational(int code) + { + return ((100 <= code) && (code <= 199)); + } + + /** + * Simple test against an code to determine if it falls into the + * Success message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Success messages. + */ + public static boolean isSuccess(int code) + { + return ((200 <= code) && (code <= 299)); + } + + /** + * Simple test against an code to determine if it falls into the + * Redirection message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Redirection messages. + */ + public static boolean isRedirection(int code) + { + return ((300 <= code) && (code <= 399)); + } + + /** + * Simple test against an code to determine if it falls into the + * Client Error message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Client Error messages. + */ + public static boolean isClientError(int code) + { + return ((400 <= code) && (code <= 499)); + } + + /** + * Simple test against an code to determine if it falls into the + * Server Error message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Server Error messages. + */ + public static boolean isServerError(int code) + { + return ((500 <= code) && (code <= 599)); + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java new file mode 100644 index 00000000000..1f19786ea7e --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java @@ -0,0 +1,37 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +/** + * HTTP constants + */ +public interface HttpTokens +{ + // Terminal symbols. + static final byte COLON= (byte)':'; + static final byte SPACE= 0x20; + static final byte CARRIAGE_RETURN= 0x0D; + static final byte LINE_FEED= 0x0A; + static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED}; + static final byte SEMI_COLON= (byte)';'; + static final byte TAB= 0x09; + + public static final int SELF_DEFINING_CONTENT= -4; + public static final int UNKNOWN_CONTENT= -3; + public static final int CHUNKED_CONTENT= -2; + public static final int EOF_CONTENT= -1; + public static final int NO_CONTENT= 0; + + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java new file mode 100644 index 00000000000..c5d9f409d5e --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java @@ -0,0 +1,592 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.UnsupportedEncodingException; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.Utf8StringBuilder; + + +/* ------------------------------------------------------------ */ +/** Http URI. + * Parse a HTTP URI from a string or byte array. Given a URI + * http://user@host:port/path/info;param?query#fragment + * this class will split it into the following undecoded optional elements:
    + *
  • {@link #getScheme()} - http:
  • + *
  • {@link #getAuthority()} - //name@host:port
  • + *
  • {@link #getHost()} - host
  • + *
  • {@link #getPort()} - port
  • + *
  • {@link #getPath()} - /path/info
  • + *
  • {@link #getParam()} - param
  • + *
  • {@link #getQuery()} - query
  • + *
  • {@link #getFragment()} - fragment
  • + *
+ * + */ +public class HttpURI +{ + private static byte[] __empty={}; + private final static int + START=0, + AUTH_OR_PATH=1, + SCHEME_OR_PATH=2, + AUTH=4, + IPV6=5, + PORT=6, + PATH=7, + PARAM=8, + QUERY=9, + ASTERISK=10; + + boolean _partial=false; + byte[] _raw=__empty; + String _rawString; + int _scheme; + int _authority; + int _host; + int _port; + int _path; + int _param; + int _query; + int _fragment; + int _end; + boolean _encoded=false; + + Utf8StringBuilder _utf8b = new Utf8StringBuilder(64); + + public HttpURI() + { + + } + + /* ------------------------------------------------------------ */ + /** + * @param parsePartialAuth If True, parse auth without prior scheme, else treat all URIs starting with / as paths + */ + public HttpURI(boolean parsePartialAuth) + { + _partial=parsePartialAuth; + } + + public HttpURI(String raw) + { + _rawString=raw; + byte[] b = raw.getBytes(); + parse(b,0,b.length); + } + + public HttpURI(byte[] raw,int offset, int length) + { + parse2(raw,offset,length); + } + + public void parse(String raw) + { + byte[] b = raw.getBytes(); + parse2(b,0,b.length); + _rawString=raw; + } + + public void parse(byte[] raw,int offset, int length) + { + _rawString=null; + parse2(raw,offset,length); + } + + private void parse2(byte[] raw,int offset, int length) + { + _encoded=false; + _raw=raw; + int i=offset; + int e=offset+length; + int state=START; + int m=offset; + _end=offset+length; + _scheme=offset; + _authority=offset; + _host=offset; + _port=offset; + _path=offset; + _param=_end; + _query=_end; + _fragment=_end; + while (i6 && c=='t') + { + if (_raw[offset+3]==':') + { + s=offset+3; + i=offset+4; + c=':'; + } + else if (_raw[offset+4]==':') + { + s=offset+4; + i=offset+5; + c=':'; + } + else if (_raw[offset+5]==':') + { + s=offset+5; + i=offset+6; + c=':'; + } + } + + switch (c) + { + case ':': + { + m = i++; + _authority = m; + _path = m; + c = (char)(0xff & _raw[i]); + if (c == '/') + state = AUTH_OR_PATH; + else + { + _host = m; + _port = m; + state = PATH; + } + break; + } + + case '/': + { + state = PATH; + break; + } + + case ';': + { + _param = s; + state = PARAM; + break; + } + + case '?': + { + _param = s; + _query = s; + state = QUERY; + break; + } + + case '#': + { + _param = s; + _query = s; + _fragment = s; + break; + } + } + continue; + } + + case AUTH: + { + switch (c) + { + + case '/': + { + m = s; + _path = m; + _port = _path; + state = PATH; + break; + } + case '@': + { + _host = i; + break; + } + case ':': + { + _port = s; + state = PORT; + break; + } + case '[': + { + state = IPV6; + break; + } + } + continue; + } + + case IPV6: + { + switch (c) + { + case '/': + { + throw new IllegalArgumentException("No closing ']' for " + StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET)); + } + case ']': + { + state = AUTH; + break; + } + } + + continue; + } + + case PORT: + { + if (c=='/') + { + m=s; + _path=m; + if (_port<=_authority) + _port=_path; + state=PATH; + } + continue; + } + + case PATH: + { + switch (c) + { + case ';': + { + _param = s; + state = PARAM; + break; + } + case '?': + { + _param = s; + _query = s; + state = QUERY; + break; + } + case '#': + { + _param = s; + _query = s; + _fragment = s; + break state; + } + case '%': + { + _encoded=true; + } + } + continue; + } + + case PARAM: + { + switch (c) + { + case '?': + { + _query = s; + state = QUERY; + break; + } + case '#': + { + _query = s; + _fragment = s; + break state; + } + } + continue; + } + + case QUERY: + { + if (c=='#') + { + _fragment=s; + break state; + } + continue; + } + + case ASTERISK: + { + throw new IllegalArgumentException("only '*'"); + } + } + } + } + + private String toUtf8String(int offset,int length) + { + _utf8b.reset(); + _utf8b.append(_raw,offset,length); + return _utf8b.toString(); + } + + public String getScheme() + { + if (_scheme==_authority) + return null; + int l=_authority-_scheme; + if (l==5 && + _raw[_scheme]=='h' && + _raw[_scheme+1]=='t' && + _raw[_scheme+2]=='t' && + _raw[_scheme+3]=='p' ) + return HttpSchemes.HTTP; + if (l==6 && + _raw[_scheme]=='h' && + _raw[_scheme+1]=='t' && + _raw[_scheme+2]=='t' && + _raw[_scheme+3]=='p' && + _raw[_scheme+4]=='s' ) + return HttpSchemes.HTTPS; + + return toUtf8String(_scheme,_authority-_scheme-1); + } + + public String getAuthority() + { + if (_authority==_path) + return null; + return toUtf8String(_authority,_path-_authority); + } + + public String getHost() + { + if (_host==_port) + return null; + return toUtf8String(_host,_port-_host); + } + + public int getPort() + { + if (_port==_path) + return -1; + return TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10); + } + + public String getPath() + { + if (_path==_param) + return null; + return toUtf8String(_path,_param-_path); + } + + public String getDecodedPath() + { + if (_path==_param) + return null; + + int length = _param-_path; + byte[] bytes=null; + int n=0; + + for (int i=_path;i<_param;i++) + { + byte b = _raw[i]; + + if (b=='%' && (i+2)<_param) + { + b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16)); + i+=2; + } + else if (bytes==null) + { + n++; + continue; + } + + if (bytes==null) + { + bytes=new byte[length]; + for (int j=0;j_query); + } + + public String getFragment() + { + if (_fragment==_end) + return null; + return toUtf8String(_fragment+1,_end-_fragment-1); + } + + public void decodeQueryTo(MultiMap parameters) + { + if (_query==_fragment) + return; + _utf8b.reset(); + UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters,_utf8b); + } + + public void decodeQueryTo(MultiMap parameters, String encoding) + throws UnsupportedEncodingException + { + if (_query==_fragment) + return; + + if (encoding==null || StringUtil.isUTF8(encoding)) + UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters); + else + UrlEncoded.decodeTo(toUtf8String(_query+1,_fragment-_query-1),parameters,encoding); + } + + public void clear() + { + _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0; + _raw=__empty; + _rawString=""; + _encoded=false; + } + + public String toString() + { + if (_rawString==null) + _rawString=toUtf8String(_scheme,_end-_scheme); + return _rawString; + } + + public void writeTo(Utf8StringBuilder buf) + { + buf.append(_raw,_scheme,_end-_scheme); + } + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersions.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersions.java new file mode 100644 index 00000000000..07746b6d081 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersions.java @@ -0,0 +1,42 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class HttpVersions +{ + public final static String + HTTP_0_9 = "", + HTTP_1_0 = "HTTP/1.0", + HTTP_1_1 = "HTTP/1.1"; + + public final static int + HTTP_0_9_ORDINAL=9, + HTTP_1_0_ORDINAL=10, + HTTP_1_1_ORDINAL=11; + + public final static BufferCache CACHE = new BufferCache(); + + public final static Buffer + HTTP_0_9_BUFFER=CACHE.add(HTTP_0_9,HTTP_0_9_ORDINAL), + HTTP_1_0_BUFFER=CACHE.add(HTTP_1_0,HTTP_1_0_ORDINAL), + HTTP_1_1_BUFFER=CACHE.add(HTTP_1_1,HTTP_1_1_ORDINAL); +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java new file mode 100644 index 00000000000..4916602dc92 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java @@ -0,0 +1,368 @@ +// ======================================================================== +// Copyright (c) 2000-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +/** + * + */ +public class MimeTypes +{ + public final static String + FORM_ENCODED="application/x-www-form-urlencoded", + MESSAGE_HTTP="message/http", + MULTIPART_BYTERANGES="multipart/byteranges", + + TEXT_HTML="text/html", + TEXT_PLAIN="text/plain", + TEXT_XML="text/xml", + TEXT_JSON="text/json", + + TEXT_HTML_8859_1="text/html;charset=ISO-8859-1", + TEXT_PLAIN_8859_1="text/plain;charset=ISO-8859-1", + TEXT_XML_8859_1="text/xml;charset=ISO-8859-1", + + TEXT_HTML_UTF_8="text/html;charset=UTF-8", + TEXT_PLAIN_UTF_8="text/plain;charset=UTF-8", + TEXT_XML_UTF_8="text/xml;charset=UTF-8", + TEXT_JSON_UTF_8="text/json;charset=UTF-8"; + + private final static String + TEXT_HTML__8859_1="text/html; charset=ISO-8859-1", + TEXT_PLAIN__8859_1="text/plain; charset=ISO-8859-1", + TEXT_XML__8859_1="text/xml; charset=ISO-8859-1", + TEXT_HTML__UTF_8="text/html; charset=UTF-8", + TEXT_PLAIN__UTF_8="text/plain; charset=UTF-8", + TEXT_XML__UTF_8="text/xml; charset=UTF-8", + TEXT_JSON__UTF_8="text/json; charset=UTF-8"; + + private final static int + FORM_ENCODED_ORDINAL=1, + MESSAGE_HTTP_ORDINAL=2, + MULTIPART_BYTERANGES_ORDINAL=3, + + TEXT_HTML_ORDINAL=4, + TEXT_PLAIN_ORDINAL=5, + TEXT_XML_ORDINAL=6, + TEXT_JSON_ORDINAL=7, + + TEXT_HTML_8859_1_ORDINAL=8, + TEXT_PLAIN_8859_1_ORDINAL=9, + TEXT_XML_8859_1_ORDINAL=10, + + TEXT_HTML_UTF_8_ORDINAL=11, + TEXT_PLAIN_UTF_8_ORDINAL=12, + TEXT_XML_UTF_8_ORDINAL=13, + TEXT_JSON_UTF_8_ORDINAL=14; + + private static int __index=15; + + public final static BufferCache CACHE = new BufferCache(); + + public final static CachedBuffer + FORM_ENCODED_BUFFER=CACHE.add(FORM_ENCODED,FORM_ENCODED_ORDINAL), + MESSAGE_HTTP_BUFFER=CACHE.add(MESSAGE_HTTP, MESSAGE_HTTP_ORDINAL), + MULTIPART_BYTERANGES_BUFFER=CACHE.add(MULTIPART_BYTERANGES,MULTIPART_BYTERANGES_ORDINAL), + + TEXT_HTML_BUFFER=CACHE.add(TEXT_HTML,TEXT_HTML_ORDINAL), + TEXT_PLAIN_BUFFER=CACHE.add(TEXT_PLAIN,TEXT_PLAIN_ORDINAL), + TEXT_XML_BUFFER=CACHE.add(TEXT_XML,TEXT_XML_ORDINAL), + TEXT_JSON_BUFFER=CACHE.add(TEXT_JSON,TEXT_JSON_ORDINAL), + + TEXT_HTML_8859_1_BUFFER=CACHE.add(TEXT_HTML_8859_1,TEXT_HTML_8859_1_ORDINAL), + TEXT_PLAIN_8859_1_BUFFER=CACHE.add(TEXT_PLAIN_8859_1,TEXT_PLAIN_8859_1_ORDINAL), + TEXT_XML_8859_1_BUFFER=CACHE.add(TEXT_XML_8859_1,TEXT_XML_8859_1_ORDINAL), + + TEXT_HTML_UTF_8_BUFFER=CACHE.add(TEXT_HTML_UTF_8,TEXT_HTML_UTF_8_ORDINAL), + TEXT_PLAIN_UTF_8_BUFFER=CACHE.add(TEXT_PLAIN_UTF_8,TEXT_PLAIN_UTF_8_ORDINAL), + TEXT_XML_UTF_8_BUFFER=CACHE.add(TEXT_XML_UTF_8,TEXT_XML_UTF_8_ORDINAL), + TEXT_JSON_UTF_8_BUFFER=CACHE.add(TEXT_JSON_UTF_8,TEXT_JSON_UTF_8_ORDINAL), + + TEXT_HTML__8859_1_BUFFER=CACHE.add(TEXT_HTML__8859_1,TEXT_HTML_8859_1_ORDINAL), + TEXT_PLAIN__8859_1_BUFFER=CACHE.add(TEXT_PLAIN__8859_1,TEXT_PLAIN_8859_1_ORDINAL), + TEXT_XML__8859_1_BUFFER=CACHE.add(TEXT_XML__8859_1,TEXT_XML_8859_1_ORDINAL), + + TEXT_HTML__UTF_8_BUFFER=CACHE.add(TEXT_HTML__UTF_8,TEXT_HTML_UTF_8_ORDINAL), + TEXT_PLAIN__UTF_8_BUFFER=CACHE.add(TEXT_PLAIN__UTF_8,TEXT_PLAIN_UTF_8_ORDINAL), + TEXT_XML__UTF_8_BUFFER=CACHE.add(TEXT_XML__UTF_8,TEXT_XML_UTF_8_ORDINAL), + TEXT_JSON__UTF_8_BUFFER=CACHE.add(TEXT_JSON__UTF_8,TEXT_JSON_UTF_8_ORDINAL); + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private final static Map __dftMimeMap = new HashMap(); + private final static Map __encodings = new HashMap(); + static + { + try + { + ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime"); + Enumeration i = mime.getKeys(); + while(i.hasMoreElements()) + { + String ext = (String)i.nextElement(); + String m = mime.getString(ext); + __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m)); + } + } + catch(MissingResourceException e) + { + Log.warn(e.toString()); + Log.debug(e); + } + + try + { + ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding"); + Enumeration i = encoding.getKeys(); + while(i.hasMoreElements()) + { + Buffer type = normalizeMimeType((String)i.nextElement()); + __encodings.put(type,encoding.getString(type.toString())); + } + } + catch(MissingResourceException e) + { + Log.warn(e.toString()); + Log.debug(e); + } + + + TEXT_HTML_BUFFER.setAssociate("ISO-8859-1",TEXT_HTML_8859_1_BUFFER); + TEXT_HTML_BUFFER.setAssociate("ISO_8859_1",TEXT_HTML_8859_1_BUFFER); + TEXT_HTML_BUFFER.setAssociate("iso-8859-1",TEXT_HTML_8859_1_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("ISO-8859-1",TEXT_PLAIN_8859_1_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("ISO_8859_1",TEXT_PLAIN_8859_1_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("iso-8859-1",TEXT_PLAIN_8859_1_BUFFER); + TEXT_XML_BUFFER.setAssociate("ISO-8859-1",TEXT_XML_8859_1_BUFFER); + TEXT_XML_BUFFER.setAssociate("ISO_8859_1",TEXT_XML_8859_1_BUFFER); + TEXT_XML_BUFFER.setAssociate("iso-8859-1",TEXT_XML_8859_1_BUFFER); + + TEXT_HTML_BUFFER.setAssociate("UTF-8",TEXT_HTML_UTF_8_BUFFER); + TEXT_HTML_BUFFER.setAssociate("UTF8",TEXT_HTML_UTF_8_BUFFER); + TEXT_HTML_BUFFER.setAssociate("utf8",TEXT_HTML_UTF_8_BUFFER); + TEXT_HTML_BUFFER.setAssociate("utf-8",TEXT_HTML_UTF_8_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("UTF-8",TEXT_PLAIN_UTF_8_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("UTF8",TEXT_PLAIN_UTF_8_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("utf8",TEXT_PLAIN_UTF_8_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("utf-8",TEXT_PLAIN_UTF_8_BUFFER); + TEXT_XML_BUFFER.setAssociate("UTF-8",TEXT_XML_UTF_8_BUFFER); + TEXT_XML_BUFFER.setAssociate("UTF8",TEXT_XML_UTF_8_BUFFER); + TEXT_XML_BUFFER.setAssociate("utf8",TEXT_XML_UTF_8_BUFFER); + TEXT_XML_BUFFER.setAssociate("utf-8",TEXT_XML_UTF_8_BUFFER); + TEXT_JSON_BUFFER.setAssociate("UTF-8",TEXT_JSON_UTF_8_BUFFER); + TEXT_JSON_BUFFER.setAssociate("UTF8",TEXT_JSON_UTF_8_BUFFER); + TEXT_JSON_BUFFER.setAssociate("utf8",TEXT_JSON_UTF_8_BUFFER); + TEXT_JSON_BUFFER.setAssociate("utf-8",TEXT_JSON_UTF_8_BUFFER); + } + + + /* ------------------------------------------------------------ */ + private Map _mimeMap; + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public MimeTypes() + { + } + + /* ------------------------------------------------------------ */ + public synchronized Map getMimeMap() + { + return _mimeMap; + } + + /* ------------------------------------------------------------ */ + /** + * @param mimeMap A Map of file extension to mime-type. + */ + public void setMimeMap(Map mimeMap) + { + if (mimeMap==null) + { + _mimeMap=null; + return; + } + + Map m=new HashMap(); + Iterator i=mimeMap.entrySet().iterator(); + while (i.hasNext()) + { + Map.Entry entry = (Map.Entry)i.next(); + m.put(entry.getKey(),normalizeMimeType(entry.getValue().toString())); + } + _mimeMap=m; + } + + /* ------------------------------------------------------------ */ + /** Get the MIME type by filename extension. + * @param filename A file name + * @return MIME type matching the longest dot extension of the + * file name. + */ + public Buffer getMimeByExtension(String filename) + { + Buffer type=null; + + if (filename!=null) + { + int i=-1; + while(type==null) + { + i=filename.indexOf(".",i+1); + + if (i<0 || i>=filename.length()) + break; + + String ext=StringUtil.asciiToLowerCase(filename.substring(i+1)); + if (_mimeMap!=null) + type = (Buffer)_mimeMap.get(ext); + if (type==null) + type=(Buffer)__dftMimeMap.get(ext); + } + } + + if (type==null) + { + if (_mimeMap!=null) + type=(Buffer)_mimeMap.get("*"); + if (type==null) + type=(Buffer)__dftMimeMap.get("*"); + } + + return type; + } + + /* ------------------------------------------------------------ */ + /** Set a mime mapping + * @param extension + * @param type + */ + public void addMimeMapping(String extension,String type) + { + if (_mimeMap==null) + _mimeMap=new HashMap(); + + _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type)); + } + + /* ------------------------------------------------------------ */ + private static synchronized Buffer normalizeMimeType(String type) + { + Buffer b =CACHE.get(type); + if (b==null) + b=CACHE.add(type,__index++); + return b; + } + + /* ------------------------------------------------------------ */ + public static String getCharsetFromContentType(Buffer value) + { + if (value instanceof CachedBuffer) + { + switch(((CachedBuffer)value).getOrdinal()) + { + case TEXT_HTML_8859_1_ORDINAL: + case TEXT_PLAIN_8859_1_ORDINAL: + case TEXT_XML_8859_1_ORDINAL: + return StringUtil.__ISO_8859_1; + + case TEXT_JSON_ORDINAL: + case TEXT_HTML_UTF_8_ORDINAL: + case TEXT_PLAIN_UTF_8_ORDINAL: + case TEXT_XML_UTF_8_ORDINAL: + case TEXT_JSON_UTF_8_ORDINAL: + return StringUtil.__UTF8; + } + } + + int i=value.getIndex(); + int end=value.putIndex(); + int state=0; + int start=0; + boolean quote=false; + for (;i + * /foo/bar - an exact path specification. + * /foo/* - a prefix path specification (must end '/*'). + * *.ext - a suffix path specification. + * / - the default path specification. + * + * Matching is performed in the following order + *
  • Exact match. + *
  • Longest prefix match. + *
  • Longest suffix match. + *
  • default. + * + * Multiple path specifications can be mapped by providing a list of + * specifications. The list is separated by the characters specified + * in the "org.eclipse.http.PathMap.separators" System property, which + * defaults to : + *

    + * Special characters within paths such as '?� and ';' are not treated specially + * as it is assumed they would have been either encoded in the original URL or + * stripped from the path. + *

    + * This class is not synchronized for get's. If concurrent modifications are + * possible then it should be synchronized at a higher level. + * + * + */ +public class PathMap extends HashMap implements Externalizable +{ + /* ------------------------------------------------------------ */ + private static String __pathSpecSeparators = + System.getProperty("org.eclipse.http.PathMap.separators",":,"); + + /* ------------------------------------------------------------ */ + /** Set the path spec separator. + * Multiple path specification may be included in a single string + * if they are separated by the characters set in this string. + * The default value is ":," or whatever has been set by the + * system property org.eclipse.http.PathMap.separators + * @param s separators + */ + public static void setPathSpecSeparators(String s) + { + __pathSpecSeparators=s; + } + + /* --------------------------------------------------------------- */ + final StringMap _prefixMap=new StringMap(); + final StringMap _suffixMap=new StringMap(); + final StringMap _exactMap=new StringMap(); + + List _defaultSingletonList=null; + Entry _prefixDefault=null; + Entry _default=null; + Set _entrySet; + boolean _nodefault=false; + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap() + { + super(11); + _entrySet=entrySet(); + } + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap(boolean nodefault) + { + super(11); + _entrySet=entrySet(); + _nodefault=nodefault; + } + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap(int capacity) + { + super (capacity); + _entrySet=entrySet(); + } + + /* --------------------------------------------------------------- */ + /** Construct from dictionary PathMap. + */ + public PathMap(Map m) + { + putAll(m); + _entrySet=entrySet(); + } + + /* ------------------------------------------------------------ */ + public void writeExternal(java.io.ObjectOutput out) + throws java.io.IOException + { + HashMap map = new HashMap(this); + out.writeObject(map); + } + + /* ------------------------------------------------------------ */ + public void readExternal(java.io.ObjectInput in) + throws java.io.IOException, ClassNotFoundException + { + HashMap map = (HashMap)in.readObject(); + this.putAll(map); + } + + /* --------------------------------------------------------------- */ + /** Add a single path match to the PathMap. + * @param pathSpec The path specification, or comma separated list of + * path specifications. + * @param object The object the path maps to + */ + public synchronized Object put(Object pathSpec, Object object) + { + StringTokenizer tok = new StringTokenizer(pathSpec.toString(),__pathSpecSeparators); + Object old =null; + + while (tok.hasMoreTokens()) + { + String spec=tok.nextToken(); + + if (!spec.startsWith("/") && !spec.startsWith("*.")) + throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'"); + + old = super.put(spec,object); + + // Make entry that was just created. + Entry entry = new Entry(spec,object); + + if (entry.getKey().equals(spec)) + { + if (spec.equals("/*")) + _prefixDefault=entry; + else if (spec.endsWith("/*")) + { + String mapped=spec.substring(0,spec.length()-2); + entry.setMapped(mapped); + _prefixMap.put(mapped,entry); + _exactMap.put(mapped,entry); + _exactMap.put(spec.substring(0,spec.length()-1),entry); + } + else if (spec.startsWith("*.")) + _suffixMap.put(spec.substring(2),entry); + else if (spec.equals(URIUtil.SLASH)) + { + if (_nodefault) + _exactMap.put(spec,entry); + else + { + _default=entry; + _defaultSingletonList= + SingletonList.newSingletonList(_default); + } + } + else + { + entry.setMapped(spec); + _exactMap.put(spec,entry); + } + } + } + + return old; + } + + /* ------------------------------------------------------------ */ + /** Get object matched by the path. + * @param path the path. + * @return Best matched object or null. + */ + public Object match(String path) + { + Map.Entry entry = getMatch(path); + if (entry!=null) + return entry.getValue(); + return null; + } + + + /* --------------------------------------------------------------- */ + /** Get the entry mapped by the best specification. + * @param path the path. + * @return Map.Entry of the best matched or null. + */ + public Entry getMatch(String path) + { + Map.Entry entry; + + if (path==null) + return null; + + int l=path.length(); + + // try exact match + entry=_exactMap.getEntry(path,0,l); + if (entry!=null) + return (Entry) entry.getValue(); + + // prefix search + int i=l; + while((i=path.lastIndexOf('/',i-1))>=0) + { + entry=_prefixMap.getEntry(path,0,i); + if (entry!=null) + return (Entry) entry.getValue(); + } + + // Prefix Default + if (_prefixDefault!=null) + return _prefixDefault; + + // Extension search + i=0; + while ((i=path.indexOf('.',i+1))>0) + { + entry=_suffixMap.getEntry(path,i+1,l-i-1); + if (entry!=null) + return (Entry) entry.getValue(); + } + + // Default + return _default; + } + + /* --------------------------------------------------------------- */ + /** Get all entries matched by the path. + * Best match first. + * @param path Path to match + * @return LazyList of Map.Entry instances key=pathSpec + */ + public Object getLazyMatches(String path) + { + Map.Entry entry; + Object entries=null; + + if (path==null) + return LazyList.getList(entries); + + int l=path.length(); + + // try exact match + entry=_exactMap.getEntry(path,0,l); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + + // prefix search + int i=l-1; + while((i=path.lastIndexOf('/',i-1))>=0) + { + entry=_prefixMap.getEntry(path,0,i); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + } + + // Prefix Default + if (_prefixDefault!=null) + entries=LazyList.add(entries,_prefixDefault); + + // Extension search + i=0; + while ((i=path.indexOf('.',i+1))>0) + { + entry=_suffixMap.getEntry(path,i+1,l-i-1); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + } + + // Default + if (_default!=null) + { + // Optimization for just the default + if (entries==null) + return _defaultSingletonList; + + entries=LazyList.add(entries,_default); + } + + return entries; + } + + /* --------------------------------------------------------------- */ + /** Get all entries matched by the path. + * Best match first. + * @param path Path to match + * @return List of Map.Entry instances key=pathSpec + */ + public List getMatches(String path) + { + return LazyList.getList(getLazyMatches(path)); + } + + /* --------------------------------------------------------------- */ + public synchronized Object remove(Object pathSpec) + { + if (pathSpec!=null) + { + String spec=(String) pathSpec; + if (spec.equals("/*")) + _prefixDefault=null; + else if (spec.endsWith("/*")) + { + _prefixMap.remove(spec.substring(0,spec.length()-2)); + _exactMap.remove(spec.substring(0,spec.length()-1)); + _exactMap.remove(spec.substring(0,spec.length()-2)); + } + else if (spec.startsWith("*.")) + _suffixMap.remove(spec.substring(2)); + else if (spec.equals(URIUtil.SLASH)) + { + _default=null; + _defaultSingletonList=null; + } + else + _exactMap.remove(spec); + } + return super.remove(pathSpec); + } + + /* --------------------------------------------------------------- */ + public void clear() + { + _exactMap.clear(); + _prefixMap.clear(); + _suffixMap.clear(); + _default=null; + _defaultSingletonList=null; + super.clear(); + } + + /* --------------------------------------------------------------- */ + /** + * @return true if match. + */ + public static boolean match(String pathSpec, String path) + throws IllegalArgumentException + { + return match(pathSpec, path, false); + } + + /* --------------------------------------------------------------- */ + /** + * @return true if match. + */ + public static boolean match(String pathSpec, String path, boolean noDefault) + throws IllegalArgumentException + { + char c = pathSpec.charAt(0); + if (c=='/') + { + if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path)) + return true; + + if(isPathWildcardMatch(pathSpec, path)) + return true; + } + else if (c=='*') + return path.regionMatches(path.length()-pathSpec.length()+1, + pathSpec,1,pathSpec.length()-1); + return false; + } + + /* --------------------------------------------------------------- */ + private static boolean isPathWildcardMatch(String pathSpec, String path) + { + // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar" + int cpl=pathSpec.length()-2; + if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl)) + { + if (path.length()==cpl || '/'==path.charAt(cpl)) + return true; + } + return false; + } + + + /* --------------------------------------------------------------- */ + /** Return the portion of a path that matches a path spec. + * @return null if no match at all. + */ + public static String pathMatch(String pathSpec, String path) + { + char c = pathSpec.charAt(0); + + if (c=='/') + { + if (pathSpec.length()==1) + return path; + + if (pathSpec.equals(path)) + return path; + + if (isPathWildcardMatch(pathSpec, path)) + return path.substring(0,pathSpec.length()-2); + } + else if (c=='*') + { + if (path.regionMatches(path.length()-(pathSpec.length()-1), + pathSpec,1,pathSpec.length()-1)) + return path; + } + return null; + } + + /* --------------------------------------------------------------- */ + /** Return the portion of a path that is after a path spec. + * @return The path info string + */ + public static String pathInfo(String pathSpec, String path) + { + char c = pathSpec.charAt(0); + + if (c=='/') + { + if (pathSpec.length()==1) + return null; + + if (pathSpec.equals(path)) + return null; + + if (isPathWildcardMatch(pathSpec, path)) + { + if (path.length()==pathSpec.length()-2) + return null; + return path.substring(pathSpec.length()-2); + } + } + return null; + } + + + /* ------------------------------------------------------------ */ + /** Relative path. + * @param base The base the path is relative to. + * @param pathSpec The spec of the path segment to ignore. + * @param path the additional path + * @return base plus path with pathspec removed + */ + public static String relativePath(String base, + String pathSpec, + String path ) + { + String info=pathInfo(pathSpec,path); + if (info==null) + info=path; + + if( info.startsWith( "./")) + info = info.substring( 2); + if( base.endsWith( URIUtil.SLASH)) + if( info.startsWith( URIUtil.SLASH)) + path = base + info.substring(1); + else + path = base + info; + else + if( info.startsWith( URIUtil.SLASH)) + path = base + info; + else + path = base + URIUtil.SLASH + info; + return path; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static class Entry implements Map.Entry + { + private Object key; + private Object value; + private String mapped; + private transient String string; + + Entry(Object key, Object value) + { + this.key=key; + this.value=value; + } + + public Object getKey() + { + return key; + } + + public Object getValue() + { + return value; + } + + public Object setValue(Object o) + { + throw new UnsupportedOperationException(); + } + + public String toString() + { + if (string==null) + string=key+"="+value; + return string; + } + + public String getMapped() + { + return mapped; + } + + void setMapped(String mapped) + { + this.mapped = mapped; + } + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/security/B64Code.java b/jetty-http/src/main/java/org/eclipse/jetty/http/security/B64Code.java new file mode 100644 index 00000000000..83928bffd90 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/security/B64Code.java @@ -0,0 +1,280 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http.security; + +import java.io.UnsupportedEncodingException; + +import org.eclipse.jetty.util.StringUtil; + +/* ------------------------------------------------------------ */ + +/** Fast B64 Encoder/Decoder as described in RFC 1421. + *

    Does not insert or interpret whitespace as described in RFC + * 1521. If you require this you must pre/post process your data. + *

    Note that in a web context the usual case is to not want + * linebreaks or other white space in the encoded output. + * + * + * + */ +public class B64Code +{ + // ------------------------------------------------------------------ + static final char pad='='; + static final char[] nibble2code= + { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' + }; + + static byte[] code2nibble=null; + + static + { + code2nibble=new byte[256]; + for (int i=0;i<256;i++) + code2nibble[i]=-1; + for (byte b=0;b<64;b++) + code2nibble[(byte)nibble2code[b]]=b; + code2nibble[(byte)pad]=0; + } + + // ------------------------------------------------------------------ + /** + * Base 64 encode as described in RFC 1421. + *

    Does not insert whitespace as described in RFC 1521. + * @param s String to encode. + * @return String containing the encoded form of the input. + */ + static public String encode(String s) + { + try + { + return encode(s,null); + } + catch (UnsupportedEncodingException e) + { + throw new IllegalArgumentException(e.toString()); + } + } + + // ------------------------------------------------------------------ + /** + * Base 64 encode as described in RFC 1421. + *

    Does not insert whitespace as described in RFC 1521. + * @param s String to encode. + * @param charEncoding String representing the name of + * the character encoding of the provided input String. + * @return String containing the encoded form of the input. + */ + static public String encode(String s,String charEncoding) + throws UnsupportedEncodingException + { + byte[] bytes; + if (charEncoding==null) + bytes=s.getBytes(StringUtil.__ISO_8859_1); + else + bytes=s.getBytes(charEncoding); + + return new String(encode(bytes)); + } + + // ------------------------------------------------------------------ + /** + * Fast Base 64 encode as described in RFC 1421. + *

    Does not insert whitespace as described in RFC 1521. + *

    Avoids creating extra copies of the input/output. + * @param b byte array to encode. + * @return char array containing the encoded form of the input. + */ + static public char[] encode(byte[] b) + { + if (b==null) + return null; + + int bLen=b.length; + char r[]=new char[((bLen+2)/3)*4]; + int ri=0; + int bi=0; + byte b0, b1, b2; + int stop=(bLen/3)*3; + while (bi>>2)&0x3f]; + r[ri++]=nibble2code[(b0<<4)&0x3f|(b1>>>4)&0x0f]; + r[ri++]=nibble2code[(b1<<2)&0x3f|(b2>>>6)&0x03]; + r[ri++]=nibble2code[b2&077]; + } + + if (bLen!=bi) + { + switch (bLen%3) + { + case 2: + b0=b[bi++]; + b1=b[bi++]; + r[ri++]=nibble2code[(b0>>>2)&0x3f]; + r[ri++]=nibble2code[(b0<<4)&0x3f|(b1>>>4)&0x0f]; + r[ri++]=nibble2code[(b1<<2)&0x3f]; + r[ri++]=pad; + break; + + case 1: + b0=b[bi++]; + r[ri++]=nibble2code[(b0>>>2)&0x3f]; + r[ri++]=nibble2code[(b0<<4)&0x3f]; + r[ri++]=pad; + r[ri++]=pad; + break; + + default: + break; + } + } + + return r; + } + + // ------------------------------------------------------------------ + /** + * Base 64 decode as described in RFC 1421. + *

    Does not attempt to cope with extra whitespace + * as described in RFC 1521. + * @param s String to decode + * @return String decoded byte array. + */ + static public String decode(String s) + { + try + { + return decode(s,StringUtil.__ISO_8859_1); + } + catch (UnsupportedEncodingException e) + { + throw new IllegalArgumentException(e.toString()); + } + } + + // ------------------------------------------------------------------ + /** + * Base 64 decode as described in RFC 1421. + *

    Does not attempt to cope with extra whitespace + * as described in RFC 1521. + * @param s String to decode + * @param charEncoding String representing the character encoding + * used to map the decoded bytes into a String. + * @return String decoded byte array. + */ + static public String decode(String s,String charEncoding) + throws UnsupportedEncodingException + { + byte[] decoded=decode(s.toCharArray()); + + if (charEncoding==null) + return new String(decoded); + return new String(decoded,charEncoding); + } + + /* ------------------------------------------------------------ */ + /** + * Fast Base 64 decode as described in RFC 1421. + *

    Does not attempt to cope with extra whitespace + * as described in RFC 1521. + *

    Avoids creating extra copies of the input/output. + *

    Note this code has been flattened for performance. + * @param b char array to decode. + * @return byte array containing the decoded form of the input. + * @throws IllegalArgumentException if the input is not a valid + * B64 encoding. + */ + static public byte[] decode(char[] b) + { + if (b==null) + return null; + + int bLen=b.length; + if (bLen%4!=0) + throw new IllegalArgumentException("Input block size is not 4"); + + int li=bLen-1; + while (li>=0 && b[li]==(byte)pad) + li--; + + if (li<0) + return new byte[0]; + + // Create result array of exact required size. + int rLen=((li+1)*3)/4; + byte r[]=new byte[rLen]; + int ri=0; + int bi=0; + int stop=(rLen/3)*3; + byte b0,b1,b2,b3; + try + { + while (ri>>4); + r[ri++]=(byte)(b1<<4|b2>>>2); + r[ri++]=(byte)(b2<<6|b3); + } + + if (rLen!=ri) + { + switch (rLen%3) + { + case 2: + b0=code2nibble[b[bi++]]; + b1=code2nibble[b[bi++]]; + b2=code2nibble[b[bi++]]; + if (b0<0 || b1<0 || b2<0) + throw new IllegalArgumentException("Not B64 encoded"); + r[ri++]=(byte)(b0<<2|b1>>>4); + r[ri++]=(byte)(b1<<4|b2>>>2); + break; + + case 1: + b0=code2nibble[b[bi++]]; + b1=code2nibble[b[bi++]]; + if (b0<0 || b1<0) + throw new IllegalArgumentException("Not B64 encoded"); + r[ri++]=(byte)(b0<<2|b1>>>4); + break; + + default: + break; + } + } + } + catch (IndexOutOfBoundsException e) + { + throw new IllegalArgumentException("char "+bi + +" was not B64 encoded"); + } + + return r; + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/security/Constraint.java b/jetty-http/src/main/java/org/eclipse/jetty/http/security/Constraint.java new file mode 100644 index 00000000000..23736194c9a --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/security/Constraint.java @@ -0,0 +1,214 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http.security; + +import java.io.Serializable; + +/* ------------------------------------------------------------ */ +/** + * Describe an auth and/or data constraint. + * + * + */ +public class Constraint implements Cloneable, Serializable +{ + /* ------------------------------------------------------------ */ + public final static String __BASIC_AUTH = "BASIC"; + + public final static String __FORM_AUTH = "FORM"; + + public final static String __DIGEST_AUTH = "DIGEST"; + + public final static String __CERT_AUTH = "CLIENT_CERT"; + + public final static String __CERT_AUTH2 = "CLIENT-CERT"; + + public static boolean validateMethod (String method) + { + if (method == null) + return false; + method = method.trim(); + if (method.equals(__FORM_AUTH) + || method.equals(__BASIC_AUTH) + || method.equals (__DIGEST_AUTH) + || method.equals (__CERT_AUTH) + || method.equals(__CERT_AUTH2)) + return true; + return false; + } + + /* ------------------------------------------------------------ */ + public final static int DC_UNSET = -1, DC_NONE = 0, DC_INTEGRAL = 1, DC_CONFIDENTIAL = 2, DC_FORBIDDEN = 3; + + /* ------------------------------------------------------------ */ + public final static String NONE = "NONE"; + + public final static String ANY_ROLE = "*"; + + /* ------------------------------------------------------------ */ + private String _name; + + private String[] _roles; + + private int _dataConstraint = DC_UNSET; + + private boolean _anyRole = false; + + private boolean _authenticate = false; + + /* ------------------------------------------------------------ */ + /** + * Constructor. + */ + public Constraint() + { + } + + /* ------------------------------------------------------------ */ + /** + * Conveniance Constructor. + * + * @param name + * @param role + */ + public Constraint(String name, String role) + { + setName(name); + setRoles(new String[] { role }); + } + + /* ------------------------------------------------------------ */ + public Object clone() throws CloneNotSupportedException + { + return super.clone(); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + */ + public void setName(String name) + { + _name = name; + } + + /* ------------------------------------------------------------ */ + public void setRoles(String[] roles) + { + _roles = roles; + _anyRole = false; + if (roles != null) + for (int i = roles.length; !_anyRole && i-- > 0;) + _anyRole |= ANY_ROLE.equals(roles[i]); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if any user role is permitted. + */ + public boolean isAnyRole() + { + return _anyRole; + } + + /* ------------------------------------------------------------ */ + /** + * @return List of roles for this constraint. + */ + public String[] getRoles() + { + return _roles; + } + + /* ------------------------------------------------------------ */ + /** + * @param role + * @return True if the constraint contains the role. + */ + public boolean hasRole(String role) + { + if (_anyRole) return true; + if (_roles != null) for (int i = _roles.length; i-- > 0;) + if (role.equals(_roles[i])) return true; + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @param authenticate True if users must be authenticated + */ + public void setAuthenticate(boolean authenticate) + { + _authenticate = authenticate; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if the constraint requires request authentication + */ + public boolean getAuthenticate() + { + return _authenticate; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if authentication required but no roles set + */ + public boolean isForbidden() + { + return _authenticate && !_anyRole && (_roles == null || _roles.length == 0); + } + + /* ------------------------------------------------------------ */ + /** + * @param c Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL & + * 2=DC_CONFIDENTIAL + */ + public void setDataConstraint(int c) + { + if (c < 0 || c > DC_CONFIDENTIAL) throw new IllegalArgumentException("Constraint out of range"); + _dataConstraint = c; + } + + /* ------------------------------------------------------------ */ + /** + * @return Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL & + * 2=DC_CONFIDENTIAL + */ + public int getDataConstraint() + { + return _dataConstraint; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if a data constraint has been set. + */ + public boolean hasDataConstraint() + { + return _dataConstraint >= DC_NONE; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return "SC{" + _name + + "," + + (_anyRole ? "*" : (_roles == null ? "-" : _roles.toString())) + + "," + + (_dataConstraint == DC_UNSET ? "DC_UNSET}" : (_dataConstraint == DC_NONE ? "NONE}" : (_dataConstraint == DC_INTEGRAL ? "INTEGRAL}" : "CONFIDENTIAL}"))); + } + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/security/Credential.java b/jetty-http/src/main/java/org/eclipse/jetty/http/security/Credential.java new file mode 100644 index 00000000000..e06664d7db5 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/security/Credential.java @@ -0,0 +1,207 @@ +// ======================================================================== +// Copyright (c) 1998-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http.security; + +import java.security.MessageDigest; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** + * Credentials. The Credential class represents an abstract mechanism for + * checking authentication credentials. A credential instance either represents + * a secret, or some data that could only be derived from knowing the secret. + *

    + * Often a Credential is related to a Password via a one way algorithm, so while + * a Password itself is a Credential, a UnixCrypt or MD5 digest of a a password + * is only a credential that can be checked against the password. + *

    + * This class includes an implementation for unix Crypt an MD5 digest. + * + * @see Password + * + */ +public abstract class Credential +{ + /* ------------------------------------------------------------ */ + /** + * Check a credential + * + * @param credentials The credential to check against. This may either be + * another Credential object, a Password object or a String + * which is interpreted by this credential. + * @return True if the credentials indicated that the shared secret is known + * to both this Credential and the passed credential. + */ + public abstract boolean check(Object credentials); + + /* ------------------------------------------------------------ */ + /** + * Get a credential from a String. If the credential String starts with a + * known Credential type (eg "CRYPT:" or "MD5:" ) then a Credential of that + * type is returned. Else the credential is assumed to be a Password. + * + * @param credential String representation of the credential + * @return A Credential or Password instance. + */ + public static Credential getCredential(String credential) + { + if (credential.startsWith(Crypt.__TYPE)) return new Crypt(credential); + if (credential.startsWith(MD5.__TYPE)) return new MD5(credential); + + return new Password(credential); + } + + /* ------------------------------------------------------------ */ + /** + * Unix Crypt Credentials + */ + public static class Crypt extends Credential + { + public static final String __TYPE = "CRYPT:"; + + private String _cooked; + + Crypt(String cooked) + { + _cooked = cooked.startsWith(Crypt.__TYPE) ? cooked.substring(__TYPE.length()) : cooked; + } + + public boolean check(Object credentials) + { + if (!(credentials instanceof String) && !(credentials instanceof Password)) Log.warn("Can't check " + credentials.getClass() + " against CRYPT"); + + String passwd = credentials.toString(); + return _cooked.equals(UnixCrypt.crypt(passwd, _cooked)); + } + + public static String crypt(String user, String pw) + { + return "CRYPT:" + UnixCrypt.crypt(pw, user); + } + } + + /* ------------------------------------------------------------ */ + /** + * MD5 Credentials + */ + public static class MD5 extends Credential + { + public static final String __TYPE = "MD5:"; + + public static final Object __md5Lock = new Object(); + + private static MessageDigest __md; + + private byte[] _digest; + + /* ------------------------------------------------------------ */ + MD5(String digest) + { + digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest; + _digest = TypeUtil.parseBytes(digest, 16); + } + + /* ------------------------------------------------------------ */ + public byte[] getDigest() + { + return _digest; + } + + /* ------------------------------------------------------------ */ + public boolean check(Object credentials) + { + try + { + byte[] digest = null; + + if (credentials instanceof Password || credentials instanceof String) + { + synchronized (__md5Lock) + { + if (__md == null) __md = MessageDigest.getInstance("MD5"); + __md.reset(); + __md.update(credentials.toString().getBytes(StringUtil.__ISO_8859_1)); + digest = __md.digest(); + } + if (digest == null || digest.length != _digest.length) return false; + for (int i = 0; i < digest.length; i++) + if (digest[i] != _digest[i]) return false; + return true; + } + else if (credentials instanceof MD5) + { + MD5 md5 = (MD5) credentials; + if (_digest.length != md5._digest.length) return false; + for (int i = 0; i < _digest.length; i++) + if (_digest[i] != md5._digest[i]) return false; + return true; + } + else if (credentials instanceof Credential) + { + // Allow credential to attempt check - i.e. this'll work + // for DigestAuthModule$Digest credentials + return ((Credential) credentials).check(this); + } + else + { + Log.warn("Can't check " + credentials.getClass() + " against MD5"); + return false; + } + } + catch (Exception e) + { + Log.warn(e); + return false; + } + } + + /* ------------------------------------------------------------ */ + public static String digest(String password) + { + try + { + byte[] digest; + synchronized (__md5Lock) + { + if (__md == null) + { + try + { + __md = MessageDigest.getInstance("MD5"); + } + catch (Exception e) + { + Log.warn(e); + return null; + } + } + + __md.reset(); + __md.update(password.getBytes(StringUtil.__ISO_8859_1)); + digest = __md.digest(); + } + + return __TYPE + TypeUtil.toString(digest, 16); + } + catch (Exception e) + { + Log.warn(e); + return null; + } + } + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/security/Password.java b/jetty-http/src/main/java/org/eclipse/jetty/http/security/Password.java new file mode 100644 index 00000000000..f96fee71477 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/security/Password.java @@ -0,0 +1,229 @@ +// ======================================================================== +// Copyright (c) 1998-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http.security; + +import java.io.IOException; +import java.util.Arrays; + +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** + * Password utility class. + * + * This utility class gets a password or pass phrase either by: + * + *

    + *  + Password is set as a system property.
    + *  + The password is prompted for and read from standard input
    + *  + A program is run to get the password.
    + * 
    + * + * Passwords that begin with OBF: are de obfuscated. Passwords can be obfuscated + * by run org.eclipse.util.Password as a main class. Obfuscated password are + * required if a system needs to recover the full password (eg. so that it may + * be passed to another system). They are not secure, but prevent casual + * observation. + *

    + * Passwords that begin with CRYPT: are oneway encrypted with UnixCrypt. The + * real password cannot be retrieved, but comparisons can be made to other + * passwords. A Crypt can be generated by running org.eclipse.util.UnixCrypt as + * a main class, passing password and then the username. Checksum passwords are + * a secure(ish) way to store passwords that only need to be checked rather than + * recovered. Note that it is not strong security - specially if simple + * passwords are used. + * + * + */ +public class Password extends Credential +{ + public static final String __OBFUSCATE = "OBF:"; + + private String _pw; + + /* ------------------------------------------------------------ */ + /** + * Constructor. + * + * @param password The String password. + */ + public Password(String password) + { + _pw = password; + + // expand password + while (_pw != null && _pw.startsWith(__OBFUSCATE)) + _pw = deobfuscate(_pw); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return _pw; + } + + /* ------------------------------------------------------------ */ + public String toStarString() + { + return "*****************************************************".substring(0, _pw.length()); + } + + /* ------------------------------------------------------------ */ + public boolean check(Object credentials) + { + if (this == credentials) return true; + + if (credentials instanceof Password) return credentials.equals(_pw); + + if (credentials instanceof String) return credentials.equals(_pw); + + if (credentials instanceof char[]) return Arrays.equals(_pw.toCharArray(), (char[]) credentials); + + if (credentials instanceof Credential) return ((Credential) credentials).check(_pw); + + return false; + } + + /* ------------------------------------------------------------ */ + public boolean equals(Object o) + { + if (this == o) return true; + + if (null == o) return false; + + if (o instanceof Password) + { + Password p = (Password) o; + return p._pw == _pw || (null != _pw && _pw.equals(p._pw)); + } + + if (o instanceof String) return o.equals(_pw); + + return false; + } + + /* ------------------------------------------------------------ */ + public int hashCode() + { + return null == _pw ? super.hashCode() : _pw.hashCode(); + } + + /* ------------------------------------------------------------ */ + public static String obfuscate(String s) + { + StringBuilder buf = new StringBuilder(); + byte[] b = s.getBytes(); + + buf.append(__OBFUSCATE); + for (int i = 0; i < b.length; i++) + { + byte b1 = b[i]; + byte b2 = b[s.length() - (i + 1)]; + int i1 = 127 + b1 + b2; + int i2 = 127 + b1 - b2; + int i0 = i1 * 256 + i2; + String x = Integer.toString(i0, 36); + + switch (x.length()) + { + case 1: + buf.append('0'); + case 2: + buf.append('0'); + case 3: + buf.append('0'); + default: + buf.append(x); + } + } + return buf.toString(); + + } + + /* ------------------------------------------------------------ */ + public static String deobfuscate(String s) + { + if (s.startsWith(__OBFUSCATE)) s = s.substring(4); + + byte[] b = new byte[s.length() / 2]; + int l = 0; + for (int i = 0; i < s.length(); i += 4) + { + String x = s.substring(i, i + 4); + int i0 = Integer.parseInt(x, 36); + int i1 = (i0 / 256); + int i2 = (i0 % 256); + b[l++] = (byte) ((i1 + i2 - 254) / 2); + } + + return new String(b, 0, l); + } + + /* ------------------------------------------------------------ */ + /** + * Get a password. A password is obtained by trying + *

      + *
    • Calling System.getProperty(realm,dft) + *
    • Prompting for a password + *
    • Using promptDft if nothing was entered. + *
    + * + * @param realm The realm name for the password, used as a SystemProperty + * name. + * @param dft The default password. + * @param promptDft The default to use if prompting for the password. + * @return Password + */ + public static Password getPassword(String realm, String dft, String promptDft) + { + String passwd = System.getProperty(realm, dft); + if (passwd == null || passwd.length() == 0) + { + try + { + System.out.print(realm + ((promptDft != null && promptDft.length() > 0) ? " [dft]" : "") + " : "); + System.out.flush(); + byte[] buf = new byte[512]; + int len = System.in.read(buf); + if (len > 0) passwd = new String(buf, 0, len).trim(); + } + catch (IOException e) + { + Log.warn(Log.EXCEPTION, e); + } + if (passwd == null || passwd.length() == 0) passwd = promptDft; + } + return new Password(passwd); + } + + /* ------------------------------------------------------------ */ + /** + * @param arg + */ + public static void main(String[] arg) + { + if (arg.length != 1 && arg.length != 2) + { + System.err.println("Usage - java org.eclipse.jetty.security.Password [] "); + System.err.println("If the password is ?, the user will be prompted for the password"); + System.exit(1); + } + String p = arg[arg.length == 1 ? 0 : 1]; + Password pw = "?".equals(p) ? new Password(p) : new Password(p); + System.err.println(pw.toString()); + System.err.println(obfuscate(pw.toString())); + System.err.println(Credential.MD5.digest(p)); + if (arg.length == 2) System.err.println(Credential.Crypt.crypt(arg[0], pw.toString())); + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/security/UnixCrypt.java b/jetty-http/src/main/java/org/eclipse/jetty/http/security/UnixCrypt.java new file mode 100644 index 00000000000..8979cc0c059 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/security/UnixCrypt.java @@ -0,0 +1,460 @@ +/* + * @(#)UnixCrypt.java 0.9 96/11/25 + * + * Copyright (c) 1996 Aki Yoshida. All rights reserved. + * + * Permission to use, copy, modify and distribute this software + * for non-commercial or commercial purposes and without fee is + * hereby granted provided that this copyright notice appears in + * all copies. + */ + +/** + * Unix crypt(3C) utility + * + * @version 0.9, 11/25/96 + * @author Aki Yoshida + */ + +/** + * modified April 2001 + * by Iris Van den Broeke, Daniel Deville + */ + +package org.eclipse.jetty.http.security; + +/* ------------------------------------------------------------ */ +/** + * Unix Crypt. Implements the one way cryptography used by Unix systems for + * simple password protection. + * + * @version $Id: UnixCrypt.java,v 1.1 2005/10/05 14:09:14 janb Exp $ + * @author Greg Wilkins (gregw) + */ +public class UnixCrypt extends Object +{ + + /* (mostly) Standard DES Tables from Tom Truscott */ + private static final byte[] IP = { /* initial permutation */ + 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 }; + + /* The final permutation is the inverse of IP - no table is necessary */ + private static final byte[] ExpandTr = { /* expansion operation */ + 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, + 28, 29, 30, 31, 32, 1 }; + + private static final byte[] PC1 = { /* permuted choice table 1 */ + 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, + + 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 }; + + private static final byte[] Rotates = { /* PC1 rotation schedule */ + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; + + private static final byte[] PC2 = { /* permuted choice table 2 */ + 9, 18, 14, 17, 11, 24, 1, 5, 22, 25, 3, 28, 15, 6, 21, 10, 35, 38, 23, 19, 12, 4, 26, 8, 43, 54, 16, 7, 27, 20, 13, 2, + + 0, 0, 41, 52, 31, 37, 47, 55, 0, 0, 30, 40, 51, 45, 33, 48, 0, 0, 44, 49, 39, 56, 34, 53, 0, 0, 46, 42, 50, 36, 29, 32 }; + + private static final byte[][] S = { /* 48->32 bit substitution tables */ + /* S[1] */ + { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, + 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 }, + /* S[2] */ + { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, + 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 }, + /* S[3] */ + { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, + 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 }, + /* S[4] */ + { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, + 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 }, + /* S[5] */ + { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, + 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 }, + /* S[6] */ + { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, + 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 }, + /* S[7] */ + { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, + 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 }, + /* S[8] */ + { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, + 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } }; + + private static final byte[] P32Tr = { /* 32-bit permutation function */ + 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 }; + + private static final byte[] CIFP = { /* + * compressed/interleaved + * permutation + */ + 1, 2, 3, 4, 17, 18, 19, 20, 5, 6, 7, 8, 21, 22, 23, 24, 9, 10, 11, 12, 25, 26, 27, 28, 13, 14, 15, 16, 29, 30, 31, 32, + + 33, 34, 35, 36, 49, 50, 51, 52, 37, 38, 39, 40, 53, 54, 55, 56, 41, 42, 43, 44, 57, 58, 59, 60, 45, 46, 47, 48, 61, 62, 63, 64 }; + + private static final byte[] ITOA64 = { /* 0..63 => ascii-64 */ + (byte) '.', (byte) '/', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', + (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', + (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', + (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', + (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', + (byte) 'x', (byte) 'y', (byte) 'z' }; + + /* ===== Tables that are initialized at run time ==================== */ + + private static byte[] A64TOI = new byte[128]; /* ascii-64 => 0..63 */ + + /* Initial key schedule permutation */ + private static long[][] PC1ROT = new long[16][16]; + + /* Subsequent key schedule rotation permutations */ + private static long[][][] PC2ROT = new long[2][16][16]; + + /* Initial permutation/expansion table */ + private static long[][] IE3264 = new long[8][16]; + + /* Table that combines the S, P, and E operations. */ + private static long[][] SPE = new long[8][64]; + + /* compressed/interleaved => final permutation table */ + private static long[][] CF6464 = new long[16][16]; + + /* ==================================== */ + + static + { + byte[] perm = new byte[64]; + byte[] temp = new byte[64]; + + // inverse table. + for (int i = 0; i < 64; i++) + A64TOI[ITOA64[i]] = (byte) i; + + // PC1ROT - bit reverse, then PC1, then Rotate, then PC2 + for (int i = 0; i < 64; i++) + perm[i] = (byte) 0; + ; + for (int i = 0; i < 64; i++) + { + int k; + if ((k = (int) PC2[i]) == 0) continue; + k += Rotates[0] - 1; + if ((k % 28) < Rotates[0]) k -= 28; + k = (int) PC1[k]; + if (k > 0) + { + k--; + k = (k | 0x07) - (k & 0x07); + k++; + } + perm[i] = (byte) k; + } + init_perm(PC1ROT, perm, 8); + + // PC2ROT - PC2 inverse, then Rotate, then PC2 + for (int j = 0; j < 2; j++) + { + int k; + for (int i = 0; i < 64; i++) + perm[i] = temp[i] = 0; + for (int i = 0; i < 64; i++) + { + if ((k = (int) PC2[i]) == 0) continue; + temp[k - 1] = (byte) (i + 1); + } + for (int i = 0; i < 64; i++) + { + if ((k = (int) PC2[i]) == 0) continue; + k += j; + if ((k % 28) <= j) k -= 28; + perm[i] = temp[k]; + } + + init_perm(PC2ROT[j], perm, 8); + } + + // Bit reverse, intial permupation, expantion + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + int k = (j < 2) ? 0 : IP[ExpandTr[i * 6 + j - 2] - 1]; + if (k > 32) + k -= 32; + else if (k > 0) k--; + if (k > 0) + { + k--; + k = (k | 0x07) - (k & 0x07); + k++; + } + perm[i * 8 + j] = (byte) k; + } + } + + init_perm(IE3264, perm, 8); + + // Compression, final permutation, bit reverse + for (int i = 0; i < 64; i++) + { + int k = IP[CIFP[i] - 1]; + if (k > 0) + { + k--; + k = (k | 0x07) - (k & 0x07); + k++; + } + perm[k - 1] = (byte) (i + 1); + } + + init_perm(CF6464, perm, 8); + + // SPE table + for (int i = 0; i < 48; i++) + perm[i] = P32Tr[ExpandTr[i] - 1]; + for (int t = 0; t < 8; t++) + { + for (int j = 0; j < 64; j++) + { + int k = (((j >> 0) & 0x01) << 5) | (((j >> 1) & 0x01) << 3) + | (((j >> 2) & 0x01) << 2) + | (((j >> 3) & 0x01) << 1) + | (((j >> 4) & 0x01) << 0) + | (((j >> 5) & 0x01) << 4); + k = S[t][k]; + k = (((k >> 3) & 0x01) << 0) | (((k >> 2) & 0x01) << 1) | (((k >> 1) & 0x01) << 2) | (((k >> 0) & 0x01) << 3); + for (int i = 0; i < 32; i++) + temp[i] = 0; + for (int i = 0; i < 4; i++) + temp[4 * t + i] = (byte) ((k >> i) & 0x01); + long kk = 0; + for (int i = 24; --i >= 0;) + kk = ((kk << 1) | ((long) temp[perm[i] - 1]) << 32 | ((long) temp[perm[i + 24] - 1])); + + SPE[t][j] = to_six_bit(kk); + } + } + } + + /** + * You can't call the constructer. + */ + private UnixCrypt() + { + } + + /** + * Returns the transposed and split code of a 24-bit code into a 4-byte + * code, each having 6 bits. + */ + private static int to_six_bit(int num) + { + return (((num << 26) & 0xfc000000) | ((num << 12) & 0xfc0000) | ((num >> 2) & 0xfc00) | ((num >> 16) & 0xfc)); + } + + /** + * Returns the transposed and split code of two 24-bit code into two 4-byte + * code, each having 6 bits. + */ + private static long to_six_bit(long num) + { + return (((num << 26) & 0xfc000000fc000000L) | ((num << 12) & 0xfc000000fc0000L) | ((num >> 2) & 0xfc000000fc00L) | ((num >> 16) & 0xfc000000fcL)); + } + + /** + * Returns the permutation of the given 64-bit code with the specified + * permutataion table. + */ + private static long perm6464(long c, long[][] p) + { + long out = 0L; + for (int i = 8; --i >= 0;) + { + int t = (int) (0x00ff & c); + c >>= 8; + long tp = p[i << 1][t & 0x0f]; + out |= tp; + tp = p[(i << 1) + 1][t >> 4]; + out |= tp; + } + return out; + } + + /** + * Returns the permutation of the given 32-bit code with the specified + * permutataion table. + */ + private static long perm3264(int c, long[][] p) + { + long out = 0L; + for (int i = 4; --i >= 0;) + { + int t = (0x00ff & c); + c >>= 8; + long tp = p[i << 1][t & 0x0f]; + out |= tp; + tp = p[(i << 1) + 1][t >> 4]; + out |= tp; + } + return out; + } + + /** + * Returns the key schedule for the given key. + */ + private static long[] des_setkey(long keyword) + { + long K = perm6464(keyword, PC1ROT); + long[] KS = new long[16]; + KS[0] = K & ~0x0303030300000000L; + + for (int i = 1; i < 16; i++) + { + KS[i] = K; + K = perm6464(K, PC2ROT[Rotates[i] - 1]); + + KS[i] = K & ~0x0303030300000000L; + } + return KS; + } + + /** + * Returns the DES encrypted code of the given word with the specified + * environment. + */ + private static long des_cipher(long in, int salt, int num_iter, long[] KS) + { + salt = to_six_bit(salt); + long L = in; + long R = L; + L &= 0x5555555555555555L; + R = (R & 0xaaaaaaaa00000000L) | ((R >> 1) & 0x0000000055555555L); + L = ((((L << 1) | (L << 32)) & 0xffffffff00000000L) | ((R | (R >> 32)) & 0x00000000ffffffffL)); + + L = perm3264((int) (L >> 32), IE3264); + R = perm3264((int) (L & 0xffffffff), IE3264); + + while (--num_iter >= 0) + { + for (int loop_count = 0; loop_count < 8; loop_count++) + { + long kp; + long B; + long k; + + kp = KS[(loop_count << 1)]; + k = ((R >> 32) ^ R) & salt & 0xffffffffL; + k |= (k << 32); + B = (k ^ R ^ kp); + + L ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)] + ^ SPE[2][(int) ((B >> 42) & 0x3f)] + ^ SPE[3][(int) ((B >> 34) & 0x3f)] + ^ SPE[4][(int) ((B >> 26) & 0x3f)] + ^ SPE[5][(int) ((B >> 18) & 0x3f)] + ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]); + + kp = KS[(loop_count << 1) + 1]; + k = ((L >> 32) ^ L) & salt & 0xffffffffL; + k |= (k << 32); + B = (k ^ L ^ kp); + + R ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)] + ^ SPE[2][(int) ((B >> 42) & 0x3f)] + ^ SPE[3][(int) ((B >> 34) & 0x3f)] + ^ SPE[4][(int) ((B >> 26) & 0x3f)] + ^ SPE[5][(int) ((B >> 18) & 0x3f)] + ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]); + } + // swap L and R + L ^= R; + R ^= L; + L ^= R; + } + L = ((((L >> 35) & 0x0f0f0f0fL) | (((L & 0xffffffff) << 1) & 0xf0f0f0f0L)) << 32 | (((R >> 35) & 0x0f0f0f0fL) | (((R & 0xffffffff) << 1) & 0xf0f0f0f0L))); + + L = perm6464(L, CF6464); + + return L; + } + + /** + * Initializes the given permutation table with the mapping table. + */ + private static void init_perm(long[][] perm, byte[] p, int chars_out) + { + for (int k = 0; k < chars_out * 8; k++) + { + + int l = p[k] - 1; + if (l < 0) continue; + int i = l >> 2; + l = 1 << (l & 0x03); + for (int j = 0; j < 16; j++) + { + int s = ((k & 0x07) + ((7 - (k >> 3)) << 3)); + if ((j & l) != 0x00) perm[i][j] |= (1L << s); + } + } + } + + /** + * Encrypts String into crypt (Unix) code. + * + * @param key the key to be encrypted + * @param setting the salt to be used + * @return the encrypted String + */ + public static String crypt(String key, String setting) + { + long constdatablock = 0L; /* encryption constant */ + byte[] cryptresult = new byte[13]; /* encrypted result */ + long keyword = 0L; + /* invalid parameters! */ + if (key == null || setting == null) return "*"; // will NOT match under + // ANY circumstances! + + int keylen = key.length(); + + for (int i = 0; i < 8; i++) + { + keyword = (keyword << 8) | ((i < keylen) ? 2 * key.charAt(i) : 0); + } + + long[] KS = des_setkey(keyword); + + int salt = 0; + for (int i = 2; --i >= 0;) + { + char c = (i < setting.length()) ? setting.charAt(i) : '.'; + cryptresult[i] = (byte) c; + salt = (salt << 6) | (0x00ff & A64TOI[c]); + } + + long rsltblock = des_cipher(constdatablock, salt, 25, KS); + + cryptresult[12] = ITOA64[(((int) rsltblock) << 2) & 0x3f]; + rsltblock >>= 4; + for (int i = 12; --i >= 2;) + { + cryptresult[i] = ITOA64[((int) rsltblock) & 0x3f]; + rsltblock >>= 6; + } + + return new String(cryptresult, 0x00, 0, 13); + } + + public static void main(String[] arg) + { + if (arg.length != 2) + { + System.err.println("Usage - java org.eclipse.util.UnixCrypt "); + System.exit(1); + } + + System.err.println("Crypt=" + crypt(arg[0], arg[1])); + } + +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslSelectChannelEndPoint.java b/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslSelectChannelEndPoint.java new file mode 100644 index 00000000000..0357f57c323 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslSelectChannelEndPoint.java @@ -0,0 +1,789 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http.ssl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.nio.NIOBuffer; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectorManager; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** + * SslHttpChannelEndPoint. + * + * + * + */ +public class SslSelectChannelEndPoint extends SelectChannelEndPoint +{ + private static final ByteBuffer[] __NO_BUFFERS={}; + + private Buffers _buffers; + + private SSLEngine _engine; + private ByteBuffer _inBuffer; + private NIOBuffer _inNIOBuffer; + private ByteBuffer _outBuffer; + private NIOBuffer _outNIOBuffer; + + private NIOBuffer[] _reuseBuffer=new NIOBuffer[2]; + private ByteBuffer[] _gather=new ByteBuffer[2]; + + private boolean _closing=false; + private SSLEngineResult _result; + private String _last; + + // ssl + protected SSLSession _session; + + // TODO get rid of this + // StringBuilder h = new StringBuilder(500); + /* + class H + { + H append(Object o) + { + System.err.print(o); + return this; + } + }; + H h = new H(); + */ + + /* ------------------------------------------------------------ */ + public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine) + throws SSLException, IOException + { + super(channel,selectSet,key); + _buffers=buffers; + + // ssl + _engine=engine; + _session=engine.getSession(); + + // TODO pool buffers and use only when needed. + _outNIOBuffer=(NIOBuffer)buffers.getBuffer(_session.getPacketBufferSize()); + _outBuffer=_outNIOBuffer.getByteBuffer(); + _inNIOBuffer=(NIOBuffer)buffers.getBuffer(_session.getPacketBufferSize()); + _inBuffer=_inNIOBuffer.getByteBuffer(); + + // h.append("CONSTRUCTED\n"); + } + + // TODO get rid of these dumps + public void dump() + { + System.err.println(_result); + // System.err.println(h.toString()); + // System.err.println("--"); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.io.nio.SelectChannelEndPoint#idleExpired() + */ + protected void idleExpired() + { + try + { + _selectSet.getManager().dispatch(new Runnable() + { + public void run() + { + doIdleExpired(); + } + }); + } + catch(Exception e) + { + Log.ignore(e); + } + } + + /* ------------------------------------------------------------ */ + protected void doIdleExpired() + { + // h.append("IDLE EXPIRED\n"); + super.idleExpired(); + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + // TODO - this really should not be done in a loop here - but with async callbacks. + + // h.append("CLOSE\n"); + _closing=true; + try + { + int tries=0; + + while (_outNIOBuffer.length()>0) + { + // TODO REMOVE loop check + if (tries++>100) + throw new IllegalStateException(); + flush(); + Thread.sleep(100); // TODO yuck + } + + _engine.closeOutbound(); + + loop: while (isOpen() && !(_engine.isInboundDone() && _engine.isOutboundDone())) + { + // TODO REMOVE loop check + if (tries++>100) + throw new IllegalStateException(); + + if (_outNIOBuffer.length()>0) + { + flush(); + Thread.sleep(100); // TODO yuck + } + + switch(_engine.getHandshakeStatus()) + { + case FINISHED: + case NOT_HANDSHAKING: + break loop; + + case NEED_UNWRAP: + Buffer buffer =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize()); + try + { + ByteBuffer bbuffer = ((NIOBuffer)buffer).getByteBuffer(); + if (!unwrap(bbuffer) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) + { + // h.append("break loop\n"); + break loop; + } + } + catch(SSLException e) + { + Log.ignore(e); + } + finally + { + _buffers.returnBuffer(buffer); + } + break; + + case NEED_TASK: + { + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + task.run(); + } + break; + } + + case NEED_WRAP: + { + if (_outNIOBuffer.length()>0) + flush(); + + try + { + _outNIOBuffer.compact(); + int put=_outNIOBuffer.putIndex(); + _outBuffer.position(put); + _result=null; + _last="close wrap"; + _result=_engine.wrap(__NO_BUFFERS,_outBuffer); + _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); + } + finally + { + _outBuffer.position(0); + } + + flush(); + + break; + } + } + } + } + catch(IOException e) + { + Log.ignore(e); + } + catch (InterruptedException e) + { + Log.ignore(e); + } + finally + { + super.close(); + + if (_inNIOBuffer!=null) + _buffers.returnBuffer(_inNIOBuffer); + if (_outNIOBuffer!=null) + _buffers.returnBuffer(_outNIOBuffer); + if (_reuseBuffer[0]!=null) + _buffers.returnBuffer(_reuseBuffer[0]); + if (_reuseBuffer[1]!=null) + _buffers.returnBuffer(_reuseBuffer[1]); + } + } + + /* ------------------------------------------------------------ */ + /* + */ + public int fill(Buffer buffer) throws IOException + { + ByteBuffer bbuf=extractInputBuffer(buffer); + int size=buffer.length(); + HandshakeStatus initialStatus = _engine.getHandshakeStatus(); + synchronized (bbuf) + { + try + { + unwrap(bbuf); + + int tries=0, wraps=0; + loop: while (true) + { + // TODO REMOVE loop check + if (tries++>100) + throw new IllegalStateException(); + + // h.append("Fill(Buffer)\n"); + + if (_outNIOBuffer.length()>0) + flush(); + + // h.append("status=").append(_engine.getHandshakeStatus()).append('\n'); + switch(_engine.getHandshakeStatus()) + { + case FINISHED: + case NOT_HANDSHAKING: + if (_closing) + return -1; + break loop; + + case NEED_UNWRAP: + if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) + { + // h.append("break loop\n"); + break loop; + } + break; + + case NEED_TASK: + { + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + // h.append("run task\n"); + task.run(); + } + if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && + HandshakeStatus.NEED_UNWRAP==_engine.getHandshakeStatus() && wraps==0) + { + // This should be NEED_WRAP + // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS. + // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it. + // See http://jira.codehaus.org/browse/JETTY-567 for more details + return -1; + } + break; + } + + case NEED_WRAP: + { + wraps++; + synchronized(_outBuffer) + { + try + { + _outNIOBuffer.compact(); + int put=_outNIOBuffer.putIndex(); + _outBuffer.position(); + _result=null; + _last="fill wrap"; + _result=_engine.wrap(__NO_BUFFERS,_outBuffer); + switch(_result.getStatus()) + { + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + Log.warn("wrap {}",_result); + case CLOSED: + _closing=true; + } + + // h.append("wrap ").append(_result).append('\n'); + _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); + } + finally + { + _outBuffer.position(0); + } + } + + flush(); + + break; + } + } + } + } + catch(SSLException e) + { + Log.warn(e.toString()); + Log.debug(e); + throw e; + } + finally + { + buffer.setPutIndex(bbuf.position()); + bbuf.position(0); + } + } + return buffer.length()-size; + + } + + /* ------------------------------------------------------------ */ + public int flush(Buffer buffer) throws IOException + { + return flush(buffer,null,null); + } + + + /* ------------------------------------------------------------ */ + /* + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int consumed=0; + int available=header.length(); + if (buffer!=null) + available+=buffer.length(); + + int tries=0; + loop: while (true) + { + // TODO REMOVE loop check + if (tries++>100) + throw new IllegalStateException(); + + // h.append("Flush ").append(tries).append(' ').append(_outNIOBuffer.length()).append('\n'); + + if (_outNIOBuffer.length()>0) + flush(); + + // h.append(_engine.getHandshakeStatus()).append('\n'); + + switch(_engine.getHandshakeStatus()) + { + case FINISHED: + case NOT_HANDSHAKING: + + if (_closing || available==0) + { + if (consumed==0) + consumed= -1; + break loop; + } + + int c=(header!=null && buffer!=null)?wrap(header,buffer):wrap(header); + if (c>0) + { + consumed+=c; + available-=c; + } + else if (c<0) + { + if (consumed==0) + consumed=-1; + break loop; + } + + break; + + case NEED_UNWRAP: + Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize()); + try + { + ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer(); + if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) + { + // h.append("break").append('\n'); + break loop; + } + } + finally + { + _buffers.returnBuffer(buf); + } + + break; + + case NEED_TASK: + { + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + // h.append("run task\n"); + task.run(); + } + break; + } + + case NEED_WRAP: + { + synchronized(_outBuffer) + { + try + { + _outNIOBuffer.compact(); + int put=_outNIOBuffer.putIndex(); + _outBuffer.position(); + _result=null; + _last="flush wrap"; + _result=_engine.wrap(__NO_BUFFERS,_outBuffer); + switch(_result.getStatus()) + { + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + Log.warn("unwrap {}",_result); + case CLOSED: + _closing=true; + } + // h.append("wrap=").append(_result).append('\n'); + _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); + } + finally + { + _outBuffer.position(0); + } + } + + flush(); + + break; + } + } + } + + return consumed; + } + + + /* ------------------------------------------------------------ */ + public void flush() throws IOException + { + while (_outNIOBuffer.length()>0) + { + int flushed=super.flush(_outNIOBuffer); + + // h.append("flushed=").append(flushed).append(" of ").append(_outNIOBuffer.length()).append('\n'); + if (flushed==0) + { + Thread.yield(); + flushed=super.flush(_outNIOBuffer); + // h.append("flushed2=").append(flushed).append(" of ").append(_outNIOBuffer.length()).append('\n'); + } + } + } + + /* ------------------------------------------------------------ */ + private ByteBuffer extractInputBuffer(Buffer buffer) + { + assert buffer instanceof NIOBuffer; + NIOBuffer nbuf=(NIOBuffer)buffer; + ByteBuffer bbuf=nbuf.getByteBuffer(); + bbuf.position(buffer.putIndex()); + return bbuf; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if progress is made + */ + private boolean unwrap(ByteBuffer buffer) throws IOException + { + if (_inNIOBuffer.hasContent()) + _inNIOBuffer.compact(); + else + _inNIOBuffer.clear(); + + int total_filled=0; + while (_inNIOBuffer.space()>0 && super.isOpen()) + { + try + { + int filled=super.fill(_inNIOBuffer); + // h.append("fill=").append(filled).append('\n'); + if (filled<=0) + break; + total_filled+=filled; + } + catch(IOException e) + { + if (_inNIOBuffer.length()==0) + throw e; + break; + } + } + + // h.append("inNIOBuffer=").append(_inNIOBuffer.length()).append('\n'); + + if (_inNIOBuffer.length()==0) + { + if(!isOpen()) + throw new org.eclipse.jetty.io.EofException(); + return false; + } + + try + { + _inBuffer.position(_inNIOBuffer.getIndex()); + _inBuffer.limit(_inNIOBuffer.putIndex()); + _result=null; + _last="unwrap"; + _result=_engine.unwrap(_inBuffer,buffer); + // h.append("unwrap=").append(_result).append('\n'); + _inNIOBuffer.skip(_result.bytesConsumed()); + } + finally + { + _inBuffer.position(0); + _inBuffer.limit(_inBuffer.capacity()); + } + + + switch(_result.getStatus()) + { + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + if (Log.isDebugEnabled()) Log.debug("unwrap {}",_result); + return (total_filled > 0); + + case CLOSED: + _closing=true; + case OK: + boolean progress=total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; + // h.append("progress=").append(progress).append('\n'); + return progress; + default: + Log.warn("unwrap "+_result); + throw new IOException(_result.toString()); + } + } + + + /* ------------------------------------------------------------ */ + private ByteBuffer extractOutputBuffer(Buffer buffer,int n) + { + NIOBuffer nBuf=null; + + if (buffer.buffer() instanceof NIOBuffer) + { + nBuf=(NIOBuffer)buffer.buffer(); + return nBuf.getByteBuffer(); + } + else + { + if (_reuseBuffer[n]==null) + _reuseBuffer[n] = (NIOBuffer)_buffers.getBuffer(_session.getApplicationBufferSize()); + NIOBuffer buf = _reuseBuffer[n]; + buf.clear(); + buf.put(buffer); + return buf.getByteBuffer(); + } + } + + /* ------------------------------------------------------------ */ + private int wrap(Buffer header, Buffer buffer) throws IOException + { + _gather[0]=extractOutputBuffer(header,0); + synchronized(_gather[0]) + { + _gather[0].position(header.getIndex()); + _gather[0].limit(header.putIndex()); + + _gather[1]=extractOutputBuffer(buffer,1); + + synchronized(_gather[1]) + { + _gather[1].position(buffer.getIndex()); + _gather[1].limit(buffer.putIndex()); + + synchronized(_outBuffer) + { + int consumed=0; + try + { + _outNIOBuffer.clear(); + _outBuffer.position(0); + _outBuffer.limit(_outBuffer.capacity()); + + _result=null; + _last="wrap wrap"; + _result=_engine.wrap(_gather,_outBuffer); + // h.append("wrap2=").append(_result).append('\n'); + _outNIOBuffer.setGetIndex(0); + _outNIOBuffer.setPutIndex(_result.bytesProduced()); + consumed=_result.bytesConsumed(); + } + finally + { + _outBuffer.position(0); + + if (consumed>0 && header!=null) + { + int len=consumed0 && buffer!=null) + { + int len=consumed0?_result.bytesConsumed():-1; + + default: + Log.warn("wrap "+_result); + throw new IOException(_result.toString()); + } + } + + /* ------------------------------------------------------------ */ + private int wrap(Buffer header) throws IOException + { + _gather[0]=extractOutputBuffer(header,0); + synchronized(_gather[0]) + { + _gather[0].position(header.getIndex()); + _gather[0].limit(header.putIndex()); + + int consumed=0; + synchronized(_outBuffer) + { + try + { + _outNIOBuffer.clear(); + _outBuffer.position(0); + _outBuffer.limit(_outBuffer.capacity()); + _result=null; + _last="wrap wrap"; + _result=_engine.wrap(_gather[0],_outBuffer); + // h.append("wrap1=").append(_result).append('\n'); + _outNIOBuffer.setGetIndex(0); + _outNIOBuffer.setPutIndex(_result.bytesProduced()); + consumed=_result.bytesConsumed(); + } + finally + { + _outBuffer.position(0); + + if (consumed>0 && header!=null) + { + int len=consumed0?_result.bytesConsumed():-1; + + default: + Log.warn("wrap "+_result); + throw new IOException(_result.toString()); + } + } + + /* ------------------------------------------------------------ */ + public boolean isBufferingInput() + { + return _inNIOBuffer.hasContent(); + } + + /* ------------------------------------------------------------ */ + public boolean isBufferingOutput() + { + return _outNIOBuffer.hasContent(); + } + + /* ------------------------------------------------------------ */ + public boolean isBufferred() + { + return true; + } + + /* ------------------------------------------------------------ */ + public SSLEngine getSSLEngine() + { + return _engine; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+_inNIOBuffer.length()+"/"+_outNIOBuffer.length()+" last "+_last+" "+_result; + } +} diff --git a/jetty-http/src/main/resources/org/eclipse/jetty/http/encoding.properties b/jetty-http/src/main/resources/org/eclipse/jetty/http/encoding.properties new file mode 100644 index 00000000000..c3e5b7ecc82 --- /dev/null +++ b/jetty-http/src/main/resources/org/eclipse/jetty/http/encoding.properties @@ -0,0 +1,3 @@ +text/html = ISO-8859-1 +text/plain = US-ASCII +text/xml = UTF-8 diff --git a/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties b/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties new file mode 100644 index 00000000000..7d2acb1d038 --- /dev/null +++ b/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties @@ -0,0 +1,181 @@ +ai = application/postscript +aif = audio/x-aiff +aifc = audio/x-aiff +aiff = audio/x-aiff +apk = application/vnd.android.package-archive +asc = text/plain +asf = video/x.ms.asf +asx = video/x.ms.asx +au = audio/basic +avi = video/x-msvideo +bcpio = application/x-bcpio +bin = application/octet-stream +cab = application/x-cabinet +cdf = application/x-netcdf +class = application/java-vm +cpio = application/x-cpio +cpt = application/mac-compactpro +crt = application/x-x509-ca-cert +csh = application/x-csh +css = text/css +csv = text/comma-separated-values +dcr = application/x-director +dir = application/x-director +dll = application/x-msdownload +dms = application/octet-stream +doc = application/msword +dtd = application/xml-dtd +dvi = application/x-dvi +dxr = application/x-director +eps = application/postscript +etx = text/x-setext +exe = application/octet-stream +ez = application/andrew-inset +gif = image/gif +gtar = application/x-gtar +gz = application/gzip +gzip = application/gzip +hdf = application/x-hdf +hqx = application/mac-binhex40 +htc = text/x-component +html = text/html +htm = text/html +ice = x-conference/x-cooltalk +ico = image/x-icon +ief = image/ief +iges = model/iges +igs = model/iges +jar = application/java-archive +java = text/plain +jnlp = application/x-java-jnlp-file +jpeg = image/jpeg +jpe = image/jpeg +jpg = image/jpeg +js = application/x-javascript +jsp = text/plain +kar = audio/midi +latex = application/x-latex +lha = application/octet-stream +lzh = application/octet-stream +man = application/x-troff-man +mathml = application/mathml+xml +me = application/x-troff-me +mesh = model/mesh +mid = audio/midi +midi = audio/midi +mif = application/vnd.mif +mol = chemical/x-mdl-molfile +movie = video/x-sgi-movie +mov = video/quicktime +mp2 = audio/mpeg +mp3 = audio/mpeg +mpeg = video/mpeg +mpe = video/mpeg +mpga = audio/mpeg +mpg = video/mpeg +ms = application/x-troff-ms +msh = model/mesh +msi = application/octet-stream +nc = application/x-netcdf +oda = application/oda +odb = application/vnd.oasis.opendocument.database +odc = application/vnd.oasis.opendocument.chart +odf = application/vnd.oasis.opendocument.formula +odg = application/vnd.oasis.opendocument.graphics +odi = application/vnd.oasis.opendocument.image +odm = application/vnd.oasis.opendocument.text-master +odp = application/vnd.oasis.opendocument.presentation +ods = application/vnd.oasis.opendocument.spreadsheet +odt = application/vnd.oasis.opendocument.text +ogg = application/ogg +otc = application/vnd.oasis.opendocument.chart-template +otf = application/vnd.oasis.opendocument.formula-template +otg = application/vnd.oasis.opendocument.graphics-template +oth = application/vnd.oasis.opendocument.text-web +oti = application/vnd.oasis.opendocument.image-template +otp = application/vnd.oasis.opendocument.presentation-template +ots = application/vnd.oasis.opendocument.spreadsheet-template +ott = application/vnd.oasis.opendocument.text-template +pbm = image/x-portable-bitmap +pdb = chemical/x-pdb +pdf = application/pdf +pgm = image/x-portable-graymap +pgn = application/x-chess-pgn +png = image/png +pnm = image/x-portable-anymap +ppm = image/x-portable-pixmap +pps = application/vnd.ms-powerpoint +ppt = application/vnd.ms-powerpoint +ps = application/postscript +qt = video/quicktime +ra = audio/x-pn-realaudio +ra = audio/x-realaudio +ram = audio/x-pn-realaudio +ras = image/x-cmu-raster +rdf = application/rdf+xml +rgb = image/x-rgb +rm = audio/x-pn-realaudio +roff = application/x-troff +rpm = application/x-rpm +rpm = audio/x-pn-realaudio +rtf = application/rtf +rtx = text/richtext +ser = application/java-serialized-object +sgml = text/sgml +sgm = text/sgml +sh = application/x-sh +shar = application/x-shar +silo = model/mesh +sit = application/x-stuffit +skd = application/x-koan +skm = application/x-koan +skp = application/x-koan +skt = application/x-koan +smi = application/smil +smil = application/smil +snd = audio/basic +spl = application/x-futuresplash +src = application/x-wais-source +sv4cpio = application/x-sv4cpio +sv4crc = application/x-sv4crc +svg = image/svg+xml +swf = application/x-shockwave-flash +t = application/x-troff +tar = application/x-tar +tar.gz = application/x-gtar +tcl = application/x-tcl +tex = application/x-tex +texi = application/x-texinfo +texinfo = application/x-texinfo +tgz = application/x-gtar +tiff = image/tiff +tif = image/tiff +tr = application/x-troff +tsv = text/tab-separated-values +txt = text/plain +ustar = application/x-ustar +vcd = application/x-cdlink +vrml = model/vrml +vxml = application/voicexml+xml +wav = audio/x-wav +wbmp = image/vnd.wap.wbmp +wmlc = application/vnd.wap.wmlc +wmlsc = application/vnd.wap.wmlscriptc +wmls = text/vnd.wap.wmlscript +wml = text/vnd.wap.wml +wrl = model/vrml +wtls-ca-certificate = application/vnd.wap.wtls-ca-certificate +xbm = image/x-xbitmap +xht = application/xhtml+xml +xhtml = application/xhtml+xml +xls = application/vnd.ms-excel +xml = application/xml +xpm = image/x-xpixmap +xpm = image/x-xpixmap +xsl = application/xml +xslt = application/xslt+xml +xul = application/vnd.mozilla.xul+xml +xwd = image/x-xwindowdump +xyz = chemical/x-xyz +z = application/compress +zip = application/zip diff --git a/jetty-http/src/main/resources/org/eclipse/jetty/http/useragents b/jetty-http/src/main/resources/org/eclipse/jetty/http/useragents new file mode 100644 index 00000000000..527fac3b182 --- /dev/null +++ b/jetty-http/src/main/resources/org/eclipse/jetty/http/useragents @@ -0,0 +1,57 @@ +Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5 +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506) +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6 +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) +Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6 +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; GTB5; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1) +Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5 +Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5 +Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322) +Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6 +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506) +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5 +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727) +Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5 +Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.20) Gecko/20081217 Firefox/2.0.0.20 +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; InfoPath.2) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506; Media Center PC 5.0) +msnbot/1.1 (+http://search.msn.com/msnbot.htm) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; FunWebProducts; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322) +Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; Media Center PC 4.0; .NET CLR 2.0.50727) +FeedBurner/1.0 (http://www.FeedBurner.com) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; InfoPath.1) +Mozilla/5.0 (Twiceler-0.9 http://www.cuil.com/twiceler/robot.html) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506; InfoPath.2) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; InfoPath.1) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; Media Center PC 4.0) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; InfoPath.1) +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1 +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.2) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1) +Jakarta Commons-HttpClient/2.0.2 +Java/1.6.0 + diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java new file mode 100644 index 00000000000..8ee81d80482 --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java @@ -0,0 +1,355 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.IOException; + +import junit.framework.TestCase; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.ByteArrayEndPoint; +import org.eclipse.jetty.io.SimpleBuffers; +import org.eclipse.jetty.io.View; + +public class HttpGeneratorClientTest extends TestCase +{ + public final static String CONTENT="The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n"; + public final static String[] connect={null,"keep-alive","close"}; + + public HttpGeneratorClientTest(String arg0) + { + super(arg0); + } + + public void testContentLength() + throws Exception + { + Buffer bb=new ByteArrayBuffer(8096); + Buffer sb=new ByteArrayBuffer(1500); + ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); + HttpGenerator generator = new HttpGenerator(new SimpleBuffers(new Buffer[]{sb,bb}),endp, sb.capacity(), bb.capacity()); + + generator.setRequest("GET","/usr"); + + HttpFields fields = new HttpFields(); + fields.add("Header","Value"); + fields.add("Content-Type","text/plain"); + + String content = "The quick brown fox jumped over the lazy dog"; + fields.addLongField("Content-Length",content.length()); + + generator.completeHeader(fields,false); + + generator.addContent(new ByteArrayBuffer(content),true); + generator.flushBuffer(); + generator.complete(); + generator.flushBuffer(); + + String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); + assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Content-Length: 44||"+content,result); + } + + public void testAutoContentLength() + throws Exception + { + Buffer bb=new ByteArrayBuffer(8096); + Buffer sb=new ByteArrayBuffer(1500); + ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); + HttpGenerator generator = new HttpGenerator(new SimpleBuffers(new Buffer[]{sb,bb}),endp, sb.capacity(), bb.capacity()); + + generator.setRequest("GET","/usr"); + + HttpFields fields = new HttpFields(); + fields.add("Header","Value"); + fields.add("Content-Type","text/plain"); + + String content = "The quick brown fox jumped over the lazy dog"; + + generator.addContent(new ByteArrayBuffer(content),true); + generator.completeHeader(fields,true); + + generator.flushBuffer(); + generator.complete(); + generator.flushBuffer(); + + String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); + assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Content-Length: 44||"+content,result); + } + + public void testChunked() + throws Exception + { + Buffer bb=new ByteArrayBuffer(8096); + Buffer sb=new ByteArrayBuffer(1500); + ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); + HttpGenerator generator = new HttpGenerator(new SimpleBuffers(new Buffer[]{sb,bb}),endp, sb.capacity(), bb.capacity()); + + generator.setRequest("GET","/usr"); + + HttpFields fields = new HttpFields(); + fields.add("Header","Value"); + fields.add("Content-Type","text/plain"); + + String content = "The quick brown fox jumped over the lazy dog"; + + generator.completeHeader(fields,false); + + generator.addContent(new ByteArrayBuffer(content),false); + generator.flushBuffer(); + generator.complete(); + generator.flushBuffer(); + + String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); + assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Transfer-Encoding: chunked||2C|"+content+"|0||",result); + } + + public void testHTTP() + throws Exception + { + Buffer bb=new ByteArrayBuffer(8096); + Buffer sb=new ByteArrayBuffer(1500); + HttpFields fields = new HttpFields(); + ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); + HttpGenerator hb = new HttpGenerator(new SimpleBuffers(new Buffer[]{sb,bb}),endp, sb.capacity(), bb.capacity()); + Handler handler = new Handler(); + HttpParser parser=null; + + // For HTTP version + for (int v=9;v<=11;v++) + { + // For each test result + for (int r=0;r2) + continue; + System.err.println(t); + throw e; + } + String request=endp.getOut().toString(); + // System.out.println(request+(hb.isPersistent()?"...\n":"---\n")); + + assertTrue(t,hb.isPersistent()); + + if (v==9) + { + assertEquals(t,"GET /context/path/info\r\n", request); + continue; + } + + parser=new HttpParser(new ByteArrayBuffer(request.getBytes()), handler); + try + { + parser.parse(); + } + catch(IOException e) + { + if (tr[r].body!=null) + throw e; + continue; + } + + if (tr[r].body!=null) + assertEquals(t,tr[r].body, this.content); + if (v==10) + assertTrue(t,hb.isPersistent() || tr[r].values[1]==null || c==2 || c==0); + else + assertTrue(t,hb.isPersistent() || c==2); + + assertTrue(t,tr[r].values[1]==null || content.length()==Integer.parseInt(tr[r].values[1])); + } + } + } + } + } + + + + static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"}; + class TR + { + String[] values=new String[headers.length]; + String body; + + TR(String ct, String cl ,String content) + { + values[0]=ct; + values[1]=cl; + values[4]="value"; + this.body=content; + } + + void build(int version,HttpGenerator hb, String connection, String te, int chunks, HttpFields fields) + throws Exception + { + values[2]=connection; + values[3]=te; + + hb.setRequest(HttpMethods.GET,"/context/path/info"); + hb.setVersion(version); + + for (int i=0;i9) + assertEquals("OK Test",f2); + + assertTrue(t,tr[r].values[1]==null || content.length()==Integer.parseInt(tr[r].values[1])); + } + } + } + } + } + + + + static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"}; + class TR + { + int code; + String[] values=new String[headers.length]; + String body; + + TR(int code,String ct, String cl ,String content) + { + this.code=code; + values[0]=ct; + values[1]=cl; + values[4]="value"; + this.body=content; + } + + void build(int version,HttpGenerator hb,String reason, String connection, String te, int chunks, HttpFields fields) + throws Exception + { + values[2]=connection; + values[3]=te; + hb.setVersion(version); + hb.setResponse(code,reason); + + for (int i=0;i=0); + assertTrue(((CachedBuffer)HttpHeaderValues.CACHE.lookup("Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)")).getOrdinal()>=0); + } + + + private Set enum2set(Enumeration e) + { + HashSet s=new HashSet(); + while(e.hasMoreElements()) + s.add(e.nextElement().toString().toLowerCase()); + return s; + } + + public void testDateFields() + throws Exception + { + HttpFields fields = new HttpFields(); + + fields.put("D1", "Fri, 31 Dec 1999 23:59:59 GMT"); + fields.put("D2", "Friday, 31-Dec-99 23:59:59 GMT"); + fields.put("D3", "Fri Dec 31 23:59:59 1999"); + fields.put("D4", "Mon Jan 1 2000 00:00:01"); + fields.put("D5", "Tue Feb 29 2000 12:00:00"); + + long d1 = fields.getDateField("D1"); + long d2 = fields.getDateField("D2"); + long d3 = fields.getDateField("D3"); + long d4 = fields.getDateField("D4"); + long d5 = fields.getDateField("D5"); + assertTrue(d1>0); + assertTrue(d2>0); + assertEquals(d1,d2); + assertEquals(d2,d3); + assertEquals(d3+2000,d4); + assertEquals(951825600000L,d5); + + d1 = fields.getDateField("D1"); + d2 = fields.getDateField("D2"); + d3 = fields.getDateField("D3"); + d4 = fields.getDateField("D4"); + d5 = fields.getDateField("D5"); + assertTrue(d1>0); + assertTrue(d2>0); + assertEquals(d1,d2); + assertEquals(d2,d3); + assertEquals(d3+2000,d4); + assertEquals(951825600000L,d5); + + fields.putDateField("D2",d1); + assertEquals("Fri, 31 Dec 1999 23:59:59 GMT",fields.getStringField("D2")); + } + + public void testLongFields() + throws Exception + { + HttpFields header = new HttpFields(); + + header.put("I1", "42"); + header.put("I2", " 43 99"); + header.put("I3", "-44;"); + header.put("I4", " - 45abc"); + header.put("N1", " - "); + header.put("N2", "xx"); + + long i1=header.getLongField("I1"); + long i2=header.getLongField("I2"); + long i3=header.getLongField("I3"); + long i4=header.getLongField("I4"); + + try{ + header.getLongField("N1"); + assertTrue(false); + } + catch(NumberFormatException e) + { + assertTrue(true); + } + + try{ + header.getLongField("N2"); + assertTrue(false); + } + catch(NumberFormatException e) + { + assertTrue(true); + } + + assertEquals(42,i1); + assertEquals(43,i2); + assertEquals(-44,i3); + assertEquals(-45,i4); + + header.putLongField("I5", 46); + header.putLongField("I6",-47); + assertEquals("46",header.getStringField("I5")); + assertEquals("-47",header.getStringField("I6")); + + } + + public void testToString() + throws Exception + { + HttpFields header = new HttpFields(); + + header.put(new ByteArrayBuffer("name0"), new View(new ByteArrayBuffer("value0"))); + header.put(new ByteArrayBuffer("name1"), new View(new ByteArrayBuffer("value1".getBytes()))); + String s1=header.toString(); + String s2=header.toString(); + //System.err.println(s1); + //System.err.println(s2); + assertEquals(s1,s2); + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java new file mode 100644 index 00000000000..28a6c1a5ab7 --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -0,0 +1,560 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import java.io.UnsupportedEncodingException; + +import junit.framework.TestCase; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.SimpleBuffers; +import org.eclipse.jetty.io.bio.StringEndPoint; +import org.eclipse.jetty.util.StringUtil; + +/** + * + * + * To change this generated comment edit the template variable "typecomment": + * Window>Preferences>Java>Templates. + * To enable and disable the creation of type comments go to + * Window>Preferences>Java>Code Generation. + */ +public class HttpParserTest extends TestCase +{ + /** + * Constructor for HttpParserTest. + * @param arg0 + */ + public HttpParserTest(String arg0) + { + super(arg0); + } + + public static void main(String[] args) + { + junit.textui.TestRunner.run(HttpParserTest.class); + } + + /** + * @see TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + } + + /** + * @see TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + } + + public void testLineParse0() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput("POST /foo HTTP/1.0\015\012" + "\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("POST", f0); + assertEquals("/foo", f1); + assertEquals("HTTP/1.0", f2); + assertEquals(-1, h); + } + + public void testLineParse1() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput("GET /999\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + f2= null; + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("GET", f0); + assertEquals("/999", f1); + assertEquals(null, f2); + assertEquals(-1, h); + } + + public void testLineParse2() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput("POST /222 \015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + f2= null; + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("POST", f0); + assertEquals("/222", f1); + assertEquals(null, f2); + assertEquals(-1, h); + } + + public void testLineParse3() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput("POST /fo\u0690 HTTP/1.0\015\012" + "\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("POST", f0); + assertEquals("/fo\u0690", f1); + assertEquals("HTTP/1.0", f2); + assertEquals(-1, h); + } + + public void testLineParse4() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput("POST /foo?param=\u0690 HTTP/1.0\015\012" + "\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("POST", f0); + assertEquals("/foo?param=\u0690", f1); + assertEquals("HTTP/1.0", f2); + assertEquals(-1, h); + } + + public void testConnect() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput("CONNECT 192.168.1.2:80 HTTP/1.1\015\012" + "\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertTrue(handler.request); + assertEquals("CONNECT", f0); + assertEquals("192.168.1.2:80", f1); + assertEquals("HTTP/1.1", f2); + assertEquals(-1, h); + } + + public void testHeaderParse() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput( + "GET / HTTP/1.0\015\012" + + "Host: localhost\015\012" + + "Header1: value1\015\012" + + "Header2 : value 2a \015\012" + + " value 2b \015\012" + + "Header3: \015\012" + + "Header4 \015\012" + + " value4\015\012" + + "Server5: notServer\015\012" + + "\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("GET", f0); + assertEquals("/", f1); + assertEquals("HTTP/1.0", f2); + assertEquals("Host", hdr[0]); + assertEquals("localhost", val[0]); + assertEquals("Header1", hdr[1]); + assertEquals("value1", val[1]); + assertEquals("Header2", hdr[2]); + assertEquals("value 2a value 2b", val[2]); + assertEquals("Header3", hdr[3]); + assertEquals("", val[3]); + assertEquals("Header4", hdr[4]); + assertEquals("value4", val[4]); + assertEquals("Server5", hdr[5]); + assertEquals("notServer", val[5]); + assertEquals(5, h); + } + + public void testChunkParse() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput( + "GET /chunk HTTP/1.0\015\012" + + "Header1: value1\015\012" + + "Transfer-Encoding: chunked\015\012" + + "\015\012" + + "a;\015\012" + + "0123456789\015\012" + + "1a\015\012" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012" + + "0\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + ByteArrayBuffer content=new ByteArrayBuffer(8192); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer,content}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), content.capacity()); + parser.parse(); + assertEquals("GET", f0); + assertEquals("/chunk", f1); + assertEquals("HTTP/1.0", f2); + assertEquals(1, h); + assertEquals("Header1", hdr[0]); + assertEquals("value1", val[0]); + assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content); + } + + public void testMultiParse() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput( + "GET /mp HTTP/1.0\015\012" + + "Header1: value1\015\012" + + "Transfer-Encoding: chunked\015\012" + + "\015\012" + + "a;\015\012" + + "0123456789\015\012" + + "1a\015\012" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012" + + "0\015\012" + + "POST /foo HTTP/1.0\015\012" + + "Header2: value2\015\012" + + "Content-Length: 0\015\012" + + "\015\012" + + "PUT /doodle HTTP/1.0\015\012" + + "Header3: value3\015\012" + + "Content-Length: 10\015\012" + + "\015\012" + + "0123456789\015\012"); + + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + ByteArrayBuffer content=new ByteArrayBuffer(8192); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer,content}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), content.capacity()); + parser.parse(); + assertEquals("GET", f0); + assertEquals("/mp", f1); + assertEquals("HTTP/1.0", f2); + assertEquals(1, h); + assertEquals("Header1", hdr[0]); + assertEquals("value1", val[0]); + assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content); + + parser.parse(); + assertEquals("POST", f0); + assertEquals("/foo", f1); + assertEquals("HTTP/1.0", f2); + assertEquals(1, h); + assertEquals("Header2", hdr[0]); + assertEquals("value2", val[0]); + assertEquals(null, _content); + + parser.parse(); + assertEquals("PUT", f0); + assertEquals("/doodle", f1); + assertEquals("HTTP/1.0", f2); + assertEquals(1, h); + assertEquals("Header3", hdr[0]); + assertEquals("value3", val[0]); + assertEquals("0123456789", _content); + + } + + public void testStreamParse() throws Exception + { + StringEndPoint io=new StringEndPoint(); + String http="GET / HTTP/1.0\015\012" + + "Header1: value1\015\012" + + "Transfer-Encoding: chunked\015\012" + + "\015\012" + + "a;\015\012" + + "0123456789\015\012" + + "1a\015\012" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012" + + "0\015\012" + + "POST /foo HTTP/1.0\015\012" + + "Header2: value2\015\012" + + "Content-Length: 0\015\012" + + "\015\012" + + "PUT /doodle HTTP/1.0\015\012" + + "Header3: value3\015\012" + + "Content-Length: 10\015\012" + + "\015\012" + + "0123456789\015\012"; + + + int[] tests= + { + 1024, + http.length() + 3, + http.length() + 2, + http.length() + 1, + http.length() + 0, + http.length() - 1, + http.length() - 2, + http.length() / 2, + http.length() / 3, + 64, + 32 + }; + + for (int t= 0; t < tests.length; t++) + { + String tst="t"+tests[t]; + try + { + ByteArrayBuffer buffer= new ByteArrayBuffer(tests[t]); + ByteArrayBuffer content=new ByteArrayBuffer(8192); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer,content}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), content.capacity()); + + + io.setInput(http); + + parser.parse(); + assertEquals(tst,"GET", f0); + assertEquals(tst,"/", f1); + assertEquals(tst,"HTTP/1.0", f2); + assertEquals(tst,1, h); + assertEquals(tst,"Header1", hdr[0]); + assertEquals(tst,"value1", val[0]); + assertEquals(tst,"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content); + + parser.parse(); + assertEquals(tst,"POST", f0); + assertEquals(tst,"/foo", f1); + assertEquals(tst,"HTTP/1.0", f2); + assertEquals(tst,1, h); + assertEquals(tst,"Header2", hdr[0]); + assertEquals(tst,"value2", val[0]); + assertEquals(tst,null, _content); + + parser.parse(); + assertEquals(tst,"PUT", f0); + assertEquals(tst,"/doodle", f1); + assertEquals(tst,"HTTP/1.0", f2); + assertEquals(tst,1, h); + assertEquals(tst,"Header3", hdr[0]); + assertEquals(tst,"value3", val[0]); + assertEquals(tst,"0123456789", _content); + } + catch(Exception e) + { + if (t+1 < tests.length) + throw e; + assertTrue(e.toString().indexOf("FULL")>=0); + } + } + } + + public void testResponseParse0() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput( + "HTTP/1.1 200 Correct\015\012" + + "Content-Length: 10\015\012" + + "Content-Type: text/plain\015\012" + + "\015\012" + + "0123456789\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("HTTP/1.1", f0); + assertEquals("200", f1); + assertEquals("Correct", f2); + assertEquals(_content.length(), 10); + assertTrue(headerCompleted); + assertTrue(messageCompleted); + } + + public void testResponseParse1() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput( + "HTTP/1.1 304 Not-Modified\015\012" + + "Connection: close\015\012" + + "\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("HTTP/1.1", f0); + assertEquals("304", f1); + assertEquals("Not-Modified", f2); + assertTrue(headerCompleted); + assertTrue(messageCompleted); + } + + public void testResponseParse2() + throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput( + "HTTP/1.1 204 No-Content\015\012" + + "Connection: close\015\012" + + "\015\012" + + "HTTP/1.1 200 Correct\015\012" + + "Content-Length: 10\015\012" + + "Content-Type: text/plain\015\012" + + "\015\012" + + "0123456789\015\012"); + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(new Buffer[]{buffer}); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler, buffer.capacity(), 0); + parser.parse(); + assertEquals("HTTP/1.1", f0); + assertEquals("204", f1); + assertEquals("No-Content", f2); + assertTrue(headerCompleted); + assertTrue(messageCompleted); + + parser.parse(); + assertEquals("HTTP/1.1", f0); + assertEquals("200", f1); + assertEquals("Correct", f2); + assertEquals(_content.length(), 10); + assertTrue(headerCompleted); + assertTrue(messageCompleted); + } + + String _content; + String f0; + String f1; + String f2; + String[] hdr; + String[] val; + int h; + + boolean headerCompleted; + boolean messageCompleted; + + class Handler extends HttpParser.EventHandler + { + HttpFields fields; + boolean request; + + public void content(Buffer ref) + { + if (_content==null) + _content=""; + _content= _content + ref; + } + + + public void startRequest(Buffer tok0, Buffer tok1, Buffer tok2) + { + try + { + request=true; + h= -1; + hdr= new String[9]; + val= new String[9]; + f0= tok0.toString(); + f1=new String(tok1.array(),tok1.getIndex(),tok1.length(),StringUtil.__UTF8); + if (tok2!=null) + f2= tok2.toString(); + else + f2=null; + + fields=new HttpFields(); + } + catch (UnsupportedEncodingException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + messageCompleted = false; + headerCompleted = false; + } + + public void parsedHeader(Buffer name, Buffer value) + { + hdr[++h]= name.toString(StringUtil.__ISO_8859_1); + val[h]= value.toString(StringUtil.__ISO_8859_1); + } + + public void headerComplete() + { + _content= null; + String s0=fields.toString(); + String s1=fields.toString(); + if (!s0.equals(s1)) + { + //System.err.println(s0); + //System.err.println(s1); + throw new IllegalStateException(); + } + + headerCompleted = true; + } + + public void messageComplete(long contentLength) + { + messageCompleted = true; + } + + + public void startResponse(Buffer version, int status, Buffer reason) + { + request=false; + f0 = version.toString(); + f1 = Integer.toString(status); + f2 = reason.toString(); + + fields=new HttpFields(); + hdr= new String[9]; + val= new String[9]; + + messageCompleted = false; + headerCompleted = false; + } + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpStatusCodeTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpStatusCodeTest.java new file mode 100644 index 00000000000..8408ffabded --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpStatusCodeTest.java @@ -0,0 +1,26 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.http; + +import junit.framework.TestCase; + + +public class HttpStatusCodeTest extends TestCase +{ + public void testInvalidGetCode() + { + assertNull("Invalid code: 800", HttpStatus.getCode(800)); + assertNull("Invalid code: 190", HttpStatus.getCode(190)); + } + +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java new file mode 100644 index 00000000000..a9b2320f016 --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java @@ -0,0 +1,187 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.http; + +import junit.framework.TestCase; + + +/* ------------------------------------------------------------ */ +/** + * Top level test harness. + * + * + */ +public class PathMapTest extends TestCase +{ + /** + * Constructor for HttpParserTest. + * + * @param arg0 + */ + public PathMapTest(String arg0) + { + super(arg0); + } + + /** + * @see TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + } + + /** + * @see TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + } + + /* --------------------------------------------------------------- */ + public void testPathMap() throws Exception + { + PathMap p = new PathMap(); + + p.put("/abs/path", "1"); + p.put("/abs/path/longer", "2"); + p.put("/animal/bird/*", "3"); + p.put("/animal/fish/*", "4"); + p.put("/animal/*", "5"); + p.put("*.tar.gz", "6"); + p.put("*.gz", "7"); + p.put("/", "8"); + p.put("/XXX:/YYY", "9"); + + String[][] tests = { + { "/abs/path", "1"}, + { "/abs/path/xxx", "8"}, + { "/abs/pith", "8"}, + { "/abs/path/longer", "2"}, + { "/abs/path/", "8"}, + { "/abs/path/xxx", "8"}, + { "/animal/bird/eagle/bald", "3"}, + { "/animal/fish/shark/grey", "4"}, + { "/animal/insect/bug", "5"}, + { "/animal", "5"}, + { "/animal/", "5"}, + { "/suffix/path.tar.gz", "6"}, + { "/suffix/path.gz", "7"}, + { "/animal/path.gz", "5"}, + { "/Other/path", "8"},}; + + for (int i = 0; i < tests.length; i++) + { + assertEquals(tests[i][0], tests[i][1], p.getMatch(tests[i][0]).getValue()); + } + + assertEquals("Get absolute path", "1", p.get("/abs/path")); + assertEquals("Match absolute path", "/abs/path", p.getMatch("/abs/path").getKey()); + assertEquals("all matches", "[/animal/bird/*=3, /animal/*=5, *.tar.gz=6, *.gz=7, /=8]", + p.getMatches("/animal/bird/path.tar.gz").toString()); + assertEquals("Dir matches", "[/animal/fish/*=4, /animal/*=5, /=8]", p.getMatches("/animal/fish/").toString()); + assertEquals("Dir matches", "[/animal/fish/*=4, /animal/*=5, /=8]", p.getMatches("/animal/fish").toString()); + assertEquals("Dir matches", "[/=8]", p.getMatches("/").toString()); + assertEquals("Dir matches", "[/=8]", p.getMatches("").toString()); + + assertEquals("pathMatch exact", "/Foo/bar", PathMap.pathMatch("/Foo/bar", "/Foo/bar")); + assertEquals("pathMatch prefix", "/Foo", PathMap.pathMatch("/Foo/*", "/Foo/bar")); + assertEquals("pathMatch prefix", "/Foo", PathMap.pathMatch("/Foo/*", "/Foo/")); + assertEquals("pathMatch prefix", "/Foo", PathMap.pathMatch("/Foo/*", "/Foo")); + assertEquals("pathMatch suffix", "/Foo/bar.ext", PathMap.pathMatch("*.ext", "/Foo/bar.ext")); + assertEquals("pathMatch default", "/Foo/bar.ext", PathMap.pathMatch("/", "/Foo/bar.ext")); + + assertEquals("pathInfo exact", null, PathMap.pathInfo("/Foo/bar", "/Foo/bar")); + assertEquals("pathInfo prefix", "/bar", PathMap.pathInfo("/Foo/*", "/Foo/bar")); + assertEquals("pathInfo prefix", "/", PathMap.pathInfo("/Foo/*", "/Foo/")); + assertEquals("pathInfo prefix", null, PathMap.pathInfo("/Foo/*", "/Foo")); + assertEquals("pathInfo suffix", null, PathMap.pathInfo("*.ext", "/Foo/bar.ext")); + assertEquals("pathInfo default", null, PathMap.pathInfo("/", "/Foo/bar.ext")); + assertEquals("multi paths", "9", p.getMatch("/XXX").getValue()); + assertEquals("multi paths", "9", p.getMatch("/YYY").getValue()); + + p.put("/*", "0"); + + assertEquals("Get absolute path", "1", p.get("/abs/path")); + assertEquals("Match absolute path", "/abs/path", p.getMatch("/abs/path").getKey()); + assertEquals("Match absolute path", "1", p.getMatch("/abs/path").getValue()); + assertEquals("Mismatch absolute path", "0", p.getMatch("/abs/path/xxx").getValue()); + assertEquals("Mismatch absolute path", "0", p.getMatch("/abs/pith").getValue()); + assertEquals("Match longer absolute path", "2", p.getMatch("/abs/path/longer").getValue()); + assertEquals("Not exact absolute path", "0", p.getMatch("/abs/path/").getValue()); + assertEquals("Not exact absolute path", "0", p.getMatch("/abs/path/xxx").getValue()); + + assertEquals("Match longest prefix", "3", p.getMatch("/animal/bird/eagle/bald").getValue()); + assertEquals("Match longest prefix", "4", p.getMatch("/animal/fish/shark/grey").getValue()); + assertEquals("Match longest prefix", "5", p.getMatch("/animal/insect/bug").getValue()); + assertEquals("mismatch exact prefix", "5", p.getMatch("/animal").getValue()); + assertEquals("mismatch exact prefix", "5", p.getMatch("/animal/").getValue()); + + assertEquals("Match longest suffix", "0", p.getMatch("/suffix/path.tar.gz").getValue()); + assertEquals("Match longest suffix", "0", p.getMatch("/suffix/path.gz").getValue()); + assertEquals("prefix rather than suffix", "5", p.getMatch("/animal/path.gz").getValue()); + + assertEquals("default", "0", p.getMatch("/Other/path").getValue()); + + assertEquals("pathMatch /*", "", PathMap.pathMatch("/*", "/xxx/zzz")); + assertEquals("pathInfo /*", "/xxx/zzz", PathMap.pathInfo("/*", "/xxx/zzz")); + + assertTrue("match /", PathMap.match("/", "/anything")); + assertTrue("match /*", PathMap.match("/*", "/anything")); + assertTrue("match /foo", PathMap.match("/foo", "/foo")); + assertTrue("!match /foo", !PathMap.match("/foo", "/bar")); + assertTrue("match /foo/*", PathMap.match("/foo/*", "/foo")); + assertTrue("match /foo/*", PathMap.match("/foo/*", "/foo/")); + assertTrue("match /foo/*", PathMap.match("/foo/*", "/foo/anything")); + assertTrue("!match /foo/*", !PathMap.match("/foo/*", "/bar")); + assertTrue("!match /foo/*", !PathMap.match("/foo/*", "/bar/")); + assertTrue("!match /foo/*", !PathMap.match("/foo/*", "/bar/anything")); + assertTrue("match *.foo", PathMap.match("*.foo", "anything.foo")); + assertTrue("!match *.foo", !PathMap.match("*.foo", "anything.bar")); + } + + /** + * See JIRA issue: JETTY-88. + */ + public void testPathMappingsOnlyMatchOnDirectoryNames() throws Exception + { + String spec = "/xyz/*"; + + assertMatch(spec, "/xyz"); + assertMatch(spec, "/xyz/"); + assertMatch(spec, "/xyz/123"); + assertMatch(spec, "/xyz/123/"); + assertMatch(spec, "/xyz/123.txt"); + assertNotMatch(spec, "/xyz123"); + assertNotMatch(spec, "/xyz123;jessionid=99"); + assertNotMatch(spec, "/xyz123/"); + assertNotMatch(spec, "/xyz123/456"); + assertNotMatch(spec, "/xyz.123"); + assertNotMatch(spec, "/xyz;123"); // as if the ; was encoded and part of the path + assertNotMatch(spec, "/xyz?123"); // as if the ? was encoded and part of the path + } + + private void assertMatch(String spec, String path) + { + boolean match = PathMap.match(spec, path); + assertTrue("PathSpec '" + spec + "' should match path '" + path + "'", match); + } + + private void assertNotMatch(String spec, String path) + { + boolean match = PathMap.match(spec, path); + assertFalse("PathSpec '" + spec + "' should not match path '" + path + "'", match); + } +} diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml new file mode 100644 index 00000000000..56bdba25c1f --- /dev/null +++ b/jetty-io/pom.xml @@ -0,0 +1,64 @@ + + + jetty-project + org.eclipse.jetty + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + org.eclipse.jetty + jetty-io + Jetty :: IO Utility + + + org.eclipse.jetty + jetty-util + ${project.version} + + + junit + junit + test + + + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.io + J2SE-1.5 + http://jetty.eclipse.org + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + artifact-jar + + jar + + + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffer.java new file mode 100644 index 00000000000..adf92c230ba --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffer.java @@ -0,0 +1,707 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jetty.util.log.Log; + +/** + * + * + */ +public abstract class AbstractBuffer implements Buffer +{ + protected final static String + __IMMUTABLE = "IMMUTABLE", + __READONLY = "READONLY", + __READWRITE = "READWRITE", + __VOLATILE = "VOLATILE"; + + protected int _access; + protected boolean _volatile; + + protected int _get; + protected int _put; + protected int _hash; + protected int _hashGet; + protected int _hashPut; + protected int _mark; + protected String _string; + protected View _view; + + /** + * Constructor for BufferView + * + * @param access 0==IMMUTABLE, 1==READONLY, 2==READWRITE + */ + public AbstractBuffer(int access, boolean isVolatile) + { + if (access == IMMUTABLE && isVolatile) + throw new IllegalArgumentException("IMMUTABLE && VOLATILE"); + setMarkIndex(-1); + _access = access; + _volatile = isVolatile; + } + + /* + * @see org.eclipse.io.Buffer#toArray() + */ + public byte[] asArray() + { + byte[] bytes = new byte[length()]; + byte[] array = array(); + if (array != null) + System.arraycopy(array, getIndex(), bytes, 0, bytes.length); + else + peek(getIndex(), bytes, 0, length()); + return bytes; + } + + public ByteArrayBuffer duplicate(int access) + { + Buffer b=this.buffer(); + if (b instanceof Buffer.CaseInsensitve) + return new ByteArrayBuffer.CaseInsensitive(asArray(), 0, length(),access); + else + return new ByteArrayBuffer(asArray(), 0, length(), access); + } + + /* + * @see org.eclipse.io.Buffer#asNonVolatile() + */ + public Buffer asNonVolatileBuffer() + { + if (!isVolatile()) return this; + return duplicate(_access); + } + + public Buffer asImmutableBuffer() + { + if (isImmutable()) return this; + return duplicate(IMMUTABLE); + } + + /* + * @see org.eclipse.util.Buffer#asReadOnlyBuffer() + */ + public Buffer asReadOnlyBuffer() + { + if (isReadOnly()) return this; + return new View(this, markIndex(), getIndex(), putIndex(), READONLY); + } + + public Buffer asMutableBuffer() + { + if (!isImmutable()) return this; + + Buffer b=this.buffer(); + if (b.isReadOnly()) + { + return duplicate(READWRITE); + } + return new View(b, markIndex(), getIndex(), putIndex(), _access); + } + + public Buffer buffer() + { + return this; + } + + public void clear() + { + setMarkIndex(-1); + setGetIndex(0); + setPutIndex(0); + } + + public void compact() + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + int s = markIndex() >= 0 ? markIndex() : getIndex(); + if (s > 0) + { + byte array[] = array(); + int length = putIndex() - s; + if (length > 0) + { + if (array != null) + System.arraycopy(array(), s, array(), 0, length); + else + poke(0, peek(s, length)); + } + if (markIndex() > 0) setMarkIndex(markIndex() - s); + setGetIndex(getIndex() - s); + setPutIndex(putIndex() - s); + } + } + + public boolean equals(Object obj) + { + if (obj==this) + return true; + + // reject non buffers; + if (obj == null || !(obj instanceof Buffer)) return false; + Buffer b = (Buffer) obj; + + if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve) + return equalsIgnoreCase(b); + + // reject different lengths + if (b.length() != length()) return false; + + // reject AbstractBuffer with different hash value + if (_hash != 0 && obj instanceof AbstractBuffer) + { + AbstractBuffer ab = (AbstractBuffer) obj; + if (ab._hash != 0 && _hash != ab._hash) return false; + } + + // Nothing for it but to do the hard grind. + int get=getIndex(); + int bi=b.putIndex(); + for (int i = putIndex(); i-->get;) + { + byte b1 = peek(i); + byte b2 = b.peek(--bi); + if (b1 != b2) return false; + } + return true; + } + + public boolean equalsIgnoreCase(Buffer b) + { + if (b==this) + return true; + + // reject different lengths + if (b.length() != length()) return false; + + // reject AbstractBuffer with different hash value + if (_hash != 0 && b instanceof AbstractBuffer) + { + AbstractBuffer ab = (AbstractBuffer) b; + if (ab._hash != 0 && _hash != ab._hash) return false; + } + + // Nothing for it but to do the hard grind. + int get=getIndex(); + int bi=b.putIndex(); + + byte[] array = array(); + byte[] barray= b.array(); + if (array!=null && barray!=null) + { + for (int i = putIndex(); i-->get;) + { + byte b1 = array[i]; + byte b2 = barray[--bi]; + if (b1 != b2) + { + if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A'); + if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A'); + if (b1 != b2) return false; + } + } + } + else + { + for (int i = putIndex(); i-->get;) + { + byte b1 = peek(i); + byte b2 = b.peek(--bi); + if (b1 != b2) + { + if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A'); + if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A'); + if (b1 != b2) return false; + } + } + } + return true; + } + + public byte get() + { + return peek(_get++); + } + + public int get(byte[] b, int offset, int length) + { + int gi = getIndex(); + int l=length(); + if (l==0) + return -1; + + if (length>l) + length=l; + + length = peek(gi, b, offset, length); + if (length>0) + setGetIndex(gi + length); + return length; + } + + public Buffer get(int length) + { + int gi = getIndex(); + Buffer view = peek(gi, length); + setGetIndex(gi + length); + return view; + } + + public final int getIndex() + { + return _get; + } + + public boolean hasContent() + { + return _put > _get; + } + + public int hashCode() + { + if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) + { + int get=getIndex(); + byte[] array = array(); + if (array==null) + { + for (int i = putIndex(); i-- >get;) + { + byte b = peek(i); + if ('a' <= b && b <= 'z') + b = (byte) (b - 'a' + 'A'); + _hash = 31 * _hash + b; + } + } + else + { + for (int i = putIndex(); i-- >get;) + { + byte b = array[i]; + if ('a' <= b && b <= 'z') + b = (byte) (b - 'a' + 'A'); + _hash = 31 * _hash + b; + } + } + if (_hash == 0) + _hash = -1; + _hashGet=_get; + _hashPut=_put; + + } + return _hash; + } + + public boolean isImmutable() + { + return _access <= IMMUTABLE; + } + + public boolean isReadOnly() + { + return _access <= READONLY; + } + + public boolean isVolatile() + { + return _volatile; + } + + public int length() + { + return _put - _get; + } + + public void mark() + { + setMarkIndex(_get - 1); + } + + public void mark(int offset) + { + setMarkIndex(_get + offset); + } + + public int markIndex() + { + return _mark; + } + + public byte peek() + { + return peek(_get); + } + + public Buffer peek(int index, int length) + { + if (_view == null) + { + _view = new View(this, -1, index, index + length, isReadOnly() ? READONLY : READWRITE); + } + else + { + _view.update(this.buffer()); + _view.setMarkIndex(-1); + _view.setGetIndex(0); + _view.setPutIndex(index + length); + _view.setGetIndex(index); + + } + return _view; + } + + public int poke(int index, Buffer src) + { + _hash=0; + /* + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + if (index < 0) + throw new IllegalArgumentException("index<0: " + index + "<0"); + */ + + int length=src.length(); + if (index + length > capacity()) + { + length=capacity()-index; + /* + if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + } + + byte[] src_array = src.array(); + byte[] dst_array = array(); + if (src_array != null && dst_array != null) + System.arraycopy(src_array, src.getIndex(), dst_array, index, length); + else if (src_array != null) + { + int s=src.getIndex(); + for (int i=0;i capacity()) + { + length=capacity()-index; + /* if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + } + + byte[] dst_array = array(); + if (dst_array != null) + System.arraycopy(b, offset, dst_array, index, length); + else + { + int s=offset; + for (int i=0;i= 0) setGetIndex(markIndex()); + } + + public void rewind() + { + setGetIndex(0); + setMarkIndex(-1); + } + + public void setGetIndex(int getIndex) + { + /* bounds checking + if (isImmutable()) + throw new IllegalStateException(__IMMUTABLE); + if (getIndex < 0) + throw new IllegalArgumentException("getIndex<0: " + getIndex + "<0"); + if (getIndex > putIndex()) + throw new IllegalArgumentException("getIndex>putIndex: " + getIndex + ">" + putIndex()); + */ + _get = getIndex; + _hash=0; + } + + public void setMarkIndex(int index) + { + /* + if (index>=0 && isImmutable()) + throw new IllegalStateException(__IMMUTABLE); + */ + _mark = index; + } + + public void setPutIndex(int putIndex) + { + /* bounds checking + if (isImmutable()) + throw new IllegalStateException(__IMMUTABLE); + if (putIndex > capacity()) + throw new IllegalArgumentException("putIndex>capacity: " + putIndex + ">" + capacity()); + if (getIndex() > putIndex) + throw new IllegalArgumentException("getIndex>putIndex: " + getIndex() + ">" + putIndex); + */ + _put = putIndex; + _hash=0; + } + + public int skip(int n) + { + if (length() < n) n = length(); + setGetIndex(getIndex() + n); + return n; + } + + public Buffer slice() + { + return peek(getIndex(), length()); + } + + public Buffer sliceFromMark() + { + return sliceFromMark(getIndex() - markIndex() - 1); + } + + public Buffer sliceFromMark(int length) + { + if (markIndex() < 0) return null; + Buffer view = peek(markIndex(), length); + setMarkIndex(-1); + return view; + } + + public int space() + { + return capacity() - _put; + } + + public String toDetailString() + { + StringBuilder buf = new StringBuilder(); + buf.append("["); + buf.append(super.hashCode()); + buf.append(","); + buf.append(this.buffer().hashCode()); + buf.append(",m="); + buf.append(markIndex()); + buf.append(",g="); + buf.append(getIndex()); + buf.append(",p="); + buf.append(putIndex()); + buf.append(",c="); + buf.append(capacity()); + buf.append("]={"); + if (markIndex() >= 0) + { + for (int i = markIndex(); i < getIndex(); i++) + { + char c = (char) peek(i); + if (Character.isISOControl(c)) + { + buf.append(c < 16 ? "\\0" : "\\"); + buf.append(Integer.toString(c, 16)); + } + else + buf.append(c); + } + buf.append("}{"); + } + int count = 0; + for (int i = getIndex(); i < putIndex(); i++) + { + char c = (char) peek(i); + if (Character.isISOControl(c)) + { + buf.append(c < 16 ? "\\0" : "\\"); + buf.append(Integer.toString(c, 16)); + } + else + buf.append(c); + if (count++ == 50) + { + if (putIndex() - i > 20) + { + buf.append(" ... "); + i = putIndex() - 20; + } + } + } + buf.append('}'); + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + if (isImmutable()) + { + if (_string == null) + _string = new String(asArray(), 0, length()); + return _string; + } + return new String(asArray(), 0, length()); + } + + /* ------------------------------------------------------------ */ + public String toString(String charset) + { + try + { + byte[] bytes=array(); + if (bytes!=null) + return new String(bytes,getIndex(),length(),charset); + return new String(asArray(), 0, length(),charset); + } + catch(Exception e) + { + Log.warn(e); + return new String(asArray(), 0, length()); + } + } + + /* ------------------------------------------------------------ */ + public String toDebugString() + { + return getClass()+"@"+super.hashCode(); + } + + /* ------------------------------------------------------------ */ + public void writeTo(OutputStream out) + throws IOException + { + byte[] array = array(); + + if (array!=null) + { + out.write(array,getIndex(),length()); + } + else + { + int len = this.length(); + byte[] buf=new byte[len>1024?1024:len]; + int offset=_get; + while (len>0) + { + int l=peek(offset,buf,0,len>buf.length?buf.length:len); + out.write(buf,0,l); + offset+=l; + len-=l; + } + } + clear(); + } + + /* ------------------------------------------------------------ */ + public int readFrom(InputStream in,int max) throws IOException + { + byte[] array = array(); + int s=space(); + if (s>max) + s=max; + + if (array!=null) + { + int l=in.read(array,_put,s); + if (l>0) + _put+=l; + return l; + } + else + { + byte[] buf=new byte[s>1024?1024:s]; + int total=0; + while (s>0) + { + int l=in.read(buf,0,buf.length); + if (l<0) + return total>0?total:-1; + int p=put(buf,0,l); + assert l==p; + s-=l; + } + return total; + } + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java new file mode 100644 index 00000000000..515ff3fd58c --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java @@ -0,0 +1,193 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; + +/* ------------------------------------------------------------ */ +/** Abstract Buffer pool. + * simple unbounded pool of buffers for header, request and response sizes. + * + */ +public abstract class AbstractBuffers extends AbstractLifeCycle implements Buffers +{ + private int _headerBufferSize=4*1024; + private int _requestBufferSize=8*1024; + private int _responseBufferSize=24*1024; + + final static private int __HEADER=0; + final static private int __REQUEST=1; + final static private int __RESPONSE=2; + final static private int __OTHER=3; + final private int[] _pool={2,1,1,2}; + + private final ThreadLocal _buffers=new ThreadLocal() + { + protected Object initialValue() + { + return new ThreadBuffers(_pool[__HEADER],_pool[__REQUEST],_pool[__RESPONSE],_pool[__OTHER]); + } + }; + + public AbstractBuffers() + { + super(); + } + + + + public Buffer getBuffer(final int size ) + { + final int set = (size==_headerBufferSize)?__HEADER + :(size==_responseBufferSize)?__RESPONSE + :(size==_requestBufferSize)?__REQUEST:__OTHER; + + final ThreadBuffers thread_buffers = (ThreadBuffers)_buffers.get(); + + final Buffer[] buffers=thread_buffers._buffers[set]; + for (int i=0;ibyte[] backing this buffer or null if none exists. + */ + byte[] array(); + + /** + * + * @return a byte[] value of the bytes from the getIndex to the putIndex. + */ + byte[] asArray(); + + /** + * Get the unerlying buffer. If this buffer wraps a backing buffer. + * @return The root backing buffer or this if there is no backing buffer; + */ + Buffer buffer(); + + /** + * + * @return a non volitile version of this Buffer value + */ + Buffer asNonVolatileBuffer(); + + /** + * + * @return a readonly version of this Buffer. + */ + Buffer asReadOnlyBuffer(); + + /** + * + * @return an immutable version of this Buffer. + */ + Buffer asImmutableBuffer(); + + /** + * + * @return an immutable version of this Buffer. + */ + Buffer asMutableBuffer(); + + /** + * + * The capacity of the buffer. This is the maximum putIndex that may be set. + * @return an int value + */ + int capacity(); + + /** + * the space remaining in the buffer. + * @return capacity - putIndex + */ + int space(); + + /** + * Clear the buffer. getIndex=0, putIndex=0. + */ + void clear(); + + /** + * Compact the buffer by discarding bytes before the postion (or mark if set). + * Bytes from the getIndex (or mark) to the putIndex are moved to the beginning of + * the buffer and the values adjusted accordingly. + */ + void compact(); + + /** + * Get the byte at the current getIndex and increment it. + * @return The byte value from the current getIndex. + */ + byte get(); + + /** + * Get bytes from the current postion and put them into the passed byte array. + * The getIndex is incremented by the number of bytes copied into the array. + * @param b The byte array to fill. + * @param offset Offset in the array. + * @param length The max number of bytes to read. + * @return The number of bytes actually read. + */ + int get(byte[] b, int offset, int length); + + /** + * + * @param length an int value + * @return a Buffer value + */ + Buffer get(int length); + + /** + * The index within the buffer that will next be read or written. + * @return an int value >=0 <= putIndex() + */ + int getIndex(); + + /** + * @return true of putIndex > getIndex + */ + boolean hasContent(); + + /** + * + * @return a boolean value true if case sensitive comparison on this buffer + */ + boolean equalsIgnoreCase(Buffer buffer); + + + /** + * + * @return a boolean value true if the buffer is immutable and that neither + * the buffer contents nor the indexes may be changed. + */ + boolean isImmutable(); + + /** + * + * @return a boolean value true if the buffer is readonly. The buffer indexes may + * be modified, but the buffer contents may not. For example a View onto an immutable Buffer will be + * read only. + */ + boolean isReadOnly(); + + /** + * + * @return a boolean value true if the buffer contents may change + * via alternate paths than this buffer. If the contents of this buffer are to be used outside of the + * current context, then a copy must be made. + */ + boolean isVolatile(); + + /** + * The number of bytes from the getIndex to the putIndex + * @return an int == putIndex()-getIndex() + */ + int length(); + + /** + * Set the mark to the current getIndex. + */ + void mark(); + + /** + * Set the mark relative to the current getIndex + * @param offset an int value to add to the current getIndex to obtain the mark value. + */ + void mark(int offset); + + /** + * The current index of the mark. + * @return an int index in the buffer or -1 if the mark is not set. + */ + int markIndex(); + + /** + * Get the byte at the current getIndex without incrementing the getIndex. + * @return The byte value from the current getIndex. + */ + byte peek(); + + /** + * Get the byte at a specific index in the buffer. + * @param index an int value + * @return a byte value + */ + byte peek(int index); + + /** + * + * @param index an int value + * @param length an int value + * @return The Buffer value from the requested getIndex. + */ + Buffer peek(int index, int length); + + /** + * + * @param index an int value + * @param b The byte array to peek into + * @param offset The offset into the array to start peeking + * @param length an int value + * @return The number of bytes actually peeked + */ + int peek(int index, byte[] b, int offset, int length); + + /** + * Put the contents of the buffer at the specific index. + * @param index an int value + * @param src a Buffer. If the source buffer is not modified + + * @return The number of bytes actually poked + */ + int poke(int index, Buffer src); + + /** + * Put a specific byte to a specific getIndex. + * @param index an int value + * @param b a byte value + */ + void poke(int index, byte b); + + /** + * Put a specific byte to a specific getIndex. + * @param index an int value + * @param b a byte array value + * @return The number of bytes actually poked + */ + int poke(int index, byte b[], int offset, int length); + + /** + * Write the bytes from the source buffer to the current getIndex. + * @param src The source Buffer it is not modified. + * @return The number of bytes actually poked + */ + int put(Buffer src); + + /** + * Put a byte to the current getIndex and increment the getIndex. + * @param b a byte value + */ + void put(byte b); + + /** + * Put a byte to the current getIndex and increment the getIndex. + * @param b a byte value + * @return The number of bytes actually poked + */ + int put(byte[] b,int offset, int length); + + /** + * Put a byte to the current getIndex and increment the getIndex. + * @param b a byte value + * @return The number of bytes actually poked + */ + int put(byte[] b); + + /** + * The index of the first element that should not be read. + * @return an int value >= getIndex() + */ + int putIndex(); + + /** + * Reset the current getIndex to the mark + */ + void reset(); + + /** + * Set the buffers start getIndex. + * @param newStart an int value + */ + void setGetIndex(int newStart); + + /** + * Set a specific value for the mark. + * @param newMark an int value + */ + void setMarkIndex(int newMark); + + /** + * + * @param newLimit an int value + */ + void setPutIndex(int newLimit); + + /** + * Skip _content. The getIndex is updated by min(remaining(), n) + * @param n The number of bytes to skip + * @return the number of bytes skipped. + */ + int skip(int n); + + /** + * + * @return a volitile Buffer from the postion to the putIndex. + */ + Buffer slice(); + + /** + * + * + * @return a volitile Buffer value from the mark to the putIndex + */ + Buffer sliceFromMark(); + + /** + * + * + * @param length an int value + * @return a valitile Buffer value from the mark of the length requested. + */ + Buffer sliceFromMark(int length); + + /** + * + * @return a String value describing the state and contents of the buffer. + */ + String toDetailString(); + + /* ------------------------------------------------------------ */ + /** Write the buffer's contents to the output stream + * @param out + */ + void writeTo(OutputStream out) throws IOException; + + /* ------------------------------------------------------------ */ + /** Read the buffer's contents from the input stream + * @param in input stream + * @param max maximum number of bytes that may be read + * @return actual number of bytes read or -1 for EOF + */ + int readFrom(InputStream in, int max) throws IOException; + + + /* ------------------------------------------------------------ */ + String toString(String charset); + + /* + * Buffers implementing this interface should be compared with case insensitive equals + * + */ + public interface CaseInsensitve + {} + + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java new file mode 100644 index 00000000000..f1e894957b7 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java @@ -0,0 +1,154 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; + +import org.eclipse.jetty.util.StringMap; + +/* ------------------------------------------------------------------------------- */ +/** + * Stores a collection of {@link Buffer} objects. + * Buffers are stored in an ordered collection and can retreived by index or value + * + */ +public class BufferCache +{ + private HashMap _bufferMap=new HashMap(); + private StringMap _stringMap=new StringMap(StringMap.CASE_INSENSTIVE); + private ArrayList _index= new ArrayList(); + + /* ------------------------------------------------------------------------------- */ + /** Add a buffer to the cache at the specified index. + * @param value The content of the buffer. + */ + public CachedBuffer add(String value, int ordinal) + { + CachedBuffer buffer= new CachedBuffer(value, ordinal); + _bufferMap.put(buffer, buffer); + _stringMap.put(value, buffer); + while ((ordinal - _index.size()) >= 0) + _index.add(null); + if (_index.get(ordinal)==null) + _index.add(ordinal, buffer); + return buffer; + } + + public CachedBuffer get(int ordinal) + { + if (ordinal < 0 || ordinal >= _index.size()) + return null; + return (CachedBuffer)_index.get(ordinal); + } + + public CachedBuffer get(Buffer buffer) + { + return (CachedBuffer)_bufferMap.get(buffer); + } + + public CachedBuffer get(String value) + { + return (CachedBuffer)_stringMap.get(value); + } + + public Buffer lookup(Buffer buffer) + { + Buffer b= get(buffer); + if (b == null) + { + if (buffer instanceof Buffer.CaseInsensitve) + return buffer; + return new View.CaseInsensitive(buffer); + } + + return b; + } + + public CachedBuffer getBest(byte[] value, int offset, int maxLength) + { + Entry entry = _stringMap.getBestEntry(value, offset, maxLength); + if (entry!=null) + return (CachedBuffer)entry.getValue(); + return null; + } + + public Buffer lookup(String value) + { + Buffer b= get(value); + if (b == null) + { + return new CachedBuffer(value,-1); + } + return b; + } + + public String toString(Buffer buffer) + { + return lookup(buffer).toString(); + } + + public int getOrdinal(Buffer buffer) + { + if (buffer instanceof CachedBuffer) + return ((CachedBuffer)buffer).getOrdinal(); + buffer=lookup(buffer); + if (buffer!=null && buffer instanceof CachedBuffer) + return ((CachedBuffer)buffer).getOrdinal(); + return -1; + } + + public static class CachedBuffer extends ByteArrayBuffer.CaseInsensitive + { + private int _ordinal; + private HashMap _associateMap=null; + + public CachedBuffer(String value, int ordinal) + { + super(value); + _ordinal= ordinal; + } + + public int getOrdinal() + { + return _ordinal; + } + + public CachedBuffer getAssociate(Object key) + { + if (_associateMap==null) + return null; + return (CachedBuffer)_associateMap.get(key); + } + + // TODO Replace Associate with a mime encoding specific solution + public void setAssociate(Object key, CachedBuffer associate) + { + if (_associateMap==null) + _associateMap=new HashMap(); + _associateMap.put(key,associate); + } + } + + + public String toString() + { + return "CACHE["+ + "bufferMap="+_bufferMap+ + ",stringMap="+_stringMap+ + ",index="+_index+ + "]"; + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java new file mode 100644 index 00000000000..275d2f3b77d --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java @@ -0,0 +1,56 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.text.DateFormatSymbols; +import java.util.Locale; + +import org.eclipse.jetty.util.DateCache; + +public class BufferDateCache extends DateCache +{ + Buffer _buffer; + String _last; + + public BufferDateCache() + { + super(); + } + + public BufferDateCache(String format, DateFormatSymbols s) + { + super(format,s); + } + + public BufferDateCache(String format, Locale l) + { + super(format,l); + } + + public BufferDateCache(String format) + { + super(format); + } + + public synchronized Buffer formatBuffer(long date) + { + String d = super.format(date); + if (d==_last) + return _buffer; + _last=d; + _buffer=new ByteArrayBuffer(d); + + return _buffer; + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferUtil.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferUtil.java new file mode 100644 index 00000000000..91b4d436d06 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferUtil.java @@ -0,0 +1,311 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.util.StringUtil; + +/* ------------------------------------------------------------------------------- */ +/** Buffer utility methods. + * + * + */ +public class BufferUtil +{ + static final byte SPACE= 0x20; + static final byte MINUS= '-'; + static final byte[] DIGIT= + {(byte)'0',(byte)'1',(byte)'2',(byte)'3',(byte)'4',(byte)'5',(byte)'6',(byte)'7',(byte)'8',(byte)'9',(byte)'A',(byte)'B',(byte)'C',(byte)'D',(byte)'E',(byte)'F'}; + + /** + * Convert buffer to an integer. + * Parses up to the first non-numeric character. If no number is found an + * IllegalArgumentException is thrown + * @param buffer A buffer containing an integer. The position is not changed. + * @return an int + */ + public static int toInt(Buffer buffer) + { + int val= 0; + boolean started= false; + boolean minus= false; + for (int i= buffer.getIndex(); i < buffer.putIndex(); i++) + { + byte b= buffer.peek(i); + if (b <= SPACE) + { + if (started) + break; + } + else if (b >= '0' && b <= '9') + { + val= val * 10 + (b - '0'); + started= true; + } + else if (b == MINUS && !started) + { + minus= true; + } + else + break; + } + + if (started) + return minus ? (-val) : val; + throw new NumberFormatException(buffer.toString()); + } + + /** + * Convert buffer to an long. + * Parses up to the first non-numeric character. If no number is found an + * IllegalArgumentException is thrown + * @param buffer A buffer containing an integer. The position is not changed. + * @return an int + */ + public static long toLong(Buffer buffer) + { + long val= 0; + boolean started= false; + boolean minus= false; + for (int i= buffer.getIndex(); i < buffer.putIndex(); i++) + { + byte b= buffer.peek(i); + if (b <= SPACE) + { + if (started) + break; + } + else if (b >= '0' && b <= '9') + { + val= val * 10L + (b - '0'); + started= true; + } + else if (b == MINUS && !started) + { + minus= true; + } + else + break; + } + + if (started) + return minus ? (-val) : val; + throw new NumberFormatException(buffer.toString()); + } + + public static void putHexInt(Buffer buffer, int n) + { + + if (n < 0) + { + buffer.put((byte)'-'); + + if (n == Integer.MIN_VALUE) + { + buffer.put((byte)(0x7f&'8')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + + return; + } + n= -n; + } + + if (n < 0x10) + { + buffer.put(DIGIT[n]); + } + else + { + boolean started= false; + // This assumes constant time int arithmatic + for (int i= 0; i < hexDivisors.length; i++) + { + if (n < hexDivisors[i]) + { + if (started) + buffer.put((byte)'0'); + continue; + } + + started= true; + int d= n / hexDivisors[i]; + buffer.put(DIGIT[d]); + n= n - d * hexDivisors[i]; + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Add hex integer BEFORE current getIndex. + * @param buffer + * @param n + */ + public static void prependHexInt(Buffer buffer, int n) + { + if (n==0) + { + int gi=buffer.getIndex(); + buffer.poke(--gi,(byte)'0'); + buffer.setGetIndex(gi); + } + else + { + boolean minus=false; + if (n<0) + { + minus=true; + n=-n; + } + + int gi=buffer.getIndex(); + while(n>0) + { + int d = 0xf&n; + n=n>>4; + buffer.poke(--gi,DIGIT[d]); + } + + if (minus) + buffer.poke(--gi,(byte)'-'); + buffer.setGetIndex(gi); + } + } + + + /* ------------------------------------------------------------ */ + public static void putDecInt(Buffer buffer, int n) + { + if (n < 0) + { + buffer.put((byte)'-'); + + if (n == Integer.MIN_VALUE) + { + buffer.put((byte)'2'); + n= 147483648; + } + else + n= -n; + } + + if (n < 10) + { + buffer.put(DIGIT[n]); + } + else + { + boolean started= false; + // This assumes constant time int arithmatic + for (int i= 0; i < decDivisors.length; i++) + { + if (n < decDivisors[i]) + { + if (started) + buffer.put((byte)'0'); + continue; + } + + started= true; + int d= n / decDivisors[i]; + buffer.put(DIGIT[d]); + n= n - d * decDivisors[i]; + } + } + } + + public static void putDecLong(Buffer buffer, long n) + { + if (n < 0) + { + buffer.put((byte)'-'); + + if (n == Long.MIN_VALUE) + { + buffer.put((byte)'9'); + n= 223372036854775808L; + } + else + n= -n; + } + + if (n < 10) + { + buffer.put(DIGIT[(int)n]); + } + else + { + boolean started= false; + // This assumes constant time int arithmatic + for (int i= 0; i < decDivisors.length; i++) + { + if (n < decDivisors[i]) + { + if (started) + buffer.put((byte)'0'); + continue; + } + + started= true; + long d= n / decDivisors[i]; + buffer.put(DIGIT[(int)d]); + n= n - d * decDivisors[i]; + } + } + } + + public static Buffer toBuffer(long value) + { + ByteArrayBuffer buf=new ByteArrayBuffer(16); + putDecLong(buf, value); + return buf; + } + + private static int[] decDivisors= + { 1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 }; + + private static int[] hexDivisors= + { 0x10000000, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, 0x10, 1 }; + + + public static void putCRLF(Buffer buffer) + { + buffer.put((byte)13); + buffer.put((byte)10); + } + + public static boolean isPrefix(Buffer prefix,Buffer buffer) + { + if (prefix.length()>buffer.length()) + return false; + int bi=buffer.getIndex(); + for (int i=prefix.getIndex(); i= 0 ? markIndex() : getIndex(); + if (s > 0) + { + int length = putIndex() - s; + if (length > 0) + { + System.arraycopy(_bytes, s,_bytes, 0, length); + } + if (markIndex() > 0) setMarkIndex(markIndex() - s); + setGetIndex(getIndex() - s); + setPutIndex(putIndex() - s); + } + } + + + public boolean equals(Object obj) + { + if (obj==this) + return true; + + if (obj == null || !(obj instanceof Buffer)) + return false; + + if (obj instanceof Buffer.CaseInsensitve) + return equalsIgnoreCase((Buffer)obj); + + + Buffer b = (Buffer) obj; + + // reject different lengths + if (b.length() != length()) + return false; + + // reject AbstractBuffer with different hash value + if (_hash != 0 && obj instanceof AbstractBuffer) + { + AbstractBuffer ab = (AbstractBuffer) obj; + if (ab._hash != 0 && _hash != ab._hash) + return false; + } + + // Nothing for it but to do the hard grind. + int get=getIndex(); + int bi=b.putIndex(); + for (int i = putIndex(); i-->get;) + { + byte b1 = _bytes[i]; + byte b2 = b.peek(--bi); + if (b1 != b2) return false; + } + return true; + } + + + public boolean equalsIgnoreCase(Buffer b) + { + if (b==this) + return true; + + // reject different lengths + if (b==null || b.length() != length()) + return false; + + // reject AbstractBuffer with different hash value + if (_hash != 0 && b instanceof AbstractBuffer) + { + AbstractBuffer ab = (AbstractBuffer) b; + if (ab._hash != 0 && _hash != ab._hash) return false; + } + + // Nothing for it but to do the hard grind. + int get=getIndex(); + int bi=b.putIndex(); + byte[] barray=b.array(); + if (barray==null) + { + for (int i = putIndex(); i-->get;) + { + byte b1 = _bytes[i]; + byte b2 = b.peek(--bi); + if (b1 != b2) + { + if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A'); + if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A'); + if (b1 != b2) return false; + } + } + } + else + { + for (int i = putIndex(); i-->get;) + { + byte b1 = _bytes[i]; + byte b2 = barray[--bi]; + if (b1 != b2) + { + if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A'); + if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A'); + if (b1 != b2) return false; + } + } + } + return true; + } + + public byte get() + { + return _bytes[_get++]; + } + + public int hashCode() + { + if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) + { + int get=getIndex(); + for (int i = putIndex(); i-- >get;) + { + byte b = _bytes[i]; + if ('a' <= b && b <= 'z') + b = (byte) (b - 'a' + 'A'); + _hash = 31 * _hash + b; + } + if (_hash == 0) + _hash = -1; + _hashGet=_get; + _hashPut=_put; + } + return _hash; + } + + + public byte peek(int index) + { + return _bytes[index]; + } + + public int peek(int index, byte[] b, int offset, int length) + { + int l = length; + if (index + l > capacity()) + { + l = capacity() - index; + if (l==0) + return -1; + } + + if (l < 0) + return -1; + + System.arraycopy(_bytes, index, b, offset, l); + return l; + } + + public void poke(int index, byte b) + { + /* + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + + if (index < 0) + throw new IllegalArgumentException("index<0: " + index + "<0"); + if (index > capacity()) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + _bytes[index] = b; + } + + public int poke(int index, Buffer src) + { + _hash=0; + + /* + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + if (index < 0) + throw new IllegalArgumentException("index<0: " + index + "<0"); + */ + + int length=src.length(); + if (index + length > capacity()) + { + length=capacity()-index; + /* + if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + } + + byte[] src_array = src.array(); + if (src_array != null) + System.arraycopy(src_array, src.getIndex(), _bytes, index, length); + else if (src_array != null) + { + int s=src.getIndex(); + for (int i=0;i capacity()) + { + length=capacity()-index; + /* if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + } + + System.arraycopy(b, offset, _bytes, index, length); + + return length; + } + + /* ------------------------------------------------------------ */ + /** Wrap a byte array. + * @param b + * @param off + * @param len + */ + public void wrap(byte[] b, int off, int len) + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + if (isImmutable()) throw new IllegalStateException(__IMMUTABLE); + _bytes=b; + clear(); + setGetIndex(off); + setPutIndex(off+len); + } + + /* ------------------------------------------------------------ */ + /** Wrap a byte array + * @param b + */ + public void wrap(byte[] b) + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + if (isImmutable()) throw new IllegalStateException(__IMMUTABLE); + _bytes=b; + setGetIndex(0); + setPutIndex(b.length); + } + + /* ------------------------------------------------------------ */ + public void writeTo(OutputStream out) + throws IOException + { + out.write(_bytes,getIndex(),length()); + clear(); + } + + /* ------------------------------------------------------------ */ + public int readFrom(InputStream in,int max) throws IOException + { + if (max<0||max>space()) + max=space(); + int p = putIndex(); + + int len=0, total=0, available=max; + while (total0) + { + p += len; + total += len; + available -= len; + setPutIndex(p); + } + if (in.available()<=0) + break; + } + if (len<0 && total==0) + return -1; + return total; + } + + /* ------------------------------------------------------------ */ + public int space() + { + return _bytes.length - _put; + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static class CaseInsensitive extends ByteArrayBuffer implements Buffer.CaseInsensitve + { + public CaseInsensitive(String s) + { + super(s); + } + + public CaseInsensitive(byte[] b, int o, int l, int rw) + { + super(b,o,l,rw); + } + + public boolean equals(Object obj) + { + return equalsIgnoreCase((Buffer)obj); + } + + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java new file mode 100644 index 00000000000..e17b849aad4 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java @@ -0,0 +1,338 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.io.IOException; + + + +/* ------------------------------------------------------------ */ +/** ByteArrayEndPoint. + * + * + */ +public class ByteArrayEndPoint implements EndPoint +{ + byte[] _inBytes; + ByteArrayBuffer _in; + ByteArrayBuffer _out; + boolean _closed; + boolean _nonBlocking; + boolean _growOutput; + + /* ------------------------------------------------------------ */ + /** + * + */ + public ByteArrayEndPoint() + { + } + + /* ------------------------------------------------------------ */ + /** + * @return the nonBlocking + */ + public boolean isNonBlocking() + { + return _nonBlocking; + } + + /* ------------------------------------------------------------ */ + /** + * @param nonBlocking the nonBlocking to set + */ + public void setNonBlocking(boolean nonBlocking) + { + _nonBlocking=nonBlocking; + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public ByteArrayEndPoint(byte[] input, int outputSize) + { + _inBytes=input; + _in=new ByteArrayBuffer(input); + _out=new ByteArrayBuffer(outputSize); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the in. + */ + public ByteArrayBuffer getIn() + { + return _in; + } + /* ------------------------------------------------------------ */ + /** + * @param in The in to set. + */ + public void setIn(ByteArrayBuffer in) + { + _in = in; + } + /* ------------------------------------------------------------ */ + /** + * @return Returns the out. + */ + public ByteArrayBuffer getOut() + { + return _out; + } + /* ------------------------------------------------------------ */ + /** + * @param out The out to set. + */ + public void setOut(ByteArrayBuffer out) + { + _out = out; + } + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#isOpen() + */ + public boolean isOpen() + { + return !_closed; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#isBlocking() + */ + public boolean isBlocking() + { + return !_nonBlocking; + } + + /* ------------------------------------------------------------ */ + public boolean blockReadable(long millisecs) + { + return true; + } + + /* ------------------------------------------------------------ */ + public boolean blockWritable(long millisecs) + { + return true; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#close() + */ + public void close() throws IOException + { + _closed=true; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer) + */ + public int fill(Buffer buffer) throws IOException + { + if (_closed) + throw new IOException("CLOSED"); + if (_in==null) + return -1; + if (_in.length()<=0) + return _nonBlocking?0:-1; + int len = buffer.put(_in); + _in.skip(len); + return len; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer) + */ + public int flush(Buffer buffer) throws IOException + { + if (_closed) + throw new IOException("CLOSED"); + if (_growOutput && buffer.length()>_out.space()) + { + _out.compact(); + + if (buffer.length()>_out.space()) + { + ByteArrayBuffer n = new ByteArrayBuffer(_out.putIndex()+buffer.length()); + + n.put(_out.peek(0,_out.putIndex())); + if (_out.getIndex()>0) + { + n.mark(); + n.setGetIndex(_out.getIndex()); + } + _out=n; + } + } + int len = _out.put(buffer); + buffer.skip(len); + return len; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + if (_closed) + throw new IOException("CLOSED"); + + int flushed=0; + + if (header!=null && header.length()>0) + flushed=flush(header); + + if (header==null || header.length()==0) + { + if (buffer!=null && buffer.length()>0) + flushed+=flush(buffer); + + if (buffer==null || buffer.length()==0) + { + if (trailer!=null && trailer.length()>0) + { + flushed+=flush(trailer); + } + } + } + + return flushed; + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public void reset() + { + _closed=false; + _in.clear(); + _out.clear(); + if (_inBytes!=null) + _in.setPutIndex(_inBytes.length); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalAddr() + */ + public String getLocalAddr() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalHost() + */ + public String getLocalHost() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalPort() + */ + public int getLocalPort() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteAddr() + */ + public String getRemoteAddr() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteHost() + */ + public String getRemoteHost() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemotePort() + */ + public int getRemotePort() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getConnection() + */ + public Object getTransport() + { + return _inBytes; + } + + /* ------------------------------------------------------------ */ + public void flush() throws IOException + { + } + + /* ------------------------------------------------------------ */ + public boolean isBufferingInput() + { + return false; + } + + /* ------------------------------------------------------------ */ + public boolean isBufferingOutput() + { + return false; + } + + /* ------------------------------------------------------------ */ + public boolean isBufferred() + { + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @return the growOutput + */ + public boolean isGrowOutput() + { + return _growOutput; + } + + /* ------------------------------------------------------------ */ + /** + * @param growOutput the growOutput to set + */ + public void setGrowOutput(boolean growOutput) + { + _growOutput=growOutput; + } + + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java new file mode 100644 index 00000000000..5545ddddeac --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java @@ -0,0 +1,23 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.io.IOException; + +public interface Connection +{ + void handle() throws IOException; + + boolean isIdle(); +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java new file mode 100644 index 00000000000..24e7fb9fc6c --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java @@ -0,0 +1,152 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.io.IOException; + + + +/** + * + * A transport EndPoint + */ +public interface EndPoint +{ + + /** + * Close any backing stream associated with the buffer + */ + void close() throws IOException; + + /** + * Fill the buffer from the current putIndex to it's capacity from whatever + * byte source is backing the buffer. The putIndex is increased if bytes filled. + * The buffer may chose to do a compact before filling. + * @return an int value indicating the number of bytes + * filled or -1 if EOF is reached. + */ + int fill(Buffer buffer) throws IOException; + + + /** + * Flush the buffer from the current getIndex to it's putIndex using whatever byte + * sink is backing the buffer. The getIndex is updated with the number of bytes flushed. + * Any mark set is cleared. + * If the entire contents of the buffer are flushed, then an implicit empty() is done. + * + * @param buffer The buffer to flush. This buffers getIndex is updated. + * @return the number of bytes written + */ + int flush(Buffer buffer) throws IOException; + + /** + * Flush the buffer from the current getIndex to it's putIndex using whatever byte + * sink is backing the buffer. The getIndex is updated with the number of bytes flushed. + * Any mark set is cleared. + * If the entire contents of the buffer are flushed, then an implicit empty() is done. + * The passed header/trailer buffers are written before/after the contents of this buffer. This may be done + * either as gather writes, as a poke into this buffer or as several writes. The implementation is free to + * select the optimal mechanism. + * @param header A buffer to write before flushing this buffer. This buffers getIndex is updated. + * @param buffer The buffer to flush. This buffers getIndex is updated. + * @param trailer A buffer to write after flushing this buffer. This buffers getIndex is updated. + * @return the total number of bytes written. + */ + int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException; + + + /* ------------------------------------------------------------ */ + /** + * @return The local IP address to which this EndPoint is bound, or null + * if this EndPoint does not represent a network connection. + */ + public String getLocalAddr(); + + /* ------------------------------------------------------------ */ + /** + * @return The local host name to which this EndPoint is bound, or null + * if this EndPoint does not represent a network connection. + */ + public String getLocalHost(); + + /* ------------------------------------------------------------ */ + /** + * @return The local port number on which this EndPoint is listening, or 0 + * if this EndPoint does not represent a network connection. + */ + public int getLocalPort(); + + /* ------------------------------------------------------------ */ + /** + * @return The remote IP address to which this EndPoint is connected, or null + * if this EndPoint does not represent a network connection. + */ + public String getRemoteAddr(); + + /* ------------------------------------------------------------ */ + /** + * @return The host name of the remote machine to which this EndPoint is connected, or null + * if this EndPoint does not represent a network connection. + */ + public String getRemoteHost(); + + /* ------------------------------------------------------------ */ + /** + * @return The remote port number to which this EndPoint is connected, or 0 + * if this EndPoint does not represent a network connection. + */ + public int getRemotePort(); + + + /* ------------------------------------------------------------ */ + public boolean isBlocking(); + + /* ------------------------------------------------------------ */ + public boolean isBufferred(); + + /* ------------------------------------------------------------ */ + public boolean blockReadable(long millisecs) throws IOException; + + /* ------------------------------------------------------------ */ + public boolean blockWritable(long millisecs) throws IOException; + + /* ------------------------------------------------------------ */ + public boolean isOpen(); + + /* ------------------------------------------------------------ */ + /** + * @return The underlying transport object (socket, channel, etc.) + */ + public Object getTransport(); + + /* ------------------------------------------------------------ */ + /** + * @return True if the endpoint has some buffered input data + */ + public boolean isBufferingInput(); + + /* ------------------------------------------------------------ */ + /** + * @return True if the endpoint has some buffered output data + */ + public boolean isBufferingOutput(); + + /* ------------------------------------------------------------ */ + /** Flush any buffered output. + * May fail to write all data if endpoint is non-blocking + * @throws IOException + */ + public void flush() throws IOException; + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EofException.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EofException.java new file mode 100644 index 00000000000..9c864aa078a --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EofException.java @@ -0,0 +1,34 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.io.EOFException; + +public class EofException extends EOFException +{ + public EofException() + { + } + + public EofException(String reason) + { + super(reason); + } + + public EofException(Throwable th) + { + if (th!=null) + initCause(th); + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/HttpException.java b/jetty-io/src/main/java/org/eclipse/jetty/io/HttpException.java new file mode 100644 index 00000000000..dc26ad75c5b --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/HttpException.java @@ -0,0 +1,88 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.io.IOException; + +public class HttpException extends IOException +{ + int _status; + String _reason; + + /* ------------------------------------------------------------ */ + public HttpException(int status) + { + _status=status; + _reason=null; + } + + /* ------------------------------------------------------------ */ + public HttpException(int status,String reason) + { + _status=status; + _reason=reason; + } + + /* ------------------------------------------------------------ */ + public HttpException(int status,String reason, Throwable rootCause) + { + _status=status; + _reason=reason; + initCause(rootCause); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the reason. + */ + public String getReason() + { + return _reason; + } + + /* ------------------------------------------------------------ */ + /** + * @param reason The reason to set. + */ + public void setReason(String reason) + { + _reason = reason; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the status. + */ + public int getStatus() + { + return _status; + } + + /* ------------------------------------------------------------ */ + /** + * @param status The status to set. + */ + public void setStatus(int status) + { + _status = status; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return ("HttpException("+_status+","+_reason+","+super.getCause()+")"); + } + + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SimpleBuffers.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SimpleBuffers.java new file mode 100644 index 00000000000..b6d0e242657 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SimpleBuffers.java @@ -0,0 +1,74 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +/* ------------------------------------------------------------ */ +/** SimpleBuffers. + * Simple implementation of Buffers holder. + * + * + */ +public class SimpleBuffers implements Buffers +{ + Buffer[] _buffers; + + /* ------------------------------------------------------------ */ + /** + * + */ + public SimpleBuffers(Buffer[] buffers) + { + _buffers=buffers; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.Buffers#getBuffer(boolean) + */ + public Buffer getBuffer(int size) + { + if (_buffers!=null) + { + for (int i=0;i<_buffers.length;i++) + { + if (_buffers[i]!=null && _buffers[i].capacity()==size) + { + Buffer b=_buffers[i]; + _buffers[i]=null; + return b; + } + } + } + return new ByteArrayBuffer(size); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.Buffers#returnBuffer(org.eclipse.io.Buffer) + */ + public void returnBuffer(Buffer buffer) + { + buffer.clear(); + if (_buffers!=null) + { + for (int i=0;i<_buffers.length;i++) + { + if (_buffers[i]==null) + _buffers[i]=buffer; + } + } + } + + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/View.java b/jetty-io/src/main/java/org/eclipse/jetty/io/View.java new file mode 100644 index 00000000000..d1418bb6875 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/View.java @@ -0,0 +1,235 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +/** + * A View on another buffer. Allows operations that do not change the _content or + * indexes of the backing buffer. + * + * + * + */ +public class View extends AbstractBuffer +{ + Buffer _buffer; + + /** + * @param buffer The Buffer on which we are presenting a View. + * @param mark The initial value of the {@link Buffer#markIndex mark index} + * @param get The initial value of the {@link Buffer#getIndex get index} + * @param put The initial value of the {@link Buffer#putIndex put index} + * @param access The access level - one of the constants from {@link Buffer}. + */ + public View(Buffer buffer, int mark, int get, int put,int access) + { + super(READWRITE,!buffer.isImmutable()); + _buffer=buffer.buffer(); + setPutIndex(put); + setGetIndex(get); + setMarkIndex(mark); + _access=access; + } + + public View(Buffer buffer) + { + super(READWRITE,!buffer.isImmutable()); + _buffer=buffer.buffer(); + setPutIndex(buffer.putIndex()); + setGetIndex(buffer.getIndex()); + setMarkIndex(buffer.markIndex()); + _access=buffer.isReadOnly()?READONLY:READWRITE; + } + + public View() + { + super(READWRITE,true); + } + + /** + * Update view to buffer + */ + public void update(Buffer buffer) + { + _access=READWRITE; + _buffer=buffer.buffer(); + setGetIndex(0); + setPutIndex(buffer.putIndex()); + setGetIndex(buffer.getIndex()); + setMarkIndex(buffer.markIndex()); + _access=buffer.isReadOnly()?READONLY:READWRITE; + } + + public void update(int get, int put) + { + int a=_access; + _access=READWRITE; + setGetIndex(0); + setPutIndex(put); + setGetIndex(get); + setMarkIndex(-1); + _access=a; + } + + /** + * @return The {@link Buffer#array()} from the underlying buffer. + */ + public byte[] array() + { + return _buffer.array(); + } + + /** + * @return The {@link Buffer#buffer()} from the underlying buffer. + */ + public Buffer buffer() + { + return _buffer.buffer(); + } + + /** + * @return The {@link Buffer#capacity} of the underlying buffer. + */ + public int capacity() + { + return _buffer.capacity(); + } + + /** + * + */ + public void clear() + { + setMarkIndex(-1); + setGetIndex(0); + setPutIndex(_buffer.getIndex()); + setGetIndex(_buffer.getIndex()); + } + + /** + * + */ + public void compact() + { + // TODO + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) + { + return this==obj ||((obj instanceof Buffer)&&((Buffer)obj).equals(this)) || super.equals(obj); + } + + /** + * @return Whether the underlying buffer is {@link Buffer#isReadOnly read only} + */ + public boolean isReadOnly() + { + return _buffer.isReadOnly(); + } + + /** + * @return Whether the underlying buffer is {@link Buffer#isVolatile volatile} + */ + public boolean isVolatile() + { + return true; + } + + /** + * @return The result of calling {@link Buffer#peek(int)} on the underlying buffer + */ + public byte peek(int index) + { + return _buffer.peek(index); + } + + /** + * @return The result of calling {@link Buffer#peek(int, byte[], int, int)} on the underlying buffer + */ + public int peek(int index, byte[] b, int offset, int length) + { + return _buffer.peek(index,b,offset,length); + } + + /** + * @return The result of calling {@link Buffer#peek(int, int)} on the underlying buffer + */ + public Buffer peek(int index, int length) + { + return _buffer.peek(index, length); + } + + /** + * @param index + * @param src + */ + public int poke(int index, Buffer src) + { + return _buffer.poke(index,src); + } + + /** + * @param index + * @param b + */ + public void poke(int index, byte b) + { + _buffer.poke(index,b); + } + + /** + * @param index + * @param b + * @param offset + * @param length + */ + public int poke(int index, byte[] b, int offset, int length) + { + return _buffer.poke(index,b,offset,length); + } + + public String toString() + { + if (_buffer==null) + return "INVALID"; + return super.toString(); + } + + public static class CaseInsensitive extends View implements Buffer.CaseInsensitve + { + public CaseInsensitive() + { + super(); + } + + public CaseInsensitive(Buffer buffer, int mark, int get, int put, int access) + { + super(buffer,mark,get,put,access); + } + + public CaseInsensitive(Buffer buffer) + { + super(buffer); + } + + public boolean equals(Object obj) + { + return this==obj ||((obj instanceof Buffer)&&((Buffer)obj).equalsIgnoreCase(this)) || super.equals(obj); + } + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java new file mode 100644 index 00000000000..74d4835c3f8 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java @@ -0,0 +1,91 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; + + +/* ------------------------------------------------------------ */ +/** Wrap a Writer as an OutputStream. + * When all you have is a Writer and only an OutputStream will do. + * Try not to use this as it indicates that your design is a dogs + * breakfast (JSP made me write it). + * + */ +public class WriterOutputStream extends OutputStream +{ + protected Writer _writer; + protected String _encoding; + private byte[] _buf=new byte[1]; + + /* ------------------------------------------------------------ */ + public WriterOutputStream(Writer writer, String encoding) + { + _writer=writer; + _encoding=encoding; + } + + /* ------------------------------------------------------------ */ + public WriterOutputStream(Writer writer) + { + _writer=writer; + } + + /* ------------------------------------------------------------ */ + public void close() + throws IOException + { + _writer.close(); + _writer=null; + _encoding=null; + } + + /* ------------------------------------------------------------ */ + public void flush() + throws IOException + { + _writer.flush(); + } + + /* ------------------------------------------------------------ */ + public void write(byte[] b) + throws IOException + { + if (_encoding==null) + _writer.write(new String(b)); + else + _writer.write(new String(b,_encoding)); + } + + /* ------------------------------------------------------------ */ + public void write(byte[] b, int off, int len) + throws IOException + { + if (_encoding==null) + _writer.write(new String(b,off,len)); + else + _writer.write(new String(b,off,len,_encoding)); + } + + /* ------------------------------------------------------------ */ + public synchronized void write(int b) + throws IOException + { + _buf[0]=(byte)b; + write(_buf); + } +} + diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java new file mode 100644 index 00000000000..77fb4d25665 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java @@ -0,0 +1,171 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io.bio; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * + * + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class SocketEndPoint extends StreamEndPoint +{ + Socket _socket; + InetSocketAddress _local; + InetSocketAddress _remote; + + /** + * + */ + public SocketEndPoint(Socket socket) + throws IOException + { + super(socket.getInputStream(),socket.getOutputStream()); + _socket=socket; + } + + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#isClosed() + */ + public boolean isOpen() + { + return super.isOpen() && _socket!=null && !_socket.isClosed() && !_socket.isInputShutdown() && !_socket.isOutputShutdown(); + } + + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#close() + */ + public void close() throws IOException + { + if (!_socket.isClosed() && !_socket.isOutputShutdown()) + { + try + { + _socket.shutdownOutput(); + } + catch(IOException e) + { + Log.ignore(e); + } + catch(UnsupportedOperationException e) + { + Log.ignore(e); + } + } + _socket.close(); + _in=null; + _out=null; + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalAddr() + */ + public String getLocalAddr() + { + if (_local==null) + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + + if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) + return StringUtil.ALL_INTERFACES; + + return _local.getAddress().getHostAddress(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalHost() + */ + public String getLocalHost() + { + if (_local==null) + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + + if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) + return StringUtil.ALL_INTERFACES; + + return _local.getAddress().getCanonicalHostName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalPort() + */ + public int getLocalPort() + { + if (_local==null) + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + if (_local==null) + return -1; + return _local.getPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteAddr() + */ + public String getRemoteAddr() + { + if (_remote==null) + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + if (_remote==null) + return null; + InetAddress addr = _remote.getAddress(); + return ( addr == null ? null : addr.getHostAddress() ); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteHost() + */ + public String getRemoteHost() + { + if (_remote==null) + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + if (_remote==null) + return null; + return _remote.getAddress().getCanonicalHostName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemotePort() + */ + public int getRemotePort() + { + if (_remote==null) + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + if (_remote==null) + return -1; + return _remote.getPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getConnection() + */ + public Object getTransport() + { + return _socket; + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java new file mode 100644 index 00000000000..f53f78db1e5 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java @@ -0,0 +1,288 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.io.bio; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EndPoint; + +/** + * + * + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class StreamEndPoint implements EndPoint +{ + InputStream _in; + OutputStream _out; + + /** + * + */ + public StreamEndPoint(InputStream in, OutputStream out) + { + _in=in; + _out=out; + } + + public boolean isBlocking() + { + return true; + } + + public boolean blockReadable(long millisecs) throws IOException + { + return true; + } + + public boolean blockWritable(long millisecs) throws IOException + { + return true; + } + + /* + * @see org.eclipse.io.BufferIO#isOpen() + */ + public boolean isOpen() + { + return _in!=null; + } + + /* + * @see org.eclipse.io.BufferIO#isOpen() + */ + public final boolean isClosed() + { + return !isOpen(); + } + + /* + * @see org.eclipse.io.BufferIO#close() + */ + public void close() throws IOException + { + if (_in!=null) + _in.close(); + _in=null; + if (_out!=null) + _out.close(); + _out=null; + } + + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#fill(org.eclipse.io.Buffer) + */ + public int fill(Buffer buffer) throws IOException + { + // TODO handle null array() + if (_in==null) + return 0; + + int space=buffer.space(); + if (space<=0) + { + if (buffer.hasContent()) + return 0; + throw new IOException("FULL"); + } + + int len = buffer.readFrom(_in,space); + + return len; + } + + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#flush(org.eclipse.io.Buffer) + */ + public int flush(Buffer buffer) throws IOException + { + // TODO handle null array() + if (_out==null) + return -1; + int length=buffer.length(); + if (length>0) + buffer.writeTo(_out); + buffer.clear(); + return length; + } + + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int len=0; + + // TODO consider copying buffer and trailer into header if there is space. + + + if (header!=null) + { + int tw=header.length(); + if (tw>0) + { + int f=flush(header); + len=f; + if (f0) + { + int f=flush(buffer); + if (f<0) + return len>0?len:f; + len+=f; + if (f0) + { + int f=flush(trailer); + if (f<0) + return len>0?len:f; + len+=f; + } + } + return len; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalAddr() + */ + public String getLocalAddr() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalHost() + */ + public String getLocalHost() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalPort() + */ + public int getLocalPort() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteAddr() + */ + public String getRemoteAddr() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteHost() + */ + public String getRemoteHost() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemotePort() + */ + public int getRemotePort() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getConnection() + */ + public Object getTransport() + { + return null; + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() + { + return _in; + } + + /* ------------------------------------------------------------ */ + public void setInputStream(InputStream in) + { + _in=in; + } + + /* ------------------------------------------------------------ */ + public OutputStream getOutputStream() + { + return _out; + } + + /* ------------------------------------------------------------ */ + public void setOutputStream(OutputStream out) + { + _out=out; + } + + + /* ------------------------------------------------------------ */ + public void flush() + throws IOException + { + _out.flush(); + } + + /* ------------------------------------------------------------ */ + public boolean isBufferingInput() + { + return false; + } + + /* ------------------------------------------------------------ */ + public boolean isBufferingOutput() + { + return false; + } + + /* ------------------------------------------------------------ */ + public boolean isBufferred() + { + return false; + } + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java new file mode 100644 index 00000000000..d21a35c6aa9 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java @@ -0,0 +1,87 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io.bio; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.eclipse.jetty.util.StringUtil; + +/** + * + * + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class StringEndPoint extends StreamEndPoint +{ + String _encoding=StringUtil.__UTF8; + ByteArrayInputStream _bin = new ByteArrayInputStream(new byte[0]); + ByteArrayOutputStream _bout = new ByteArrayOutputStream(); + + public StringEndPoint() + { + super(null,null); + _in=_bin; + _out=_bout; + } + + public StringEndPoint(String encoding) + throws IOException + { + this(); + if (encoding!=null) + _encoding=encoding; + } + + public void setInput(String s) + { + try + { + byte[] bytes = s.getBytes(_encoding); + _bin=new ByteArrayInputStream(bytes); + _in=_bin; + _bout = new ByteArrayOutputStream(); + _out=_bout; + } + catch(Exception e) + { + throw new IllegalStateException(e.toString()); + } + } + + public String getOutput() + { + try + { + String s = new String(_bout.toByteArray(),_encoding); + _bout.reset(); + return s; + } + catch(Exception e) + { + e.printStackTrace(); + throw new IllegalStateException(_encoding+": "+e.toString()); + } + } + + /** + * @return true if there are bytes remaining to be read from the encoded input + */ + public boolean hasMore() + { + return _bin.available()>0; + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java new file mode 100644 index 00000000000..0d01ad671fb --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java @@ -0,0 +1,435 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; + + +/** + * + * + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class ChannelEndPoint implements EndPoint +{ + protected ByteChannel _channel; + protected ByteBuffer[] _gather2=new ByteBuffer[2]; + protected Socket _socket; + protected InetSocketAddress _local; + protected InetSocketAddress _remote; + + /** + * + */ + public ChannelEndPoint(ByteChannel channel) + { + super(); + this._channel = channel; + if (channel instanceof SocketChannel) + _socket=((SocketChannel)channel).socket(); + } + + public boolean isBlocking() + { + if (_channel instanceof SelectableChannel) + return ((SelectableChannel)_channel).isBlocking(); + return true; + } + + public boolean blockReadable(long millisecs) throws IOException + { + return true; + } + + public boolean blockWritable(long millisecs) throws IOException + { + return true; + } + + /* + * @see org.eclipse.io.EndPoint#isOpen() + */ + public boolean isOpen() + { + return _channel.isOpen(); + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#close() + */ + public void close() throws IOException + { + if (_channel.isOpen()) + { + try + { + if (_channel instanceof SocketChannel) + { + // TODO - is this really required? + Socket socket= ((SocketChannel)_channel).socket(); + if (!socket.isClosed()&&!socket.isOutputShutdown()) + socket.shutdownOutput(); + } + } + catch(IOException e) + { + Log.ignore(e); + } + catch(UnsupportedOperationException e) + { + Log.ignore(e); + } + finally + { + _channel.close(); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer) + */ + public int fill(Buffer buffer) throws IOException + { + Buffer buf = buffer.buffer(); + int len=0; + if (buf instanceof NIOBuffer) + { + NIOBuffer nbuf = (NIOBuffer)buf; + ByteBuffer bbuf=nbuf.getByteBuffer(); + synchronized(nbuf) + { + try + { + bbuf.position(buffer.putIndex()); + len=_channel.read(bbuf); + if (len<0) + _channel.close(); + } + finally + { + buffer.setPutIndex(bbuf.position()); + bbuf.position(0); + } + } + } + else + { + throw new IOException("Not Implemented"); + } + + return len; + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer) + */ + public int flush(Buffer buffer) throws IOException + { + Buffer buf = buffer.buffer(); + int len=0; + if (buf instanceof NIOBuffer) + { + NIOBuffer nbuf = (NIOBuffer)buf; + ByteBuffer bbuf=nbuf.getByteBuffer(); + + // TODO synchronize + synchronized(bbuf) + { + try + { + bbuf.position(buffer.getIndex()); + bbuf.limit(buffer.putIndex()); + len=_channel.write(bbuf); + } + finally + { + if (len>0) + buffer.skip(len); + bbuf.position(0); + bbuf.limit(bbuf.capacity()); + } + } + } + else if (buf instanceof RandomAccessFileBuffer) + { + len = buffer.length(); + ((RandomAccessFileBuffer)buf).writeTo(_channel,buffer.getIndex(),buffer.length()); + if (len>0) + buffer.skip(len); + } + else if (buffer.array()!=null) + { + ByteBuffer b = ByteBuffer.wrap(buffer.array(), buffer.getIndex(), buffer.length()); + len=_channel.write(b); + if (len>0) + buffer.skip(len); + } + else + { + throw new IOException("Not Implemented"); + } + return len; + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int length=0; + + Buffer buf0 = header==null?null:header.buffer(); + Buffer buf1 = buffer==null?null:buffer.buffer(); + + if (_channel instanceof GatheringByteChannel && + header!=null && header.length()!=0 && buf0 instanceof NIOBuffer && + buffer!=null && buffer.length()!=0 && buf1 instanceof NIOBuffer) + { + NIOBuffer nbuf0 = (NIOBuffer)buf0; + ByteBuffer bbuf0=nbuf0.getByteBuffer(); + NIOBuffer nbuf1 = (NIOBuffer)buf1; + ByteBuffer bbuf1=nbuf1.getByteBuffer(); + + synchronized(this) + { + // We must sync because buffers may be shared (eg nbuf1 is likely to be cached content). + synchronized(bbuf0) + { + synchronized(bbuf1) + { + try + { + // Adjust position indexs of buf0 and buf1 + bbuf0.position(header.getIndex()); + bbuf0.limit(header.putIndex()); + bbuf1.position(buffer.getIndex()); + bbuf1.limit(buffer.putIndex()); + + _gather2[0]=bbuf0; + _gather2[1]=bbuf1; + + // do the gathering write. + length=(int)((GatheringByteChannel)_channel).write(_gather2); + + int hl=header.length(); + if (length>hl) + { + header.clear(); + buffer.skip(length-hl); + } + else if (length>0) + { + header.skip(length); + } + + } + finally + { + // adjust buffer 0 and 1 + if (!header.isImmutable()) + header.setGetIndex(bbuf0.position()); + if (!buffer.isImmutable()) + buffer.setGetIndex(bbuf1.position()); + + bbuf0.position(0); + bbuf1.position(0); + bbuf0.limit(bbuf0.capacity()); + bbuf1.limit(bbuf1.capacity()); + } + } + } + } + } + else + { + // TODO - consider copying buffers buffer and trailer into header if there is space! + + // flush header + if (header!=null && header.length()>0) + length=flush(header); + + // flush buffer + if ((header==null || header.length()==0) && + buffer!=null && buffer.length()>0) + length+=flush(buffer); + + // flush trailer + if ((header==null || header.length()==0) && + (buffer==null || buffer.length()==0) && + trailer!=null && trailer.length()>0) + length+=flush(trailer); + } + + return length; + } + + /** + * @return Returns the channel. + */ + public ByteChannel getChannel() + { + return _channel; + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalAddr() + */ + public String getLocalAddr() + { + if (_socket==null) + return null; + + if (_local==null) + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + + if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) + return StringUtil.ALL_INTERFACES; + + return _local.getAddress().getHostAddress(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalHost() + */ + public String getLocalHost() + { + if (_socket==null) + return null; + + if (_local==null) + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + + if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) + return StringUtil.ALL_INTERFACES; + + return _local.getAddress().getCanonicalHostName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalPort() + */ + public int getLocalPort() + { + if (_socket==null) + return 0; + + if (_local==null) + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + if (_local==null) + return -1; + return _local.getPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteAddr() + */ + public String getRemoteAddr() + { + if (_socket==null) + return null; + + if (_remote==null) + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + + if (_remote==null) + return null; + return _remote.getAddress().getHostAddress(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteHost() + */ + public String getRemoteHost() + { + if (_socket==null) + return null; + + if (_remote==null) + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + + if (_remote==null) + return null; + return _remote.getAddress().getCanonicalHostName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemotePort() + */ + public int getRemotePort() + { + if (_socket==null) + return 0; + + if (_remote==null) + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + + if (_remote==null) + return -1; + return _remote==null?-1:_remote.getPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getConnection() + */ + public Object getTransport() + { + return _channel; + } + + /* ------------------------------------------------------------ */ + public void flush() + throws IOException + { + } + + /* ------------------------------------------------------------ */ + public boolean isBufferingInput() + { + return false; + } + + /* ------------------------------------------------------------ */ + public boolean isBufferingOutput() + { + return false; + } + + /* ------------------------------------------------------------ */ + public boolean isBufferred() + { + return false; + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java new file mode 100644 index 00000000000..a40334aaeec --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java @@ -0,0 +1,331 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io.nio; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +import org.eclipse.jetty.io.AbstractBuffer; +import org.eclipse.jetty.io.Buffer; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class DirectNIOBuffer extends AbstractBuffer implements NIOBuffer +{ + protected ByteBuffer _buf; + private ReadableByteChannel _in; + private InputStream _inStream; + private WritableByteChannel _out; + private OutputStream _outStream; + + public DirectNIOBuffer(int size) + { + super(READWRITE,NON_VOLATILE); + _buf = ByteBuffer.allocateDirect(size); + _buf.position(0); + _buf.limit(_buf.capacity()); + } + + public DirectNIOBuffer(ByteBuffer buffer,boolean immutable) + { + super(immutable?IMMUTABLE:READWRITE,NON_VOLATILE); + if (!buffer.isDirect()) + throw new IllegalArgumentException(); + _buf = buffer; + setGetIndex(buffer.position()); + setPutIndex(buffer.limit()); + } + + /** + * @param file + */ + public DirectNIOBuffer(File file) throws IOException + { + super(READONLY,NON_VOLATILE); + FileInputStream fis = new FileInputStream(file); + FileChannel fc = fis.getChannel(); + _buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); + setGetIndex(0); + setPutIndex((int)file.length()); + _access=IMMUTABLE; + } + + /* ------------------------------------------------------------ */ + public boolean isDirect() + { + return true; + } + + /* ------------------------------------------------------------ */ + public byte[] array() + { + return null; + } + + /* ------------------------------------------------------------ */ + public int capacity() + { + return _buf.capacity(); + } + + /* ------------------------------------------------------------ */ + public byte peek(int position) + { + return _buf.get(position); + } + + public int peek(int index, byte[] b, int offset, int length) + { + int l = length; + if (index+l > capacity()) + { + l=capacity()-index; + if (l==0) + return -1; + } + + if (l < 0) + return -1; + try + { + _buf.position(index); + _buf.get(b,offset,l); + } + finally + { + _buf.position(0); + } + + return l; + } + + public void poke(int index, byte b) + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0"); + if (index > capacity()) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + _buf.put(index,b); + } + + public int poke(int index, Buffer src) + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + + byte[] array=src.array(); + if (array!=null) + { + int length = poke(index,array,src.getIndex(),src.length()); + return length; + } + else + { + Buffer src_buf=src.buffer(); + if (src_buf instanceof DirectNIOBuffer) + { + ByteBuffer src_bytebuf = ((DirectNIOBuffer)src_buf)._buf; + if (src_bytebuf==_buf) + src_bytebuf=_buf.duplicate(); + try + { + _buf.position(index); + int space = _buf.remaining(); + + int length=src.length(); + if (length>space) + length=space; + + src_bytebuf.position(src.getIndex()); + src_bytebuf.limit(src.getIndex()+length); + + _buf.put(src_bytebuf); + return length; + } + finally + { + _buf.position(0); + src_bytebuf.limit(src_bytebuf.capacity()); + src_bytebuf.position(0); + } + } + else + return super.poke(index,src); + } + } + + public int poke(int index, byte[] b, int offset, int length) + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + + if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0"); + + if (index + length > capacity()) + { + length=capacity()-index; + if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + } + + try + { + _buf.position(index); + + int space=_buf.remaining(); + + if (length>space) + length=space; + if (length>0) + _buf.put(b,offset,length); + return length; + } + finally + { + _buf.position(0); + } + } + + /* ------------------------------------------------------------ */ + public ByteBuffer getByteBuffer() + { + return _buf; + } + + /* ------------------------------------------------------------ */ + public int readFrom(InputStream in, int max) throws IOException + { + if (_in==null || !_in.isOpen() || in!=_inStream) + { + _in=Channels.newChannel(in); + _inStream=in; + } + + if (max<0 || max>space()) + max=space(); + int p = putIndex(); + + try + { + int len=0, total=0, available=max; + int loop=0; + while (total0) + { + p += len; + total += len; + available -= len; + setPutIndex(p); + loop=0; + } + else if (loop++>1) + break; + if (in.available()<=0) + break; + } + if (len<0 && total==0) + return -1; + return total; + + } + catch(IOException e) + { + _in=null; + _inStream=in; + throw e; + } + finally + { + if (_in!=null && !_in.isOpen()) + { + _in=null; + _inStream=in; + } + _buf.position(0); + _buf.limit(_buf.capacity()); + } + } + + /* ------------------------------------------------------------ */ + public void writeTo(OutputStream out) throws IOException + { + if (_out==null || !_out.isOpen() || _out!=_outStream) + { + _out=Channels.newChannel(out); + _outStream=out; + } + + synchronized (_buf) + { + try + { + int loop=0; + while(hasContent() && _out.isOpen()) + { + _buf.position(getIndex()); + _buf.limit(putIndex()); + int len=_out.write(_buf); + if (len<0) + break; + else if (len>0) + { + skip(len); + loop=0; + } + else if (loop++>1) + break; + } + + } + catch(IOException e) + { + _out=null; + _outStream=null; + throw e; + } + finally + { + if (_out!=null && !_out.isOpen()) + { + _out=null; + _outStream=null; + } + _buf.position(0); + _buf.limit(_buf.capacity()); + } + } + } + + + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java new file mode 100644 index 00000000000..9dec71a5698 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java @@ -0,0 +1,56 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.io.nio; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.io.ByteArrayBuffer; + +public class IndirectNIOBuffer extends ByteArrayBuffer implements NIOBuffer +{ + protected ByteBuffer _buf; + + /* ------------------------------------------------------------ */ + public IndirectNIOBuffer(int size) + { + super(READWRITE,NON_VOLATILE); + _buf = ByteBuffer.allocate(size); + _buf.position(0); + _buf.limit(_buf.capacity()); + _bytes=_buf.array(); + } + + /* ------------------------------------------------------------ */ + public IndirectNIOBuffer(ByteBuffer buffer,boolean immutable) + { + super(immutable?IMMUTABLE:READWRITE,NON_VOLATILE); + if (buffer.isDirect()) + throw new IllegalArgumentException(); + _buf = buffer; + setGetIndex(buffer.position()); + setPutIndex(buffer.limit()); + _bytes=_buf.array(); + } + + /* ------------------------------------------------------------ */ + public ByteBuffer getByteBuffer() + { + return _buf; + } + + /* ------------------------------------------------------------ */ + public boolean isDirect() + { + return false; + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NIOBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NIOBuffer.java new file mode 100644 index 00000000000..99b939687b9 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NIOBuffer.java @@ -0,0 +1,32 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io.nio; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.io.Buffer; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public interface NIOBuffer extends Buffer +{ + /* ------------------------------------------------------------ */ + public ByteBuffer getByteBuffer(); + + /* ------------------------------------------------------------ */ + public boolean isDirect(); +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java new file mode 100644 index 00000000000..94e5883855e --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java @@ -0,0 +1,186 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.io.nio; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; + +import org.eclipse.jetty.io.AbstractBuffer; +import org.eclipse.jetty.io.Buffer; + +public class RandomAccessFileBuffer extends AbstractBuffer implements Buffer +{ + RandomAccessFile _file; + FileChannel _channel; + int _capacity=Integer.MAX_VALUE; + + public RandomAccessFileBuffer(File file) + throws FileNotFoundException + { + super(READWRITE,true); + assert file.length()<=Integer.MAX_VALUE; + _file = new RandomAccessFile(file,"rw"); + _channel=_file.getChannel(); + setGetIndex(0); + setPutIndex((int)file.length()); + } + + public RandomAccessFileBuffer(File file,int capacity) + throws FileNotFoundException + { + super(READWRITE,true); + assert capacity>=file.length(); + assert file.length()<=Integer.MAX_VALUE; + _capacity=capacity; + _file = new RandomAccessFile(file,"rw"); + _channel=_file.getChannel(); + setGetIndex(0); + setPutIndex((int)file.length()); + } + + public RandomAccessFileBuffer(File file,int capacity,int access) + throws FileNotFoundException + { + super(access,true); + assert capacity>=file.length(); + assert file.length()<=Integer.MAX_VALUE; + _capacity=capacity; + _file = new RandomAccessFile(file,access==READWRITE?"rw":"r"); + _channel=_file.getChannel(); + setGetIndex(0); + setPutIndex((int)file.length()); + } + + public byte[] array() + { + return null; + } + + public int capacity() + { + return _capacity; + } + + public void clear() + { + try + { + synchronized (_file) + { + super.clear(); + _file.setLength(0); + } + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + + public byte peek() + { + synchronized (_file) + { + try + { + if (_get!=_file.getFilePointer()) + _file.seek(_get); + return _file.readByte(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public byte peek(int index) + { + synchronized (_file) + { + try + { + _file.seek(index); + return _file.readByte(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public int peek(int index, byte[] b, int offset, int length) + { + synchronized (_file) + { + try + { + _file.seek(index); + return _file.read(b,offset,length); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public void poke(int index, byte b) + { + synchronized (_file) + { + try + { + _file.seek(index); + _file.writeByte(b); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public int poke(int index, byte[] b, int offset, int length) + { + synchronized (_file) + { + try + { + _file.seek(index); + _file.write(b,offset,length); + return length; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public int writeTo(WritableByteChannel channel,int index, int length) + throws IOException + { + synchronized (_file) + { + return (int)_channel.transferTo(index,length,channel); + } + } + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java new file mode 100644 index 00000000000..8b5ff04a5b2 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java @@ -0,0 +1,516 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io.nio; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.thread.Timeout; + +/* ------------------------------------------------------------ */ +/** + * An Endpoint that can be scheduled by {@link SelectorManager}. + * + * + * + */ +public class SelectChannelEndPoint extends ChannelEndPoint implements Runnable, AsyncEndPoint +{ + protected SelectorManager _manager; + protected SelectorManager.SelectSet _selectSet; + protected boolean _dispatched = false; + protected boolean _redispatched = false; + protected boolean _writable = true; + protected SelectionKey _key; + protected int _interestOps; + protected boolean _readBlocked; + protected boolean _writeBlocked; + protected Connection _connection; + private boolean _open; + private Timeout.Task _idleTask = new IdleTask(); + + /* ------------------------------------------------------------ */ + public Connection getConnection() + { + return _connection; + } + + /* ------------------------------------------------------------ */ + public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) + { + super(channel); + + _manager = selectSet.getManager(); + _selectSet = selectSet; + _connection = _manager.newConnection(channel,this); + _dispatched = false; + _redispatched = false; + _open=true; + _manager.endPointOpened(this); + + _key = key; + scheduleIdle(); + } + + /* ------------------------------------------------------------ */ + /** Called by selectSet to schedule handling + * + */ + public void schedule() throws IOException + { + // If threads are blocked on this + synchronized (this) + { + // If there is no key, then do nothing + if (_key == null || !_key.isValid()) + { + _readBlocked=false; + _writeBlocked=false; + this.notifyAll(); + return; + } + + // If there are threads dispatched reading and writing + if (_readBlocked || _writeBlocked) + { + // assert _dispatched; + if (_readBlocked && _key.isReadable()) + _readBlocked=false; + if (_writeBlocked && _key.isWritable()) + _writeBlocked=false; + + // wake them up is as good as a dispatched. + this.notifyAll(); + + // we are not interested in further selecting + _key.interestOps(0); + return; + } + + // Otherwise if we are still dispatched + if (!isReadyForDispatch()) + { + // we are not interested in further selecting + _key.interestOps(0); + return; + } + + // Remove writeable op + if ((_key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && (_key.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) + { + // Remove writeable op + _interestOps = _key.interestOps() & ~SelectionKey.OP_WRITE; + _key.interestOps(_interestOps); + _writable = true; // Once writable is in ops, only removed with dispatch. + } + + if (!dispatch()) + updateKey(); + } + } + + /* ------------------------------------------------------------ */ + public boolean dispatch() + { + synchronized(this) + { + if (_dispatched) + { + _redispatched=true; + return true; + } + + _dispatched = _manager.dispatch((Runnable)this); + if(!_dispatched) + Log.warn("Dispatched Failed!"); + return _dispatched; + } + } + + /* ------------------------------------------------------------ */ + /** + * Called when a dispatched thread is no longer handling the endpoint. The selection key + * operations are updated. + */ + protected boolean undispatch() + { + synchronized (this) + { + if (_redispatched) + { + _redispatched=false; + return false; + } + _dispatched = false; + updateKey(); + } + return true; + } + + /* ------------------------------------------------------------ */ + public void scheduleIdle() + { + _selectSet.scheduleIdle(_idleTask); + } + + /* ------------------------------------------------------------ */ + public void cancelIdle() + { + _selectSet.cancelIdle(_idleTask); + } + + /* ------------------------------------------------------------ */ + protected void idleExpired() + { + try + { + close(); + } + catch (IOException e) + { + Log.ignore(e); + } + } + + /* ------------------------------------------------------------ */ + /* + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int l = super.flush(header, buffer, trailer); + _writable = l!=0; + return l; + } + + /* ------------------------------------------------------------ */ + /* + */ + public int flush(Buffer buffer) throws IOException + { + int l = super.flush(buffer); + _writable = l!=0; + return l; + } + + /* ------------------------------------------------------------ */ + public boolean isReadyForDispatch() + { + return !_dispatched; + } + + /* ------------------------------------------------------------ */ + /* + * Allows thread to block waiting for further events. + */ + public boolean blockReadable(long timeoutMs) throws IOException + { + synchronized (this) + { + long start=_selectSet.getNow(); + try + { + _readBlocked=true; + while (isOpen() && _readBlocked) + { + try + { + updateKey(); + this.wait(timeoutMs); + + if (_readBlocked && timeoutMs<(_selectSet.getNow()-start)) + return false; + } + catch (InterruptedException e) + { + Log.warn(e); + } + } + } + finally + { + _readBlocked=false; + } + } + return true; + } + + /* ------------------------------------------------------------ */ + /* + * Allows thread to block waiting for further events. + */ + public boolean blockWritable(long timeoutMs) throws IOException + { + synchronized (this) + { + long start=_selectSet.getNow(); + try + { + _writeBlocked=true; + while (isOpen() && _writeBlocked) + { + try + { + updateKey(); + this.wait(timeoutMs); + + if (_writeBlocked && timeoutMs<(_selectSet.getNow()-start)) + return false; + } + catch (InterruptedException e) + { + Log.warn(e); + } + } + } + finally + { + _writeBlocked=false; + } + } + return true; + } + + /* ------------------------------------------------------------ */ + public void setWritable(boolean writable) + { + _writable=writable; + } + + /* ------------------------------------------------------------ */ + public void scheduleWrite() + { + _writable=false; + updateKey(); + } + + /* ------------------------------------------------------------ */ + /** + * Updates selection key. Adds operations types to the selection key as needed. No operations + * are removed as this is only done during dispatch. This method records the new key and + * schedules a call to doUpdateKey to do the keyChange + */ + private void updateKey() + { + synchronized (this) + { + + int ops=-1; + if (getChannel().isOpen()) + { + _interestOps = + ((!_dispatched || _readBlocked) ? SelectionKey.OP_READ : 0) + | ((!_writable || _writeBlocked) ? SelectionKey.OP_WRITE : 0); + try + { + ops = ((_key!=null && _key.isValid())?_key.interestOps():-1); + } + catch(Exception e) + { + _key=null; + Log.ignore(e); + } + } + if(_interestOps == ops && getChannel().isOpen()) + return; + + } + _selectSet.addChange(this); + _selectSet.wakeup(); + } + + /* ------------------------------------------------------------ */ + /** + * Synchronize the interestOps with the actual key. Call is scheduled by a call to updateKey + */ + void doUpdateKey() + { + synchronized (this) + { + if (getChannel().isOpen()) + { + if (_interestOps>0) + { + if (_key==null || !_key.isValid()) + { + SelectableChannel sc = (SelectableChannel)getChannel(); + if (sc.isRegistered()) + { + updateKey(); + } + else + { + try + { + _key=((SelectableChannel)getChannel()).register(_selectSet.getSelector(),_interestOps,this); + } + catch (Exception e) + { + Log.ignore(e); + if (_key!=null && _key.isValid()) + { + _key.cancel(); + } + cancelIdle(); + if (_open) + _manager.endPointClosed(this); + _open=false; + _key = null; + } + } + } + else + { + _key.interestOps(_interestOps); + } + } + else + { + if (_key.isValid()) + _key.interestOps(0); + else + _key=null; + } + } + else + { + if (_key!=null && _key.isValid()) + _key.cancel(); + + cancelIdle(); + if (_open) + _manager.endPointClosed(this); + _open=false; + _key = null; + } + } + } + + /* ------------------------------------------------------------ */ + /* + */ + public void run() + { + + boolean dispatched=true; + do + { + try + { + _connection.handle(); + } + catch (ClosedChannelException e) + { + Log.ignore(e); + } + catch (EofException e) + { + Log.debug("EOF", e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + catch (HttpException e) + { + Log.debug("BAD", e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + catch (Throwable e) + { + Log.warn("handle failed", e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + finally + { + dispatched=!undispatch(); + } + } + while(dispatched); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.nio.ChannelEndPoint#close() + */ + public void close() throws IOException + { + try + { + super.close(); + } + catch (IOException e) + { + Log.ignore(e); + } + finally + { + updateKey(); + } + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return "SCEP@" + hashCode() + "[d=" + _dispatched + ",io=" + + ((SelectionKey.OP_ACCEPT&_interestOps)!=0?"A":"")+ + ((SelectionKey.OP_CONNECT&_interestOps)!=0?"C":"")+ + ((SelectionKey.OP_READ&_interestOps)!=0?"R":"")+ + ((SelectionKey.OP_WRITE&_interestOps)!=0?"W":"")+ + ",w=" + _writable + ",b=" + _readBlocked + "|" + _writeBlocked + "]"; + } + + /* ------------------------------------------------------------ */ + public Timeout.Task getTimeoutTask() + { + return _idleTask; + } + + /* ------------------------------------------------------------ */ + public SelectSet getSelectSet() + { + return _selectSet; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class IdleTask extends Timeout.Task + { + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.Timeout.Task#expire() + */ + public void expired() + { + idleExpired(); + } + + public String toString() + { + return "TimeoutTask:" + SelectChannelEndPoint.this.toString(); + } + + } + +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java new file mode 100644 index 00000000000..9255de2ca43 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java @@ -0,0 +1,658 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io.nio; + +import java.io.IOException; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.thread.Timeout; + + +/* ------------------------------------------------------------ */ +/** + * The Selector Manager manages and number of SelectSets to allow + * NIO scheduling to scale to large numbers of connections. + * + * + * + */ +public abstract class SelectorManager extends AbstractLifeCycle +{ + private long _maxIdleTime; + private long _lowResourcesConnections; + private long _lowResourcesMaxIdleTime; + private transient SelectSet[] _selectSet; + private int _selectSets=1; + private volatile int _set; + + + /* ------------------------------------------------------------ */ + /** + * @param maxIdleTime The maximum period in milli seconds that a connection may be idle before it is closed. + * @see {@link #setLowResourcesMaxIdleTime(long)} + */ + public void setMaxIdleTime(long maxIdleTime) + { + _maxIdleTime=maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @param selectSets + */ + public void setSelectSets(int selectSets) + { + long lrc = _lowResourcesConnections * _selectSets; + _selectSets=selectSets; + _lowResourcesConnections=lrc/_selectSets; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public long getMaxIdleTime() + { + return _maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public int getSelectSets() + { + return _selectSets; + } + + /* ------------------------------------------------------------ */ + /** Register a channel + * @param channel + * @param att Attached Object + * @throws IOException + */ + public void register(SocketChannel channel, Object att) throws IOException + { + int s=_set++; + s=s%_selectSets; + SelectSet[] sets=_selectSet; + if (sets!=null) + { + SelectSet set=sets[s]; + set.addChange(channel,att); + set.wakeup(); + } + } + + /* ------------------------------------------------------------ */ + /** Register a serverchannel + * @param acceptChannel + * @return + * @throws IOException + */ + public void register(ServerSocketChannel acceptChannel) throws IOException + { + int s=_set++; + s=s%_selectSets; + SelectSet set=_selectSet[s]; + set.addChange(acceptChannel); + set.wakeup(); + } + + /* ------------------------------------------------------------ */ + /** + * @return the lowResourcesConnections + */ + public long getLowResourcesConnections() + { + return _lowResourcesConnections*_selectSets; + } + + /* ------------------------------------------------------------ */ + /** + * Set the number of connections, which if exceeded places this manager in low resources state. + * This is not an exact measure as the connection count is averaged over the select sets. + * @param lowResourcesConnections the number of connections + * @see {@link #setLowResourcesMaxIdleTime(long)} + */ + public void setLowResourcesConnections(long lowResourcesConnections) + { + _lowResourcesConnections=(lowResourcesConnections+_selectSets-1)/_selectSets; + } + + /* ------------------------------------------------------------ */ + /** + * @return the lowResourcesMaxIdleTime + */ + public long getLowResourcesMaxIdleTime() + { + return _lowResourcesMaxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when this SelectSet has more connections than {@link #getLowResourcesConnections()} + * @see {@link #setMaxIdleTime(long)} + */ + public void setLowResourcesMaxIdleTime(long lowResourcesMaxIdleTime) + { + _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @param acceptorID + * @throws IOException + */ + public void doSelect(int acceptorID) throws IOException + { + SelectSet[] sets= _selectSet; + if (sets!=null && sets.length>acceptorID && sets[acceptorID]!=null) + sets[acceptorID].doSelect(); + } + + /* ------------------------------------------------------------ */ + /** + * @param key + * @return + * @throws IOException + */ + protected abstract SocketChannel acceptChannel(SelectionKey key) throws IOException; + + /* ------------------------------------------------------------------------------- */ + public abstract boolean dispatch(Runnable task); + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + _selectSet = new SelectSet[_selectSets]; + for (int i=0;i<_selectSet.length;i++) + _selectSet[i]= new SelectSet(i); + + super.doStart(); + } + + + /* ------------------------------------------------------------------------------- */ + protected void doStop() throws Exception + { + SelectSet[] sets= _selectSet; + _selectSet=null; + if (sets!=null) + for (int i=0;i[] _changes; + private transient Timeout _idleTimeout; + private transient int _nextSet; + private transient Timeout _timeout; + private transient Selector _selector; + private transient int _setID; + private transient int _jvmBug; + private volatile boolean _selecting; + + /* ------------------------------------------------------------ */ + SelectSet(int acceptorID) throws Exception + { + _setID=acceptorID; + + _idleTimeout = new Timeout(this); + _idleTimeout.setDuration(getMaxIdleTime()); + _timeout = new Timeout(this); + _timeout.setDuration(0L); + + // create a selector; + _selector = Selector.open(); + _changes = new List[] {new ArrayList(),new ArrayList()}; + _change=0; + } + + /* ------------------------------------------------------------ */ + public void addChange(Object point) + { + synchronized (_changes) + { + _changes[_change].add(point); + if (point instanceof SocketChannel) + _changes[_change].add(null); + } + } + + /* ------------------------------------------------------------ */ + public void addChange(SocketChannel channel, Object att) + { + synchronized (_changes) + { + _changes[_change].add(channel); + _changes[_change].add(att); + } + } + + /* ------------------------------------------------------------ */ + public void cancelIdle(Timeout.Task task) + { + task.cancel(); + } + + /* ------------------------------------------------------------ */ + /** + * Select and dispatch tasks found from changes and the selector. + * + * @throws IOException + */ + public void doSelect() throws IOException + { + try + { + List changes; + final Selector selector; + synchronized (_changes) + { + changes=_changes[_change]; + _change=_change==0?1:0; + _selecting=true; + selector=_selector; + } + + + // Make any key changes required + for (int i = 0; i < changes.size(); i++) + { + try + { + Object o = changes.get(i); + if (o instanceof EndPoint) + { + // Update the operations for a key. + SelectChannelEndPoint endpoint = (SelectChannelEndPoint)o; + endpoint.doUpdateKey(); + } + else if (o instanceof Runnable) + { + dispatch((Runnable)o); + } + else if (o instanceof SocketChannel) + { + // finish accepting/connecting this connection + SocketChannel channel=(SocketChannel)o; + Object att = changes.get(++i); + + if (channel.isConnected()) + { + SelectionKey key = channel.register(selector,SelectionKey.OP_READ,att); + SelectChannelEndPoint endpoint = newEndPoint(channel,this,key); + key.attach(endpoint); + endpoint.schedule(); + } + else + { + channel.register(selector,SelectionKey.OP_CONNECT,att); + } + + } + else if (o instanceof ServerSocketChannel) + { + ServerSocketChannel channel = (ServerSocketChannel)o; + channel.register(getSelector(),SelectionKey.OP_ACCEPT); + } + else + throw new IllegalArgumentException(o.toString()); + } + catch (CancelledKeyException e) + { + if (isRunning()) + Log.warn(e); + else + Log.debug(e); + } + } + changes.clear(); + + long idle_next = 0; + long retry_next = 0; + long now=System.currentTimeMillis(); + synchronized (this) + { + _idleTimeout.setNow(now); + _timeout.setNow(now); + if (_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections) + _idleTimeout.setDuration(_lowResourcesMaxIdleTime); + else + _idleTimeout.setDuration(_maxIdleTime); + idle_next=_idleTimeout.getTimeToNext(); + retry_next=_timeout.getTimeToNext(); + } + + // workout how low to wait in select + long wait = 1000L; // not getMaxIdleTime() as the now value of the idle timers needs to be updated. + if (idle_next >= 0 && wait > idle_next) + wait = idle_next; + if (wait > 0 && retry_next >= 0 && wait > retry_next) + wait = retry_next; + + // Do the select. + if (wait > 0) + { + long before=now; + int selected=selector.select(wait); + now = System.currentTimeMillis(); + _idleTimeout.setNow(now); + _timeout.setNow(now); + + // Look for JVM bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933 + if (selected==0 && (now-before)5) + { + // Probably JVM BUG! + for (SelectionKey key: selector.keys()) + { + if (key.interestOps()==0 && key.isValid()) + key.cancel(); + } + selector.selectNow(); + } + } + else + _jvmBug=0; + } + else + { + selector.selectNow(); + _jvmBug=0; + } + + // have we been destroyed while sleeping + if (_selector==null || !selector.isOpen()) + return; + + // Look for things to do + for (SelectionKey key: selector.selectedKeys()) + { + try + { + if (!key.isValid()) + { + key.cancel(); + SelectChannelEndPoint endpoint = (SelectChannelEndPoint)key.attachment(); + if (endpoint != null) + endpoint.doUpdateKey(); + continue; + } + + Object att = key.attachment(); + if (att instanceof SelectChannelEndPoint) + { + ((SelectChannelEndPoint)att).schedule(); + } + else if (key.isAcceptable()) + { + SocketChannel channel = acceptChannel(key); + if (channel==null) + continue; + + channel.configureBlocking(false); + + // TODO make it reluctant to leave 0 + _nextSet=++_nextSet%_selectSet.length; + + // Is this for this selectset + if (_nextSet==_setID) + { + // bind connections to this select set. + SelectionKey cKey = channel.register(_selectSet[_nextSet].getSelector(), SelectionKey.OP_READ); + SelectChannelEndPoint endpoint=newEndPoint(channel,_selectSet[_nextSet],cKey); + cKey.attach(endpoint); + if (endpoint != null) + endpoint.schedule(); + } + else + { + // nope - give it to another. + _selectSet[_nextSet].addChange(channel); + _selectSet[_nextSet].wakeup(); + } + } + else if (key.isConnectable()) + { + // Complete a connection of a registered channel + SocketChannel channel = (SocketChannel)key.channel(); + boolean connected=false; + try + { + connected=channel.finishConnect(); + } + catch(Exception e) + { + connectionFailed(channel,e,att); + } + finally + { + if (connected) + { + key.interestOps(SelectionKey.OP_READ); + SelectChannelEndPoint endpoint = newEndPoint(channel,this,key); + key.attach(endpoint); + endpoint.schedule(); + } + else + { + key.cancel(); + } + } + } + else + { + // Wrap readable registered channel in an endpoint + SocketChannel channel = (SocketChannel)key.channel(); + SelectChannelEndPoint endpoint = newEndPoint(channel,this,key); + key.attach(endpoint); + if (key.isReadable()) + endpoint.schedule(); + } + key = null; + } + catch (CancelledKeyException e) + { + Log.ignore(e); + } + catch (Exception e) + { + if (isRunning()) + Log.warn(e); + else + Log.ignore(e); + + if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid()) + key.cancel(); + } + } + + // Everything always handled + selector.selectedKeys().clear(); + + // tick over the timers + _idleTimeout.tick(now); + _timeout.tick(now); + } + catch (CancelledKeyException e) + { + Log.ignore(e); + } + finally + { + _selecting=false; + } + } + + /* ------------------------------------------------------------ */ + public SelectorManager getManager() + { + return SelectorManager.this; + } + + /* ------------------------------------------------------------ */ + public long getNow() + { + return _idleTimeout.getNow(); + } + + /* ------------------------------------------------------------ */ + public void scheduleIdle(Timeout.Task task) + { + if (_idleTimeout.getDuration() <= 0) + return; + _idleTimeout.schedule(task); + } + + /* ------------------------------------------------------------ */ + public void scheduleTimeout(Timeout.Task task, long timeoutMs) + { + _timeout.schedule(task, timeoutMs); + } + + /* ------------------------------------------------------------ */ + public void cancelTimeout(Timeout.Task task) + { + task.cancel(); + } + + /* ------------------------------------------------------------ */ + public void wakeup() + { + Selector selector = _selector; + if (selector!=null) + selector.wakeup(); + } + + /* ------------------------------------------------------------ */ + Selector getSelector() + { + return _selector; + } + + /* ------------------------------------------------------------ */ + void stop() throws Exception + { + boolean selecting=true; + while(selecting) + { + wakeup(); + selecting=_selecting; + } + + ArrayList keys=new ArrayList(_selector.keys()); + Iterator iter =keys.iterator(); + + while (iter.hasNext()) + { + SelectionKey key = (SelectionKey)iter.next(); + if (key==null) + continue; + EndPoint endpoint = (EndPoint)key.attachment(); + if (endpoint!=null) + { + try + { + endpoint.close(); + } + catch(IOException e) + { + Log.ignore(e); + } + } + } + + synchronized (this) + { + selecting=_selecting; + while(selecting) + { + wakeup(); + selecting=_selecting; + } + + _idleTimeout.cancelAll(); + _timeout.cancelAll(); + try + { + if (_selector != null) + _selector.close(); + } + catch (IOException e) + { + Log.ignore(e); + } + _selector=null; + } + } + } +} diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/AbstractBuffersTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/AbstractBuffersTest.java new file mode 100644 index 00000000000..4bbf1d500e9 --- /dev/null +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/AbstractBuffersTest.java @@ -0,0 +1,181 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import junit.framework.TestCase; + +public class AbstractBuffersTest + extends TestCase +{ + public boolean _stress = Boolean.getBoolean("STRESS"); + private int _headerBufferSize = 6 * 1024; + + InnerAbstractBuffers buffers; + + List threadList = new ArrayList(); + + int numThreads = _stress?100:10; + + int runTestLength = _stress?5000:1000; + + int threadWaitTime = 5; + + boolean runTest = false; + + AtomicLong buffersRetrieved; + + private static int __LOCAL = 1; + + private static int __LIST = 2; + + private static int __QUEUE = 3; + + protected void setUp() + throws Exception + { + super.setUp(); + } + + protected void tearDown() + throws Exception + { + super.tearDown(); + } + + public void execAbstractBuffer() + throws Exception + { + threadList.clear(); + buffersRetrieved = new AtomicLong( 0 ); + buffers = new InnerAbstractBuffers(); + + for ( int i = 0; i < numThreads; ++i ) + { + threadList.add( new BufferPeeper( "BufferPeeper: " + i ) ); + } + + System.gc(); + System.gc(); + System.gc(); + System.gc(); + System.gc(); + System.gc(); + System.gc(); + System.gc(); + long mem0 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + runTest = true; + + Thread.sleep( runTestLength ); + + System.gc(); + System.gc(); + System.gc(); + System.gc(); + System.gc(); + System.gc(); + System.gc(); + System.gc(); + long mem1 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + + runTest = false; + + long totalBuffersRetrieved = buffersRetrieved.get(); + + System.out.println( "Buffers Retrieved: " + totalBuffersRetrieved ); + System.out.println( "Memory Used: " + ( mem1 - mem0 ) ); + + for ( Iterator i = threadList.iterator(); i.hasNext(); ) + { + Thread t = i.next(); + t.stop(); + } + } + + public void testAbstractBuffers() + throws Exception + { + execAbstractBuffer( ); + + } + + + class InnerAbstractBuffers + extends AbstractBuffers + { + + public Buffer newBuffer( int size ) + { + return new ByteArrayBuffer( size ); + } + + } + + + /** + * generic buffer peeper + * + * + */ + class BufferPeeper + extends Thread + { + private String _bufferName; + + public BufferPeeper( String bufferName ) + { + _bufferName = bufferName; + + start(); + } + + public void run() + { + while ( true ) + { + try + { + + if ( runTest ) + { + Buffer buf = buffers.getBuffer( _headerBufferSize ); + + buffersRetrieved.getAndIncrement(); + + + buf.put( new Byte( "2" ).byteValue() ); + + // sleep( threadWaitTime ); + + buffers.returnBuffer( buf ); + } + else + { + sleep( 1 ); + } + } + catch ( Exception e ) + { + e.printStackTrace(); + break; + } + } + } + } + +} diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/BufferCacheTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/BufferCacheTest.java new file mode 100644 index 00000000000..f24b1f040af --- /dev/null +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/BufferCacheTest.java @@ -0,0 +1,162 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import junit.framework.TestCase; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class BufferCacheTest extends TestCase +{ + final static String[] S= + { "S0", "S1", "s2", "s3" }; + + BufferCache cache; + + public BufferCacheTest(String arg0) + { + super(arg0); + } + + public static void main(String[] args) + { + junit.textui.TestRunner.run(BufferCacheTest.class); + } + + /** + * @see TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + cache=new BufferCache(); + cache.add(S[1],1); + cache.add(S[2],2); + cache.add(S[3],3); + } + + /** + * @see TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + } + + public void testLookupIndex() + { + for (int i=0; i0) + assertEquals(i,index); + else + assertEquals(-1,index); + } + } + + public void testGetBuffer() + { + for (int i=0; i0) + assertEquals(i,b.peek(1)-'0'); + else + assertEquals(null,b); + } + } + + public void testLookupBuffer() + { + for (int i=0; i0) + assertTrue(""+i,S[i]==b.toString()); + else + { + assertTrue(""+i,S[i]!=b.toString()); + assertEquals(""+i,S[i],b.toString()); + } + } + } + + public void testLookupPartialBuffer() + { + cache.add("44444",4); + + ByteArrayBuffer buf=new ByteArrayBuffer("44444"); + Buffer b=cache.lookup(buf); + assertEquals("44444",b.toString()); + assertEquals(4,cache.getOrdinal(b)); + + buf=new ByteArrayBuffer("4444"); + b=cache.lookup(buf); + assertEquals(-1,cache.getOrdinal(b)); + + buf=new ByteArrayBuffer("44444x"); + b=cache.lookup(buf); + assertEquals(-1,cache.getOrdinal(b)); + + + } + + public void testInsensitiveLookupBuffer() + { + for (int i=0; i0) + assertTrue("test"+i,S[i]==b.toString()); + else + assertTrue("test"+i,S[i]!=b.toString()); + } + } + + public void testToString() + { + for (int i=0; i0) + assertTrue(S[i]==b); + else + assertTrue(S[i]!=b); + } + } + +} diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/BufferTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/BufferTest.java new file mode 100644 index 00000000000..7a6650a3683 --- /dev/null +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/BufferTest.java @@ -0,0 +1,279 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.io; + +import java.io.File; + +import junit.framework.TestCase; + +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.RandomAccessFileBuffer; +import org.eclipse.jetty.util.StringUtil; + +/** + * + * + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class BufferTest extends TestCase +{ + Buffer[] buffer; + + public static void main(String[] args) + { + } + + /* + * @see TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + File file = File.createTempFile("test",".buf"); + file.deleteOnExit(); + file.createNewFile(); + + buffer=new Buffer[]{ + new RandomAccessFileBuffer(file,10), + new ByteArrayBuffer(10), + new IndirectNIOBuffer(10), + new DirectNIOBuffer(10) + }; + } + + /* + * @see TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + } + + /* + * + */ + public void testBuffer() + throws Exception + { + for (int i=0;i0;) + result+=s.charAt(c); + } + long end=System.currentTimeMillis(); + System.err.println("charAt "+(end-start)+" "+result); + + start=System.currentTimeMillis(); + result=0; + for (int loop=0;loop0;) + result+=ca[c]; + } + end=System.currentTimeMillis(); + System.err.println("getChars "+(end-start)+" "+result); + + } +} diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml new file mode 100644 index 00000000000..3e47c8403cf --- /dev/null +++ b/jetty-jaspi/pom.xml @@ -0,0 +1,78 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-jaspi + Jetty :: Security + Jetty security infrastructure + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.security + J2SE-1.5 + org.eclipse.jetty.security;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + org.eclipse.jetty + jetty-security + ${project.version} + + + org.apache.geronimo.specs + geronimo-jaspi_1.0_spec + 1.0-SNAPSHOT + + + junit + junit + test + + + diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java new file mode 100644 index 00000000000..a010b51f0c6 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java @@ -0,0 +1,149 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi; + +import java.security.Principal; +import java.util.Map; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.AuthStatus; +import javax.security.auth.message.callback.CallerPrincipalCallback; +import javax.security.auth.message.callback.GroupPrincipalCallback; +import javax.security.auth.message.config.ServerAuthConfig; +import javax.security.auth.message.config.ServerAuthContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.DefaultAuthentication; +import org.eclipse.jetty.security.LazyAuthentication; +import org.eclipse.jetty.security.ServerAuthException; +import org.eclipse.jetty.server.UserIdentity; + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class JaspiAuthenticator implements Authenticator +{ + private final String _authContextId; + private final ServerAuthConfig _authConfig; + private final Map _authProperties; + private final ServletCallbackHandler _callbackHandler; + private final Subject _serviceSubject; + private final boolean _allowLazyAuthentication; + + + public JaspiAuthenticator(String authContextId, ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, + Subject serviceSubject, boolean allowLazyAuthentication) + { + // TODO maybe pass this in via setConfiguration ? + if (callbackHandler == null) + throw new NullPointerException("No CallbackHandler"); + if (authConfig == null) + throw new NullPointerException("No AuthConfig"); + this._authContextId = authContextId; + this._authConfig = authConfig; + this._authProperties = authProperties; + this._callbackHandler = callbackHandler; + this._serviceSubject = serviceSubject; + this._allowLazyAuthentication = allowLazyAuthentication; + } + + + public void setConfiguration(Configuration configuration) + { + } + + + public String getAuthMethod() + { + return "JASPI"; + } + + public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException + { + if (_allowLazyAuthentication && !mandatory) + return new LazyAuthentication(this,request,response); + + JaspiMessageInfo info = new JaspiMessageInfo((HttpServletRequest)request,(HttpServletResponse)response,mandatory); + request.setAttribute("org.eclipse.jetty.security.jaspi.info",info); + return validateRequest(info); + } + + // most likely validatedUser is not needed here. + public Authentication.Status secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication validatedUser) throws ServerAuthException + { + JaspiMessageInfo info = (JaspiMessageInfo)req.getAttribute("org.eclipse.jetty.security.jaspi.info"); + if (info==null)info = new JaspiMessageInfo((HttpServletRequest)req,(HttpServletResponse)res,mandatory); + return secureResponse(info,validatedUser); + } + + public Authentication validateRequest(JaspiMessageInfo messageInfo) throws ServerAuthException + { + try + { + ServerAuthContext authContext = _authConfig.getAuthContext(_authContextId,_serviceSubject,_authProperties); + Subject clientSubject = new Subject(); + + AuthStatus authStatus = authContext.validateRequest(messageInfo,clientSubject,_serviceSubject); + String authMethod = (String)messageInfo.getMap().get(JaspiMessageInfo.AUTH_METHOD_KEY); + CallerPrincipalCallback principalCallback = _callbackHandler.getThreadCallerPrincipalCallback(); + Principal principal = principalCallback == null?null:principalCallback.getPrincipal(); + GroupPrincipalCallback groupPrincipalCallback = _callbackHandler.getThreadGroupPrincipalCallback(); + String[] groups = groupPrincipalCallback == null?null:groupPrincipalCallback.getGroups(); + + Set ids = clientSubject.getPrivateCredentials(UserIdentity.class); + if (ids.size()>0) + return new DefaultAuthentication(toServerAuthStatus(authStatus),authMethod,ids.iterator().next()); + return Authentication.SEND_FAILURE_RESULTS; + } + catch (AuthException e) + { + throw new ServerAuthException(e); + } + } + + public Authentication.Status secureResponse(JaspiMessageInfo messageInfo, Authentication validatedUser) throws ServerAuthException + { + try + { + ServerAuthContext authContext = _authConfig.getAuthContext(_authContextId,_serviceSubject,_authProperties); + authContext.cleanSubject(messageInfo,validatedUser.getUserIdentity().getSubject()); + return toServerAuthStatus(authContext.secureResponse(messageInfo,_serviceSubject)); + } + catch (AuthException e) + { + throw new ServerAuthException(e); + } + } + + Authentication.Status toServerAuthStatus(AuthStatus authStatus) throws ServerAuthException + { + if (authStatus == AuthStatus.SEND_CONTINUE) + return Authentication.Status.SEND_CONTINUE; + if (authStatus == AuthStatus.SEND_FAILURE) + return Authentication.Status.SEND_FAILURE; + if (authStatus == AuthStatus.SEND_SUCCESS) + return Authentication.Status.SEND_SUCCESS; + if (authStatus == AuthStatus.SUCCESS) + return Authentication.Status.SUCCESS; + throw new ServerAuthException("Invalid server status: " + authStatus); + } + +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java new file mode 100644 index 00000000000..c1229458652 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java @@ -0,0 +1,157 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi; + +import java.security.Principal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.config.AuthConfigFactory; +import javax.security.auth.message.config.AuthConfigProvider; +import javax.security.auth.message.config.RegistrationListener; +import javax.security.auth.message.config.ServerAuthConfig; +import javax.servlet.ServletContext; + +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.DefaultAuthenticatorFactory; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.Authenticator.Configuration; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.log.Log; + +public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory +{ + private static String MESSAGE_LAYER = "HTTP"; + + private Subject _serviceSubject; + private String _serverName; + + + /* ------------------------------------------------------------ */ + /** + * @return the serviceSubject + */ + public Subject getServiceSubject() + { + return _serviceSubject; + } + + /* ------------------------------------------------------------ */ + /** + * @param serviceSubject the serviceSubject to set + */ + public void setServiceSubject(Subject serviceSubject) + { + _serviceSubject = serviceSubject; + } + + /* ------------------------------------------------------------ */ + /** + * @return the serverName + */ + public String getServerName() + { + return _serverName; + } + + /* ------------------------------------------------------------ */ + /** + * @param serverName the serverName to set + */ + public void setServerName(String serverName) + { + _serverName = serverName; + } + + /* ------------------------------------------------------------ */ + protected Authenticator newAuthenticator(Server server, ServletContext context, Configuration configuration, LoginService loginService) + { + Authenticator authenticator=null; + try + { + AuthConfigFactory authConfigFactory = AuthConfigFactory.getFactory(); + RegistrationListener listener = new RegistrationListener() + { + public void notify(String layer, String appContext) + {} + }; + + Subject serviceSubject=findServiceSubject(server); + String serverName=findServerName(server,serviceSubject); + + + String appContext = serverName + " " + context.getContextPath(); + AuthConfigProvider authConfigProvider = authConfigFactory.getConfigProvider(MESSAGE_LAYER,appContext,listener); + if (authConfigProvider != null) + { + ServletCallbackHandler servletCallbackHandler = new ServletCallbackHandler(loginService); + ServerAuthConfig serverAuthConfig = authConfigProvider.getServerAuthConfig(MESSAGE_LAYER,appContext,servletCallbackHandler); + if (serverAuthConfig != null) + { + Map map = new HashMap(); + for (String key : configuration.getInitParameterNames()) + map.put(key,configuration.getInitParameter(key)); + authenticator= new JaspiAuthenticator(appContext,serverAuthConfig,map,servletCallbackHandler, + serviceSubject, + configuration.isLazy()); + } + } + } + catch (AuthException e) + { + Log.warn(e); + } + return authenticator; + } + + /* ------------------------------------------------------------ */ + /** Find a service Subject. + * If {@link #setServiceSubject(Subject)} has not been used to + * set a subject, then the {@link Server#getBeans(Class)} method is + * used to look for a Subject. + */ + protected Subject findServiceSubject(Server server) + { + if (_serviceSubject!=null) + return _serviceSubject; + List subjects = server.getBeans(Subject.class); + if (subjects.size()>0) + return (Subject)subjects.get(0); + return null; + } + + /* ------------------------------------------------------------ */ + /** Find a servername. + * If {@link #setServerName(String)} has not been called, then + * use the name of the a principal in the service subject. + * If not found, return "server". + */ + protected String findServerName(Server server, Subject subject) + { + if (_serverName!=null) + return _serverName; + if (subject!=null) + { + Set principals = subject.getPrincipals(); + if (principals!=null && !principals.isEmpty()) + return principals.iterator().next().getName(); + } + + return "server"; + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiMessageInfo.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiMessageInfo.java new file mode 100644 index 00000000000..f691ef24128 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiMessageInfo.java @@ -0,0 +1,215 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.security.auth.message.MessageInfo; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + + +/** + * Almost an implementation of jaspi MessageInfo. + * + * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $ + */ +public class JaspiMessageInfo implements MessageInfo +{ + public static final String MANDATORY_KEY = "javax.security.auth.message.MessagePolicy.isMandatory"; + public static final String AUTH_METHOD_KEY = "javax.servlet.http.authType"; + private ServletRequest request; + private ServletResponse response; + private final MIMap map; + + public JaspiMessageInfo(ServletRequest request, ServletResponse response, boolean isAuthMandatory) + { + this.request = request; + this.response = response; + //JASPI 3.8.1 + map = new MIMap(isAuthMandatory); + } + + public Map getMap() + { + return map; + } + + public Object getRequestMessage() + { + return request; + } + + public Object getResponseMessage() + { + return response; + } + + public void setRequestMessage(Object request) + { + this.request = (ServletRequest)request; + } + + public void setResponseMessage(Object response) + { + this.response = (ServletResponse)response; + } + + public String getAuthMethod() + { + return map.getAuthMethod(); + } + + public boolean isAuthMandatory() { + return map.isAuthMandatory(); + } + + + //TODO this has bugs in the view implementations. Changing them will not affect the hardcoded values. + private static class MIMap implements Map + { + private final boolean isMandatory; + private String authMethod; + private Map delegate; + + private MIMap(boolean mandatory) + { + isMandatory = mandatory; + } + + public int size() + { + return (isMandatory? 1:0) + + (authMethod == null? 0: 1) + + (delegate == null? 0: delegate.size()); + } + + public boolean isEmpty() + { + return !isMandatory && authMethod == null && (delegate == null || delegate.isEmpty()); + } + + public boolean containsKey(Object key) + { + if (MANDATORY_KEY.equals(key)) return true; + if (AUTH_METHOD_KEY.equals(key)) return true; + return delegate != null && delegate.containsKey(key); + } + + public boolean containsValue(Object value) + { + if (isMandatory && "true".equals(value)) return true; + if (authMethod == value || (authMethod != null && authMethod.equals(value))) return true; + return delegate != null && delegate.containsValue(value); + } + + public Object get(Object key) + { + if (MANDATORY_KEY.equals(key)) return isMandatory? "true": null; + if (AUTH_METHOD_KEY.equals(key)) return authMethod; + if (delegate == null) return null; + return delegate.get(key); + } + + public Object put(Object key, Object value) + { + if (MANDATORY_KEY.equals(key)) + { + throw new IllegalArgumentException("Mandatory not mutable"); + } + if (AUTH_METHOD_KEY.equals(key)) + { + String authMethod = this.authMethod; + this.authMethod = (String) value; + if (delegate != null) delegate.put(AUTH_METHOD_KEY, value); + return authMethod; + } + + return getDelegate(true).put(key, value); + } + + public Object remove(Object key) + { + if (MANDATORY_KEY.equals(key)) + { + throw new IllegalArgumentException("Mandatory not mutable"); + } + if (AUTH_METHOD_KEY.equals(key)) + { + String authMethod = this.authMethod; + this.authMethod = null; + if (delegate != null) delegate.remove(AUTH_METHOD_KEY); + return authMethod; + } + if (delegate == null) return null; + return delegate.remove(key); + } + + public void putAll(Map map) + { + if (map != null) + { + for (Object o: map.entrySet()) + { + Map.Entry entry = (Entry) o; + put(entry.getKey(), entry.getValue()); + } + } + } + + public void clear() + { + authMethod = null; + delegate = null; + } + + public Set keySet() + { + return getDelegate(true).keySet(); + } + + public Collection values() + { + return getDelegate(true).values(); + } + + public Set entrySet() + { + return getDelegate(true).entrySet(); + } + + private Map getDelegate(boolean create) + { + if (!create || delegate != null) return delegate; + if (create) + { + delegate = new HashMap(); + if (isMandatory) delegate.put(MANDATORY_KEY, "true"); + if (authMethod != null) delegate.put(AUTH_METHOD_KEY, authMethod); + } + return delegate; + } + + boolean isAuthMandatory() { + return isMandatory; + } + + String getAuthMethod() { + return authMethod; + } + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java new file mode 100644 index 00000000000..15caef790f1 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java @@ -0,0 +1,134 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi; + +import java.io.IOException; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.message.callback.CallerPrincipalCallback; +import javax.security.auth.message.callback.CertStoreCallback; +import javax.security.auth.message.callback.GroupPrincipalCallback; +import javax.security.auth.message.callback.PasswordValidationCallback; +import javax.security.auth.message.callback.PrivateKeyCallback; +import javax.security.auth.message.callback.SecretKeyCallback; +import javax.security.auth.message.callback.TrustStoreCallback; + +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.authentication.LoginCallback; +import org.eclipse.jetty.security.authentication.LoginCallbackImpl; +import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback; +import org.eclipse.jetty.server.UserIdentity; + +/** + * + * Idiot class required by jaspi stupidity + * + * @#*($)#@&^)$@#&*$@ + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class ServletCallbackHandler implements CallbackHandler +{ + private final LoginService _loginService; + + private final ThreadLocal _callerPrincipals = new ThreadLocal(); + private final ThreadLocal _groupPrincipals = new ThreadLocal(); + + public ServletCallbackHandler(LoginService loginService) + { + _loginService = loginService; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for (Callback callback : callbacks) + { + // jaspi to server communication + if (callback instanceof CallerPrincipalCallback) + { + _callerPrincipals.set((CallerPrincipalCallback) callback); + } + else if (callback instanceof GroupPrincipalCallback) + { + _groupPrincipals.set((GroupPrincipalCallback) callback); + } + else if (callback instanceof PasswordValidationCallback) + { + PasswordValidationCallback passwordValidationCallback = (PasswordValidationCallback) callback; + Subject subject = passwordValidationCallback.getSubject(); + + UserIdentity user = _loginService.login(passwordValidationCallback.getUsername(),passwordValidationCallback.getPassword()); + + if (user!=null) + { + passwordValidationCallback.setResult(true); + passwordValidationCallback.getSubject().getPrincipals().addAll(user.getSubject().getPrincipals()); + passwordValidationCallback.getSubject().getPrivateCredentials().add(user); + } + } + else if (callback instanceof CredentialValidationCallback) + { + CredentialValidationCallback credentialValidationCallback = (CredentialValidationCallback) callback; + Subject subject = credentialValidationCallback.getSubject(); + LoginCallback loginCallback = new LoginCallbackImpl(subject, + credentialValidationCallback.getUsername(), + credentialValidationCallback.getCredential()); + + UserIdentity user = _loginService.login(credentialValidationCallback.getUsername(),credentialValidationCallback.getCredential()); + + if (user!=null) + { + credentialValidationCallback.setResult(true); + + credentialValidationCallback.getSubject().getPrincipals().addAll(user.getSubject().getPrincipals()); + credentialValidationCallback.getSubject().getPrivateCredentials().add(user); + } + } + // server to jaspi communication + // TODO implement these + else if (callback instanceof CertStoreCallback) + { + } + else if (callback instanceof PrivateKeyCallback) + { + } + else if (callback instanceof SecretKeyCallback) + { + } + else if (callback instanceof TrustStoreCallback) + { + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + + public CallerPrincipalCallback getThreadCallerPrincipalCallback() + { + CallerPrincipalCallback callerPrincipalCallback = _callerPrincipals.get(); + _callerPrincipals.remove(); + return callerPrincipalCallback; + } + + public GroupPrincipalCallback getThreadGroupPrincipalCallback() + { + GroupPrincipalCallback groupPrincipalCallback = _groupPrincipals.get(); + _groupPrincipals.remove(); + return groupPrincipalCallback; + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java new file mode 100644 index 00000000000..58add7c7c2e --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java @@ -0,0 +1,71 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi; + +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.MessageInfo; +import javax.security.auth.message.config.ServerAuthConfig; +import javax.security.auth.message.config.ServerAuthContext; + +/** + * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $ + */ +public class SimpleAuthConfig implements ServerAuthConfig +{ + public static final String HTTP_SERVLET = "HttpServlet"; + + private final String _appContext; + + private final ServerAuthContext _serverAuthContext; + + public SimpleAuthConfig(String appContext, ServerAuthContext serverAuthContext) + { + this._appContext = appContext; + this._serverAuthContext = serverAuthContext; + } + + public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject, Map properties) throws AuthException + { + return _serverAuthContext; + } + + // supposed to be of form host-namecontext-path + public String getAppContext() + { + return _appContext; + } + + // not used yet + public String getAuthContextID(MessageInfo messageInfo) throws IllegalArgumentException + { + return null; + } + + public String getMessageLayer() + { + return HTTP_SERVLET; + } + + public boolean isProtected() + { + return true; + } + + public void refresh() throws AuthException, SecurityException + { + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/CredentialValidationCallback.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/CredentialValidationCallback.java new file mode 100644 index 00000000000..e22aaa32e60 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/CredentialValidationCallback.java @@ -0,0 +1,74 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.security.jaspi.callback; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; + +import org.eclipse.jetty.http.security.Credential; + +/** + * CredentialValidationCallback + * + * Store a jetty Credential for a user so that it can be + * validated by jaspi + */ +public class CredentialValidationCallback implements Callback +{ + private Credential _credential; + private boolean _result; + private Subject _subject; + private String _userName; + + + public CredentialValidationCallback (Subject subject, String userName, Credential credential) + { + _subject = subject; + _userName = userName; + _credential = credential; + } + + public Credential getCredential () + { + return _credential; + } + + public void clearCredential () + { + _credential = null; + } + + public boolean getResult() + { + return _result; + } + + public javax.security.auth.Subject getSubject() + { + return _subject; + } + + public java.lang.String getUsername() + { + return _userName; + } + + public void setResult(boolean result) + { + _result = result; + } + + +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java new file mode 100644 index 00000000000..4948cc4c690 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java @@ -0,0 +1,146 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi.modules; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.AuthStatus; +import javax.security.auth.message.MessageInfo; +import javax.security.auth.message.MessagePolicy; +import javax.security.auth.message.callback.CallerPrincipalCallback; +import javax.security.auth.message.callback.GroupPrincipalCallback; +import javax.security.auth.message.config.ServerAuthContext; +import javax.security.auth.message.module.ServerAuthModule; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.security.authentication.LoginCallbackImpl; +import org.eclipse.jetty.security.jaspi.JaspiMessageInfo; +import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback; +import org.eclipse.jetty.util.StringUtil; + +/** + * @deprecated use *ServerAuthentication + * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $ + */ +public class BaseAuthModule implements ServerAuthModule, ServerAuthContext +{ + private static final Class[] SUPPORTED_MESSAGE_TYPES = new Class[] { HttpServletRequest.class, HttpServletResponse.class }; + + protected static final String LOGIN_SERVICE_KEY = "org.eclipse.jetty.security.jaspi.modules.LoginService"; + + protected CallbackHandler callbackHandler; + + public Class[] getSupportedMessageTypes() + { + return SUPPORTED_MESSAGE_TYPES; + } + + public BaseAuthModule() + { + } + + public BaseAuthModule(CallbackHandler callbackHandler) + { + this.callbackHandler = callbackHandler; + } + + public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler, Map options) throws AuthException + { + this.callbackHandler = handler; + } + + public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException + { + // TODO apparently we either get the LoginCallback or the LoginService + // but not both :-( + // Set loginCallbacks = + // subject.getPrivateCredentials(LoginCallback.class); + // if (!loginCallbacks.isEmpty()) { + // LoginCallback loginCallback = loginCallbacks.iterator().next(); + // } + // try { + // loginService.logout(subject); + // } catch (ServerAuthException e) { + // throw new AuthException(e.getMessage()); + // } + } + + public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException + { + // servlets do not need secured responses + return AuthStatus.SUCCESS; + } + + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException + { + return AuthStatus.FAILURE; + } + + /** + * @param messageInfo message info to examine for mandatory flag + * @return whether authentication is mandatory or optional + */ + protected boolean isMandatory(MessageInfo messageInfo) + { + String mandatory = (String) messageInfo.getMap().get(JaspiMessageInfo.MANDATORY_KEY); + if (mandatory == null) return false; + return Boolean.valueOf(mandatory); + } + + protected boolean login(Subject clientSubject, String credentials, + String authMethod, MessageInfo messageInfo) + throws IOException, UnsupportedCallbackException + { + credentials = credentials.substring(credentials.indexOf(' ')+1); + credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1); + int i = credentials.indexOf(':'); + String userName = credentials.substring(0,i); + String password = credentials.substring(i+1); + return login(clientSubject, userName, new Password(password), authMethod, messageInfo); + } + + protected boolean login(Subject clientSubject, String username, + Credential credential, String authMethod, + MessageInfo messageInfo) + throws IOException, UnsupportedCallbackException + { + CredentialValidationCallback credValidationCallback = new CredentialValidationCallback(clientSubject, username, credential); + callbackHandler.handle(new Callback[] { credValidationCallback }); + if (credValidationCallback.getResult()) + { + Set loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class); + if (!loginCallbacks.isEmpty()) + { + LoginCallbackImpl loginCallback = loginCallbacks.iterator().next(); + CallerPrincipalCallback callerPrincipalCallback = new CallerPrincipalCallback(clientSubject, loginCallback.getUserPrincipal()); + GroupPrincipalCallback groupPrincipalCallback = new GroupPrincipalCallback(clientSubject, loginCallback.getRoles()); + callbackHandler.handle(new Callback[] { callerPrincipalCallback, groupPrincipalCallback }); + } + messageInfo.getMap().put(JaspiMessageInfo.AUTH_METHOD_KEY, authMethod); + } + return credValidationCallback.getResult(); + + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java new file mode 100644 index 00000000000..45b55c2f4fd --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java @@ -0,0 +1,96 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi.modules; + +import java.io.IOException; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.AuthStatus; +import javax.security.auth.message.MessageInfo; +import javax.security.auth.message.MessagePolicy; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.util.log.Log; + +/** + * @deprecated use *ServerAuthentication + * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $ + */ +public class BasicAuthModule extends BaseAuthModule +{ + + private String realmName; + + private static final String REALM_KEY = "org.eclipse.jetty.security.jaspi.modules.RealmName"; + + public BasicAuthModule() + { + } + + public BasicAuthModule(CallbackHandler callbackHandler, String realmName) + { + super(callbackHandler); + this.realmName = realmName; + } + + @Override + public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, + CallbackHandler handler, Map options) + throws AuthException + { + super.initialize(requestPolicy, responsePolicy, handler, options); + realmName = (String) options.get(REALM_KEY); + } + + @Override + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, + Subject serviceSubject) + throws AuthException + { + HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage(); + HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage(); + String credentials = request.getHeader(HttpHeaders.AUTHORIZATION); + + try + { + if (credentials != null) + { + if (Log.isDebugEnabled()) Log.debug("Credentials: " + credentials); + if (login(clientSubject, credentials, Constraint.__BASIC_AUTH, messageInfo)) { return AuthStatus.SUCCESS; } + + } + + if (!isMandatory(messageInfo)) { return AuthStatus.SUCCESS; } + response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "basic realm=\"" + realmName + '"'); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return AuthStatus.SEND_CONTINUE; + } + catch (IOException e) + { + throw new AuthException(e.getMessage()); + } + catch (UnsupportedCallbackException e) + { + throw new AuthException(e.getMessage()); + } + + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java new file mode 100644 index 00000000000..45750194b9d --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java @@ -0,0 +1,89 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi.modules; + +import java.io.IOException; +import java.security.Principal; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.AuthStatus; +import javax.security.auth.message.MessageInfo; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.http.security.Password; + +/** + * @deprecated use *ServerAuthentication + * @version $Rev: 4530 $ $Date: 2009-02-13 00:47:44 +0100 (Fri, 13 Feb 2009) $ + */ +public class ClientCertAuthModule extends BaseAuthModule +{ + + public ClientCertAuthModule() + { + } + + public ClientCertAuthModule(CallbackHandler callbackHandler) + { + super(callbackHandler); + } + + @Override + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, + Subject serviceSubject) + throws AuthException + { + HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage(); + HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage(); + java.security.cert.X509Certificate[] certs = (java.security.cert.X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); + + try + { + // Need certificates. + if (certs == null || certs.length == 0 || certs[0] == null) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, + "A client certificate is required for accessing this web application but the server's listener is not configured for mutual authentication (or the client did not provide a certificate)."); + return AuthStatus.SEND_FAILURE; + } + Principal principal = certs[0].getSubjectDN(); + if (principal == null) principal = certs[0].getIssuerDN(); + final String username = principal == null ? "clientcert" : principal.getName(); + // TODO no idea if this is correct + final String password = new String(B64Code.encode(certs[0].getSignature())); + + // TODO is cert_auth correct? + if (login(clientSubject, username, new Password(password), Constraint.__CERT_AUTH, messageInfo)) { return AuthStatus.SUCCESS; } + + if (!isMandatory(messageInfo)) { return AuthStatus.SUCCESS; } + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The provided client certificate does not correspond to a trusted user."); + return AuthStatus.SEND_FAILURE; + } + catch (IOException e) + { + throw new AuthException(e.getMessage()); + } + catch (UnsupportedCallbackException e) + { + throw new AuthException(e.getMessage()); + } + + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java new file mode 100644 index 00000000000..f03e895af7e --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java @@ -0,0 +1,357 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi.modules; + +import java.io.IOException; +import java.security.MessageDigest; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.AuthStatus; +import javax.security.auth.message.MessageInfo; +import javax.security.auth.message.MessagePolicy; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * @deprecated use *ServerAuthentication + * @version $Rev: 4627 $ $Date: 2009-02-20 00:07:19 +0100 (Fri, 20 Feb 2009) $ + */ +public class DigestAuthModule extends BaseAuthModule +{ + + protected long maxNonceAge = 0; + + protected long nonceSecret = this.hashCode() ^ System.currentTimeMillis(); + + protected boolean useStale = false; + + private String realmName; + + private static final String REALM_KEY = "org.eclipse.jetty.security.jaspi.modules.RealmName"; + + public DigestAuthModule() + { + } + + public DigestAuthModule(CallbackHandler callbackHandler, String realmName) + { + super(callbackHandler); + this.realmName = realmName; + } + + @Override + public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, + CallbackHandler handler, Map options) + throws AuthException + { + super.initialize(requestPolicy, responsePolicy, handler, options); + realmName = (String) options.get(REALM_KEY); + } + + @Override + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, + Subject serviceSubject) + throws AuthException + { + HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage(); + HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage(); + String credentials = request.getHeader(HttpHeaders.AUTHORIZATION); + + try + { + boolean stale = false; + // TODO extract from request + long timestamp = System.currentTimeMillis(); + if (credentials != null) + { + if (Log.isDebugEnabled()) Log.debug("Credentials: " + credentials); + QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false); + final Digest digest = new Digest(request.getMethod()); + String last = null; + String name = null; + + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken(); + char c = (tok.length() == 1) ? tok.charAt(0) : '\0'; + + switch (c) + { + case '=': + name = last; + last = tok; + break; + case ',': + name = null; + case ' ': + break; + + default: + last = tok; + if (name != null) + { + if ("username".equalsIgnoreCase(name)) + digest.username = tok; + else if ("realm".equalsIgnoreCase(name)) + digest.realm = tok; + else if ("nonce".equalsIgnoreCase(name)) + digest.nonce = tok; + else if ("nc".equalsIgnoreCase(name)) + digest.nc = tok; + else if ("cnonce".equalsIgnoreCase(name)) + digest.cnonce = tok; + else if ("qop".equalsIgnoreCase(name)) + digest.qop = tok; + else if ("uri".equalsIgnoreCase(name)) + digest.uri = tok; + else if ("response".equalsIgnoreCase(name)) digest.response = tok; + break; + } + } + } + + int n = checkNonce(digest.nonce, timestamp); + + if (n > 0) + { + if (login(clientSubject, digest.username, digest, Constraint.__DIGEST_AUTH, messageInfo)) { return AuthStatus.SUCCESS; } + } + else if (n == 0) stale = true; + + } + + if (!isMandatory(messageInfo)) { return AuthStatus.SUCCESS; } + String domain = request.getContextPath(); + if (domain == null) domain = "/"; + response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"" + realmName + + "\", domain=\"" + + domain + + "\", nonce=\"" + + newNonce(timestamp) + + "\", algorithm=MD5, qop=\"auth\"" + + (useStale ? (" stale=" + stale) : "")); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return AuthStatus.SEND_CONTINUE; + } + catch (IOException e) + { + throw new AuthException(e.getMessage()); + } + catch (UnsupportedCallbackException e) + { + throw new AuthException(e.getMessage()); + } + + } + + public String newNonce(long ts) + { + // long ts=request.getTimeStamp(); + long sk = nonceSecret; + + byte[] nounce = new byte[24]; + for (int i = 0; i < 8; i++) + { + nounce[i] = (byte) (ts & 0xff); + ts = ts >> 8; + nounce[8 + i] = (byte) (sk & 0xff); + sk = sk >> 8; + } + + byte[] hash = null; + try + { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.reset(); + md.update(nounce, 0, 16); + hash = md.digest(); + } + catch (Exception e) + { + Log.warn(e); + } + + for (int i = 0; i < hash.length; i++) + { + nounce[8 + i] = hash[i]; + if (i == 23) break; + } + + return new String(B64Code.encode(nounce)); + } + + /** + * @param nonce + * @param timestamp should be timestamp of request. + * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce + */ + /* ------------------------------------------------------------ */ + public int checkNonce(String nonce, long timestamp) + { + try + { + byte[] n = B64Code.decode(nonce.toCharArray()); + if (n.length != 24) return -1; + + long ts = 0; + long sk = nonceSecret; + byte[] n2 = new byte[16]; + System.arraycopy(n, 0, n2, 0, 8); + for (int i = 0; i < 8; i++) + { + n2[8 + i] = (byte) (sk & 0xff); + sk = sk >> 8; + ts = (ts << 8) + (0xff & (long) n[7 - i]); + } + + long age = timestamp - ts; + if (Log.isDebugEnabled()) Log.debug("age=" + age); + + byte[] hash = null; + try + { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.reset(); + md.update(n2, 0, 16); + hash = md.digest(); + } + catch (Exception e) + { + Log.warn(e); + } + + for (int i = 0; i < 16; i++) + if (n[i + 8] != hash[i]) return -1; + + if (maxNonceAge > 0 && (age < 0 || age > maxNonceAge)) return 0; // stale + + return 1; + } + catch (Exception e) + { + Log.ignore(e); + } + return -1; + } + + private static class Digest extends Credential + { + String method = null; + + String username = null; + + String realm = null; + + String nonce = null; + + String nc = null; + + String cnonce = null; + + String qop = null; + + String uri = null; + + String response = null; + + /* ------------------------------------------------------------ */ + Digest(String m) + { + method = m; + } + + /* ------------------------------------------------------------ */ + public boolean check(Object credentials) + { + String password = (credentials instanceof String) ? (String) credentials : credentials.toString(); + + try + { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] ha1; + if (credentials instanceof Credential.MD5) + { + // Credentials are already a MD5 digest - assume it's in + // form user:realm:password (we have no way to know since + // it's a digest, alright?) + ha1 = ((Credential.MD5) credentials).getDigest(); + } + else + { + // calc A1 digest + md.update(username.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(realm.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(password.getBytes(StringUtil.__ISO_8859_1)); + ha1 = md.digest(); + } + // calc A2 digest + md.reset(); + md.update(method.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(uri.getBytes(StringUtil.__ISO_8859_1)); + byte[] ha2 = md.digest(); + + // calc digest + // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" + // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) ) + // <"> + // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) + // ) > <"> + + md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(nonce.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(nc.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(cnonce.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(qop.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1)); + byte[] digest = md.digest(); + + // check digest + return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response)); + } + catch (Exception e) + { + Log.warn(e); + } + + return false; + } + + public String toString() + { + return username + "," + response; + } + + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java new file mode 100644 index 00000000000..1c5c91ed6dd --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java @@ -0,0 +1,436 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi.modules; + +import java.io.IOException; +import java.io.Serializable; +import java.security.Principal; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.AuthStatus; +import javax.security.auth.message.MessageInfo; +import javax.security.auth.message.MessagePolicy; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.security.CrossContextPsuedoSession; +import org.eclipse.jetty.security.authentication.LoginCallbackImpl; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * @deprecated use *ServerAuthentication + * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $ + */ +public class FormAuthModule extends BaseAuthModule +{ + /* ------------------------------------------------------------ */ + public final static String __J_URI = "org.eclipse.jetty.util.URI"; + + public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth"; + + public final static String __J_SECURITY_CHECK = "/j_security_check"; + + public final static String __J_USERNAME = "j_username"; + + public final static String __J_PASSWORD = "j_password"; + + // private String realmName; + public static final String LOGIN_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.LoginPage"; + + public static final String ERROR_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.ErrorPage"; + + public static final String SSO_SOURCE_KEY = "org.eclipse.jetty.security.jaspi.modules.SsoSource"; + + private String _formErrorPage; + + private String _formErrorPath; + + private String _formLoginPage; + + private String _formLoginPath; + + private CrossContextPsuedoSession ssoSource; + + public FormAuthModule() + { + } + + public FormAuthModule(CallbackHandler callbackHandler, String loginPage, String errorPage) + { + super(callbackHandler); + setLoginPage(loginPage); + setErrorPage(errorPage); + } + + public FormAuthModule(CallbackHandler callbackHandler, CrossContextPsuedoSession ssoSource, + String loginPage, String errorPage) + { + super(callbackHandler); + this.ssoSource = ssoSource; + setLoginPage(loginPage); + setErrorPage(errorPage); + } + + @Override + public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, + CallbackHandler handler, Map options) + throws AuthException + { + super.initialize(requestPolicy, responsePolicy, handler, options); + setLoginPage((String) options.get(LOGIN_PAGE_KEY)); + setErrorPage((String) options.get(ERROR_PAGE_KEY)); + ssoSource = (CrossContextPsuedoSession) options.get(SSO_SOURCE_KEY); + } + + private void setLoginPage(String path) + { + if (!path.startsWith("/")) + { + Log.warn("form-login-page must start with /"); + path = "/" + path; + } + _formLoginPage = path; + _formLoginPath = path; + if (_formLoginPath.indexOf('?') > 0) _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?')); + } + + /* ------------------------------------------------------------ */ + private void setErrorPage(String path) + { + if (path == null || path.trim().length() == 0) + { + _formErrorPath = null; + _formErrorPage = null; + } + else + { + if (!path.startsWith("/")) + { + Log.warn("form-error-page must start with /"); + path = "/" + path; + } + _formErrorPage = path; + _formErrorPath = path; + + if (_formErrorPath.indexOf('?') > 0) _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?')); + } + } + + @Override + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException + { + HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage(); + HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage(); + HttpSession session = request.getSession(isMandatory(messageInfo)); + String uri = request.getPathInfo(); + // not mandatory and not authenticated + if (session == null || isLoginOrErrorPage(uri)) return AuthStatus.SUCCESS; + + try + { + // Handle a request for authentication. + // TODO perhaps j_securitycheck can be uri suffix? + if (uri.endsWith(__J_SECURITY_CHECK)) + { + + final String username = request.getParameter(__J_USERNAME); + final String password = request.getParameter(__J_PASSWORD); + boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password)); + if (success) + { + // Redirect to original request + String nuri = (String) session.getAttribute(__J_URI); + if (nuri == null || nuri.length() == 0) + { + nuri = request.getContextPath(); + if (nuri.length() == 0) nuri = URIUtil.SLASH; + } + session.removeAttribute(__J_URI); // Remove popped return + // URI. + response.setContentLength(0); + response.sendRedirect(response.encodeRedirectURL(nuri)); + + return AuthStatus.SEND_CONTINUE; + } + // not authenticated + if (Log.isDebugEnabled()) Log.debug("Form authentication FAILED for " + StringUtil.printable(username)); + if (_formErrorPage == null) + { + if (response != null) response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + else + { + response.setContentLength(0); + response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage))); + } + // TODO is this correct response if isMandatory false??? Can + // that occur? + return AuthStatus.SEND_FAILURE; + } + // Check if the session is already authenticated. + FormCredential form_cred = (FormCredential) session.getAttribute(__J_AUTHENTICATED); + + if (form_cred != null) + { + boolean success = tryLogin(messageInfo, clientSubject, response, session, form_cred._jUserName, new Password(new String(form_cred._jPassword))); + if (success) { return AuthStatus.SUCCESS; } + // CallbackHandler loginCallbackHandler = new + // UserPasswordCallbackHandler(form_cred._jUserName, + // form_cred._jPassword); + // LoginResult loginResult = loginService.login(clientSubject, + // loginCallbackHandler); + // //TODO what should happen if !isMandatory but credentials + // exist and are wrong? + // if (loginResult.isSuccess()) + // { + // callbackHandler.handle(new + // Callback[]{loginResult.getCallerPrincipalCallback(), + // loginResult.getGroupPrincipalCallback()}); + // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY, + // Constraint.__FORM_AUTH); + // + // form_cred = new FormCredential(form_cred._jUserName, + // form_cred._jPassword, + // loginResult.getCallerPrincipalCallback().getPrincipal()); + // + // session.setAttribute(__J_AUTHENTICATED, form_cred); + // if (ssoSource != null && ssoSource.fetch(request) == null) + // { + // UserInfo userInfo = new UserInfo(form_cred._jUserName, + // form_cred._jPassword); + // ssoSource.store(userInfo, response); + // } + // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY, + // Constraint.__FORM_AUTH); + // return AuthStatus.SUCCESS; + // } + + // // We have a form credential. Has it been distributed? + // if (form_cred._userPrincipal==null) + // { + // // This form_cred appears to have been distributed. Need to + // reauth + // form_cred.authenticate(realm, request); + // + // // Sign-on to SSO mechanism + // if (form_cred._userPrincipal!=null && realm instanceof + // SSORealm) + // ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new + // Password(form_cred._jPassword)); + // + // } + // else if (!realm.reauthenticate(form_cred._userPrincipal)) + // // Else check that it is still authenticated. + // form_cred._userPrincipal=null; + // + // // If this credential is still authenticated + // if (form_cred._userPrincipal!=null) + // { + // if(Log.isDebugEnabled())Log.debug("FORM Authenticated for + // "+form_cred._userPrincipal.getName()); + // request.setAuthType(Constraint.__FORM_AUTH); + // //jaspi + // // request.setUserPrincipal(form_cred._userPrincipal); + // return form_cred._userPrincipal; + // } + // else + // session.setAttribute(__J_AUTHENTICATED,null); + // } + // else if (realm instanceof SSORealm) + // { + // // Try a single sign on. + // Credential cred = + // ((SSORealm)realm).getSingleSignOn(request,response); + // + // if (request.getUserPrincipal()!=null) + // { + // form_cred=new FormCredential(); + // form_cred._userPrincipal=request.getUserPrincipal(); + // form_cred._jUserName=form_cred._userPrincipal.getName(); + // if (cred!=null) + // form_cred._jPassword=cred.toString(); + // if(Log.isDebugEnabled())Log.debug("SSO for + // "+form_cred._userPrincipal); + // + // request.setAuthType(Constraint.__FORM_AUTH); + // session.setAttribute(__J_AUTHENTICATED,form_cred); + // return form_cred._userPrincipal; + // } + } + else if (ssoSource != null) + { + UserInfo userInfo = ssoSource.fetch(request); + if (userInfo != null) + { + boolean success = tryLogin(messageInfo, clientSubject, response, session, userInfo.getUserName(), new Password(new String(userInfo.getPassword()))); + if (success) { return AuthStatus.SUCCESS; } + } + } + + // Don't authenticate authform or errorpage + if (!isMandatory(messageInfo) || isLoginOrErrorPage(uri)) + // TODO verify this is correct action + return AuthStatus.SUCCESS; + + // redirect to login page + if (request.getQueryString() != null) uri += "?" + request.getQueryString(); + session.setAttribute(__J_URI, request.getScheme() + "://" + + request.getServerName() + + ":" + + request.getServerPort() + + URIUtil.addPaths(request.getContextPath(), uri)); + response.setContentLength(0); + response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage))); + return AuthStatus.SEND_CONTINUE; + } + catch (IOException e) + { + throw new AuthException(e.getMessage()); + } + catch (UnsupportedCallbackException e) + { + throw new AuthException(e.getMessage()); + } + + } + + private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject, + HttpServletResponse response, HttpSession session, + String username, Password password) + throws AuthException, IOException, UnsupportedCallbackException + { + if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo)) + { + char[] pwdChars = password.toString().toCharArray(); + Set loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class); + if (!loginCallbacks.isEmpty()) + { + LoginCallbackImpl loginCallback = loginCallbacks.iterator().next(); + FormCredential form_cred = new FormCredential(username, pwdChars, loginCallback.getUserPrincipal()); + + session.setAttribute(__J_AUTHENTICATED, form_cred); + } + + // Sign-on to SSO mechanism + if (ssoSource != null) + { + UserInfo userInfo = new UserInfo(username, pwdChars); + ssoSource.store(userInfo, response); + } + return true; + } + return false; + // LoginCallback loginCallback = new LoginCallback(clientSubject, + // username, password); + // loginService.login(loginCallback); + // if (loginCallback.isSuccess()) + // { + // CallerPrincipalCallback callerPrincipalCallback = new + // CallerPrincipalCallback(clientSubject, + // loginCallback.getUserPrincipal()); + // GroupPrincipalCallback groupPrincipalCallback = new + // GroupPrincipalCallback(clientSubject, + // loginCallback.getGroups().toArray(new + // String[loginCallback.getGroups().size()])); + // callbackHandler.handle(new Callback[] {callerPrincipalCallback, + // groupPrincipalCallback}); + // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY, + // Constraint.__FORM_AUTH); + // FormCredential form_cred = new FormCredential(username, password, + // loginCallback.getUserPrincipal()); + // + // session.setAttribute(__J_AUTHENTICATED, form_cred); + // // Sign-on to SSO mechanism + // if (ssoSource != null) + // { + // UserInfo userInfo = new UserInfo(username, password); + // ssoSource.store(userInfo, response); + // } + // } + // return loginCallback.isSuccess(); + } + + public boolean isLoginOrErrorPage(String pathInContext) + { + return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath)); + } + + /* ------------------------------------------------------------ */ + /** + * FORM Authentication credential holder. + */ + private static class FormCredential implements Serializable, HttpSessionBindingListener + { + String _jUserName; + + char[] _jPassword; + + transient Principal _userPrincipal; + + private FormCredential(String _jUserName, char[] _jPassword, Principal _userPrincipal) + { + this._jUserName = _jUserName; + this._jPassword = _jPassword; + this._userPrincipal = _userPrincipal; + } + + public void valueBound(HttpSessionBindingEvent event) + { + } + + public void valueUnbound(HttpSessionBindingEvent event) + { + if (Log.isDebugEnabled()) Log.debug("Logout " + _jUserName); + + // TODO jaspi call cleanSubject() + // if (_realm instanceof SSORealm) + // ((SSORealm) _realm).clearSingleSignOn(_jUserName); + // + // if (_realm != null && _userPrincipal != null) + // _realm.logout(_userPrincipal); + } + + public int hashCode() + { + return _jUserName.hashCode() + _jPassword.hashCode(); + } + + public boolean equals(Object o) + { + if (!(o instanceof FormCredential)) return false; + FormCredential fc = (FormCredential) o; + return _jUserName.equals(fc._jUserName) && Arrays.equals(_jPassword, fc._jPassword); + } + + public String toString() + { + return "Cred[" + _jUserName + "]"; + } + + } + +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java new file mode 100644 index 00000000000..7221823889e --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java @@ -0,0 +1,48 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.jaspi.modules; + +import java.util.Arrays; + +/** + * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $ + */ +public class UserInfo +{ + private final String userName; + + private char[] password; + + public UserInfo(String userName, char[] password) + { + this.userName = userName; + this.password = password; + } + + public String getUserName() + { + return userName; + } + + public char[] getPassword() + { + return password; + } + + public void clearPassword() + { + Arrays.fill(password, (char) 0); + password = null; + } +} diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml new file mode 100644 index 00000000000..171f3dcea62 --- /dev/null +++ b/jetty-jmx/pom.xml @@ -0,0 +1,77 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-jmx + Jetty :: JMX Management + JMX management artifact for jetty. + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.jmx + http://jetty.eclipse.org + J2SE-1.5 + !org.eclipse.component.management.*,!org.eclipse.jetty.deployer.management.*,!org.eclipse.jetty.nio.management.*,!org.eclipse.log.management.*,!org.eclipse.jetty.jmx.*,!org.eclipse.thread.management.*,!org.eclipse.jetty.jmx.handler.*,!org.eclipse.jetty.jmx.*,!org.eclipse.jetty.jmx.servlet.*,!org.eclipse.jetty.jmx.webapp.*,* + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + config.xml + + + + + + + + + + junit + junit + test + + + org.eclipse.jetty + jetty-webapp + ${project.version} + + + diff --git a/jetty-jmx/src/main/config/etc/jetty-jmx.xml b/jetty-jmx/src/main/config/etc/jetty-jmx.xml new file mode 100644 index 00000000000..a3ee4f3b604 --- /dev/null +++ b/jetty-jmx/src/main/config/etc/jetty-jmx.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java new file mode 100644 index 00000000000..f16477379ce --- /dev/null +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java @@ -0,0 +1,274 @@ +// ======================================================================== +// Copyright (c) 2005-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jmx; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import javax.management.MBeanServer; +import javax.management.ObjectInstance; +import javax.management.ObjectName; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.Container; +import org.eclipse.jetty.util.component.Container.Relationship; +import org.eclipse.jetty.util.log.Log; + +public class MBeanContainer extends AbstractLifeCycle implements Container.Listener +{ + private final MBeanServer _server; + private final WeakHashMap _beans = new WeakHashMap(); + private final HashMap _unique = new HashMap(); + private String _domain = null; + private MultiMap _relations = new MultiMap(); + + + public synchronized ObjectName findMBean(Object object) + { + ObjectName bean = (ObjectName)_beans.get(object); + return bean==null?null:bean; + } + + public synchronized Object findBean(ObjectName oname) + { + for (Iterator iter = _beans.entrySet().iterator(); iter.hasNext();) + { + Map.Entry entry = (Map.Entry) iter.next(); + ObjectName bean = (ObjectName)entry.getValue(); + if (bean.equals(oname)) + return entry.getKey(); + } + return null; + } + + public MBeanContainer(MBeanServer server) + { + this._server = server; + } + + public MBeanServer getMBeanServer() + { + return _server; + } + + public void setDomain (String domain) + { + _domain =domain; + } + + public String getDomain() + { + return _domain; + } + + public void doStart() + { + } + + public synchronized void add(Relationship relationship) + { + ObjectName parent=(ObjectName)_beans.get(relationship.getParent()); + if (parent==null) + { + addBean(relationship.getParent()); + parent=(ObjectName)_beans.get(relationship.getParent()); + } + + ObjectName child=(ObjectName)_beans.get(relationship.getChild()); + if (child==null) + { + addBean(relationship.getChild()); + child=(ObjectName)_beans.get(relationship.getChild()); + } + + if (parent!=null && child!=null) + _relations.add(parent,relationship); + + + } + + public synchronized void remove(Relationship relationship) + { + ObjectName parent=(ObjectName)_beans.get(relationship.getParent()); + ObjectName child=(ObjectName)_beans.get(relationship.getChild()); + if (parent!=null && child!=null) + _relations.removeValue(parent,relationship); + } + + public synchronized void removeBean(Object obj) + { + ObjectName bean=(ObjectName)_beans.remove(obj); + + if (bean!=null) + { + List r=_relations.getValues(bean); + if (r!=null && r.size()>0) + { + Log.debug("Unregister {}", r); + Iterator iter = new ArrayList(r).iterator(); + while (iter.hasNext()) + { + Relationship rel = (Relationship)iter.next(); + rel.getContainer().update(rel.getParent(),rel.getChild(),null,rel.getRelationship(),true); + } + } + + try + { + _server.unregisterMBean(bean); + Log.debug("Unregistered {}", bean); + } + catch (javax.management.InstanceNotFoundException e) + { + Log.ignore(e); + } + catch (Exception e) + { + Log.warn(e); + } + } + } + + public synchronized void addBean(Object obj) + { + try + { + if (obj == null || _beans.containsKey(obj)) + return; + + Object mbean = ObjectMBean.mbeanFor(obj); + if (mbean == null) + return; + + ObjectName oname = null; + if (mbean instanceof ObjectMBean) + { + ((ObjectMBean) mbean).setMBeanContainer(this); + oname = ((ObjectMBean)mbean).getObjectName(); + } + + //no override mbean object name, so make a generic one + if (oname == null) + { + String type=obj.getClass().getName().toLowerCase(); + int dot = type.lastIndexOf('.'); + if (dot >= 0) + type = type.substring(dot + 1); + + String name=null; + if (mbean instanceof ObjectMBean) + { + name = ((ObjectMBean)mbean).getObjectNameBasis(); + if (name!=null) + { + name=name.replace('\\','/'); + if (name.endsWith("/")) + name=name.substring(0,name.length()-1); + + int slash=name.lastIndexOf('/',name.length()-1); + if (slash>0) + name=name.substring(slash+1); + dot=name.lastIndexOf('.'); + if (dot>0) + name=name.substring(0,dot); + + name=name.replace(':','_').replace('*','_').replace('?','_').replace('=','_').replace(',','_').replace(' ','_'); + } + } + + String basis=(name!=null&&name.length()>1)?("type="+type+",name="+name):("type="+type); + + Integer count = (Integer) _unique.get(basis); + count = TypeUtil.newInteger(count == null ? 0 : (1 + count.intValue())); + _unique.put(basis, count); + + //if no explicit domain, create one + String domain = _domain; + if (domain==null) + domain = obj.getClass().getPackage().getName(); + + oname = ObjectName.getInstance(domain+":"+basis+",id="+count); + } + + ObjectInstance oinstance = _server.registerMBean(mbean, oname); + Log.debug("Registered {}" , oinstance.getObjectName()); + _beans.put(obj, oinstance.getObjectName()); + + } + catch (Exception e) + { + Log.warn("bean: "+obj,e); + } + } + + public void doStop() + { + while (_beans.size()>0) + removeBean(_beans.keySet().iterator().next()); + } + + private class ShutdownHook extends Thread + { + private final ObjectName mletName; + private final ObjectName adaptorName; + private final ObjectName processorName; + + public ShutdownHook(ObjectName mletName, ObjectName adaptorName, ObjectName processorName) + { + this.mletName = mletName; + this.adaptorName = adaptorName; + this.processorName = processorName; + } + + public void run() + { + halt(); + unregister(processorName); + unregister(adaptorName); + unregister(mletName); + } + + private void halt() + { + try + { + _server.invoke(adaptorName, "stop", null, null); + } + catch (Exception e) + { + Log.warn(e); + } + } + + private void unregister(ObjectName objectName) + { + try + { + _server.unregisterMBean(objectName); + Log.debug("Unregistered " + objectName); + } + catch (Exception e) + { + Log.warn(e); + } + } + } + +} diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java new file mode 100644 index 00000000000..a1f3d82450c --- /dev/null +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java @@ -0,0 +1,719 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jmx; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.DynamicMBean; +import javax.management.InvalidAttributeValueException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.modelmbean.ModelMBean; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** ObjectMBean. + * A dynamic MBean that can wrap an arbitary Object instance. + * the attributes and methods exposed by this bean are controlled by + * the merge of property bundles discovered by names related to all + * superclasses and all superinterfaces. + * + * Attributes and methods exported may be "Object" and must exist on the + * wrapped object, or "MBean" and must exist on a subclass of OBjectMBean + * or "MObject" which exists on the wrapped object, but whose values are + * converted to MBean object names. + * + */ +public class ObjectMBean implements DynamicMBean +{ + private static Class[] OBJ_ARG = new Class[]{Object.class}; + + protected Object _managed; + private MBeanInfo _info; + private Map _getters=new HashMap(); + private Map _setters=new HashMap(); + private Map _methods=new HashMap(); + private Set _convert=new HashSet(); + private ClassLoader _loader; + private MBeanContainer _mbeanContainer; + + private static String OBJECT_NAME_CLASS = ObjectName.class.getName(); + private static String OBJECT_NAME_ARRAY_CLASS = ObjectName[].class.getName(); + + /* ------------------------------------------------------------ */ + /** + * Create MBean for Object. Attempts to create an MBean for the object by searching the package + * and class name space. For example an object of the type + * + *
    +     * class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface
    +     * 
    + * + * Then this method would look for the following classes: + *
      + *
    • com.acme.management.MyClassMBean + *
    • com.acme.util.management.BaseClassMBean + *
    • org.eclipse.jetty.jmx.ObjectMBean + *
    + * + * @param o The object + * @return A new instance of an MBean for the object or null. + */ + public static Object mbeanFor(Object o) + { + try + { + Class oClass = o.getClass(); + Object mbean = null; + + while (mbean == null && oClass != null) + { + String pName = oClass.getPackage().getName(); + String cName = oClass.getName().substring(pName.length() + 1); + String mName = pName + ".management." + cName + "MBean"; + + + try + { + Class mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName,true); + if (Log.isDebugEnabled()) + Log.debug("mbeanFor " + o + " mClass=" + mClass); + + try + { + Constructor constructor = mClass.getConstructor(OBJ_ARG); + mbean=constructor.newInstance(new Object[]{o}); + } + catch(Exception e) + { + Log.ignore(e); + if (ModelMBean.class.isAssignableFrom(mClass)) + { + mbean=mClass.newInstance(); + ((ModelMBean)mbean).setManagedResource(o, "objectReference"); + } + } + + if (Log.isDebugEnabled()) + Log.debug("mbeanFor " + o + " is " + mbean); + return mbean; + } + catch (ClassNotFoundException e) + { + if (e.toString().endsWith("MBean")) + Log.ignore(e); + else + Log.warn(e); + } + catch (Error e) + { + Log.warn(e); + mbean = null; + } + catch (Exception e) + { + Log.warn(e); + mbean = null; + } + + oClass = oClass.getSuperclass(); + } + } + catch (Exception e) + { + Log.ignore(e); + } + return null; + } + + + public ObjectMBean(Object managedObject) + { + _managed = managedObject; + _loader = Thread.currentThread().getContextClassLoader(); + } + + public Object getManagedObject() + { + return _managed; + } + + public ObjectName getObjectName() + { + return null; + } + + public String getObjectNameBasis() + { + return null; + } + + protected void setMBeanContainer(MBeanContainer container) + { + this._mbeanContainer = container; + } + + public MBeanContainer getMBeanContainer () + { + return this._mbeanContainer; + } + + + public MBeanInfo getMBeanInfo() + { + try + { + if (_info==null) + { + // Start with blank lazy lists attributes etc. + String desc=null; + Object attributes=null; + Object constructors=null; + Object operations=null; + Object notifications=null; + + // Find list of classes that can influence the mbean + Class o_class=_managed.getClass(); + Object influences = findInfluences(null, _managed.getClass()); + + // Set to record defined items + Set defined=new HashSet(); + + // For each influence + for (int i=0;i0) + { + // define an operation + if (!defined.contains(key) && key.indexOf('[')<0) + { + defined.add(key); + operations=LazyList.add(operations,defineOperation(key, value, bundle)); + } + } + else + { + // define an attribute + if (!defined.contains(key)) + { + defined.add(key); + attributes=LazyList.add(attributes,defineAttribute(key, value)); + } + } + } + + } + catch(MissingResourceException e) + { + Log.ignore(e); + } + } + + _info = new MBeanInfo(o_class.getName(), + desc, + (MBeanAttributeInfo[])LazyList.toArray(attributes, MBeanAttributeInfo.class), + (MBeanConstructorInfo[])LazyList.toArray(constructors, MBeanConstructorInfo.class), + (MBeanOperationInfo[])LazyList.toArray(operations, MBeanOperationInfo.class), + (MBeanNotificationInfo[])LazyList.toArray(notifications, MBeanNotificationInfo.class)); + } + } + catch(RuntimeException e) + { + Log.warn(e); + throw e; + } + return _info; + } + + + /* ------------------------------------------------------------ */ + public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException + { + Method getter = (Method) _getters.get(name); + if (getter == null) + throw new AttributeNotFoundException(name); + try + { + Object o = _managed; + if (getter.getDeclaringClass().isInstance(this)) + o = this; // mbean method + + // get the attribute + Object r=getter.invoke(o, (java.lang.Object[]) null); + + // convert to ObjectName if need be. + if (r!=null && _convert.contains(name)) + { + if (r.getClass().isArray()) + { + ObjectName[] on = new ObjectName[Array.getLength(r)]; + for (int i=0;i 0 ? "," : "") + signature[i]; + methodKey += ")"; + + ClassLoader old_loader=Thread.currentThread().getContextClassLoader(); + try + { + Thread.currentThread().setContextClassLoader(_loader); + Method method = (Method) _methods.get(methodKey); + if (method == null) + throw new NoSuchMethodException(methodKey); + + Object o = _managed; + if (method.getDeclaringClass().isInstance(this)) + o = this; + return method.invoke(o, params); + } + catch (NoSuchMethodException e) + { + Log.warn(Log.EXCEPTION, e); + throw new ReflectionException(e); + } + catch (IllegalAccessException e) + { + Log.warn(Log.EXCEPTION, e); + throw new MBeanException(e); + } + catch (InvocationTargetException e) + { + Log.warn(Log.EXCEPTION, e); + throw new ReflectionException((Exception) e.getTargetException()); + } + finally + { + Thread.currentThread().setContextClassLoader(old_loader); + } + } + + private static Object findInfluences(Object influences, Class aClass) + { + if (aClass!=null) + { + // This class is an influence + influences=LazyList.add(influences,aClass); + + // So are the super classes + influences=findInfluences(influences,aClass.getSuperclass()); + + // So are the interfaces + Class[] ifs = aClass.getInterfaces(); + for (int i=0;ifs!=null && i + *
  • "Object" The field/method is on the managed object. + *
  • "MBean" The field/method is on the mbean proxy object + *
  • "MObject" The field/method is on the managed object and value should be converted to MBean reference + *
  • "MMBean" The field/method is on the mbean proxy object and value should be converted to MBean reference + * + * the access is either "RW" or "RO". + */ + public MBeanAttributeInfo defineAttribute(String name, String metaData) + { + String description = ""; + boolean writable = true; + boolean onMBean = false; + boolean convert = false; + + if (metaData!= null) + { + String[] tokens = metaData.split(":", 3); + for (int t=0;t0?",":"(")+args[i]; + } + signature+=(i>0?")":"()"); + + // Build param infos + for (i = 0; i < args.length; i++) + { + String param_desc = bundle.getString(signature + "[" + i + "]"); + parts=param_desc.split(" *: *",2); + if (Log.isDebugEnabled()) + Log.debug(parts[0]+": "+parts[1]); + pInfo[i] = new MBeanParameterInfo(parts[0].trim(), args[i], parts[1].trim()); + } + + // build the operation info + Method method = oClass.getMethod(method_name, types); + Class returnClass = method.getReturnType(); + _methods.put(signature, method); + if (convert) + _convert.add(signature); + + return new MBeanOperationInfo(method_name, description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact); + } + catch (Exception e) + { + Log.warn("Operation '"+signature+"'", e); + throw new IllegalArgumentException(e.toString()); + } + + } + +} diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ServerMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ServerMBean.java new file mode 100644 index 00000000000..cf375a4aded --- /dev/null +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ServerMBean.java @@ -0,0 +1,44 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jmx; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; + +/** + * + */ +public class ServerMBean extends ObjectMBean +{ + private final long startupTime; + private final Server server; + + public ServerMBean(Object managedObject) + { + super(managedObject); + startupTime = System.currentTimeMillis(); + server = (Server)managedObject; + } + + public Handler[] getContexts() + { + return server.getChildHandlersByClass(ContextHandler.class); + } + + public long getStartupTime() + { + return startupTime; + } +} diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/handler/ContextHandlerMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/handler/ContextHandlerMBean.java new file mode 100644 index 00000000000..6eef0b45c6b --- /dev/null +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/handler/ContextHandlerMBean.java @@ -0,0 +1,78 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jmx.handler; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.jmx.ObjectMBean; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Attributes; + +public class ContextHandlerMBean extends ObjectMBean +{ + public ContextHandlerMBean(Object managedObject) + { + super(managedObject); + } + + /* ------------------------------------------------------------ */ + public String getObjectNameBasis() + { + if (_managed!=null && _managed instanceof ContextHandler) + { + ContextHandler context = (ContextHandler)_managed; + String name = context.getDisplayName(); + if (name!=null) + return name; + + if (context.getBaseResource()!=null && context.getBaseResource().getName().length()>1) + return context.getBaseResource().getName(); + } + return super.getObjectNameBasis(); + } + + public Map getContextAttributes() + { + Map map = new HashMap(); + Attributes attrs = ((ContextHandler)_managed).getAttributes(); + Enumeration en = attrs.getAttributeNames(); + while (en.hasMoreElements()) + { + String name = (String)en.nextElement(); + Object value = attrs.getAttribute(name); + map.put(name,value); + } + return map; + } + + public void setContextAttribute(String name, Object value) + { + Attributes attrs = ((ContextHandler)_managed).getAttributes(); + attrs.setAttribute(name,value); + } + + public void setContextAttribute(String name, String value) + { + Attributes attrs = ((ContextHandler)_managed).getAttributes(); + attrs.setAttribute(name,value); + } + + public void removeContextAttribute(String name) + { + Attributes attrs = ((ContextHandler)_managed).getAttributes(); + attrs.removeAttribute(name); + } +} diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/servlet/HolderMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/servlet/HolderMBean.java new file mode 100644 index 00000000000..41dcb782530 --- /dev/null +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/servlet/HolderMBean.java @@ -0,0 +1,38 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jmx.servlet; + +import org.eclipse.jetty.jmx.ObjectMBean; +import org.eclipse.jetty.servlet.Holder; + +public class HolderMBean extends ObjectMBean +{ + public HolderMBean(Object managedObject) + { + super(managedObject); + } + + /* ------------------------------------------------------------ */ + public String getObjectNameBasis() + { + if (_managed!=null && _managed instanceof Holder) + { + Holder holder = (Holder)_managed; + String name = holder.getName(); + if (name!=null) + return name; + } + return super.getObjectNameBasis(); + } +} diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/webapp/WebAppContextMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/webapp/WebAppContextMBean.java new file mode 100644 index 00000000000..27c3d1cca8a --- /dev/null +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/webapp/WebAppContextMBean.java @@ -0,0 +1,43 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jmx.webapp; + +import org.eclipse.jetty.jmx.handler.ContextHandlerMBean; +import org.eclipse.jetty.webapp.WebAppContext; + +public class WebAppContextMBean extends ContextHandlerMBean +{ + + public WebAppContextMBean(Object managedObject) + { + super(managedObject); + } + + /* ------------------------------------------------------------ */ + public String getObjectNameBasis() + { + String basis = super.getObjectNameBasis(); + if (basis!=null) + return basis; + + if (_managed!=null && _managed instanceof WebAppContext) + { + WebAppContext context = (WebAppContext)_managed; + String name = context.getWar(); + if (name!=null) + return name; + } + return null; + } +} diff --git a/jetty-jmx/src/main/resources/org/eclipse/cometd/management/AbstractBayeux-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/cometd/management/AbstractBayeux-mbean.properties new file mode 100644 index 00000000000..9f16bcf1c57 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/cometd/management/AbstractBayeux-mbean.properties @@ -0,0 +1,9 @@ +AbstractBayeux: Implementation of the Bayeux protocol for cometd +channelCount: Number of channels +clientCount: Number of users +interval: The time in ms a client should delay between reconnects +logLevel: The log level. 0=none, 1=info, 2=debug +maxInterval: The maximum time in ms to wait between polls before timing out a client. +multiFrameInterval: The time in ms a client should delay between reconnects when multiple connections from the same browser are detected. +timeout: The time in ms a client should wait for a reply before timing out +directDeliver: Whether a published message should be delivered directly to subscribers, or a new message should be created that holds only supported fields diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/deployer/management/ContextDeployer-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/deployer/management/ContextDeployer-mbean.properties new file mode 100644 index 00000000000..1e590e4a741 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/deployer/management/ContextDeployer-mbean.properties @@ -0,0 +1,11 @@ +ContextDeployer: Deployer for runtime deploy/undeploy of webapps +contexts: MObject: The ContextHandlerCollection to which the deployer deploys +scanInterval: Object: The interval in seconds between scans of the deploy directory +configurationDir: Object:RO: The deploy directory +setConfigurationDir(org.eclipse.resource.Resource):ACTION:Set the deploy directory +setConfigurationDir(org.eclipse.resource.Resource)[0]:resource:The directory +setConfigurationDir(java.lang.String):ACTION:Set the deploy directory +setConfigurationDir(java.lang.String)[0]:dir:The directory +setConfigurationDir(java.io.File):ACTION:Set the deploy directory +setConfigurationDir(java.io.File)[0]:file:The directory +configurationManager: MObject:Source of property values for property name resolution in deployed config file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/deployer/management/WebAppDeployer-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/deployer/management/WebAppDeployer-mbean.properties new file mode 100644 index 00000000000..a1304e1e7a0 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/deployer/management/WebAppDeployer-mbean.properties @@ -0,0 +1,14 @@ +WebAppDeployer: Deployer for startup deployment of webapps +contexts: MObject: The ContextHandlerCollection to which the deployer deploys +allowDuplicates: Object:R0: Whether or not duplicate deployments are allowed +setAllowDuplicates(boolean):ACTION: Whether or not duplicate deployments are allowed +setAllowDuplicates(boolean)[0]:allowDuplicates: True allows duplicate webapps to be deployed while false does not +defaultsDescriptor: Object: The webdefault.xml descriptor to use for all webapps deployed by the deployer +configurationClasses: Object: The set of configuration classes to apply to the deployment of each webapp +webAppDir: Object: The directory where the webapps are to be found to deploy +extract: Object:RO: Whether or not to extract war files on deployment +setExtract(boolean):ACTION:Set whether or not to extract war files +setExtract(boolean)[0]:extract: True will extract war files while false will not +parentLoaderPriority: Object:RO: Whether to use j2se classloading order or servlet spec classloading order +setParentLoaderPriority(boolean):ACTION: Set which classloading paradigm to use +setParentLoaderPriority(boolean)[0]:parentPriorityClassLoading: True uses j2se classloading order while false uses servlet spec order diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/ContextHandler-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/ContextHandler-mbean.properties new file mode 100644 index 00000000000..f3e2c2a5cb6 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/ContextHandler-mbean.properties @@ -0,0 +1,28 @@ +ContextHandler: URI Context +aliases: True if alias checking is performed on resource +allowNullPathInfo: Checks if the /context is not redirected to /context/ +attributes: The attributes to set using class Attributes +classLoader: The context classLoader +classPath: RO: The file classpath +compactPath: True if URLs are compacted to replace the multiple '/'s with a single '/' +connectorNames: Names and ports of accepted connectors +contextAttributes: RO:MBean: Map of context attributes +contextPath: URI prefix of context +displayName: RO: Display name of the Context +errorHandler: MObject: The error handler to use for the context +eventListeners: The context event listeners +initParams: Initial Parameter map for the context +maxFormContentSize: The maximum content size +mimeTypes: The mime types allowed for the context +removeContextAttribute(java.lang.String): MBean:ACTION: remove context attribute +removeContextAttribute(java.lang.String)[0]: name: The attribute name +resourceBase: Document root for the context +setContextAttribute(java.lang.String,java.lang.Object): MBean:ACTION: Set context attribute +setContextAttribute(java.lang.String,java.lang.Object)[0]: name: The attribute name +setContextAttribute(java.lang.String,java.lang.Object)[1]: value: The attribute value +setContextAttribute(java.lang.String,java.lang.String): MBean:ACTION: Set context attribute +setContextAttribute(java.lang.String,java.lang.String)[0]: name: The attribute name +setContextAttribute(java.lang.String,java.lang.String)[1]: value: The attribute value +shutdown: False if this context is accepting new requests. True for graceful shutdown, which allows existing requests to complete +virtualHosts: Virtual hosts accepted by the context +welcomeFiles: Partial URIs of directory welcome files diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/ContextHandlerCollection-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/ContextHandlerCollection-mbean.properties new file mode 100644 index 00000000000..4ab1fef7cc9 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/ContextHandlerCollection-mbean.properties @@ -0,0 +1,2 @@ +ContextHandlerCollection: Context Handler Collection +mapContexts(): Update the mapping of context path to context \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerCollection-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerCollection-mbean.properties new file mode 100644 index 00000000000..37152f504d0 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerCollection-mbean.properties @@ -0,0 +1,2 @@ +HandlerCollection: Handler of multiple Handlers +handlers: MObject:Wrapped handlers diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerContainer-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerContainer-mbean.properties new file mode 100644 index 00000000000..fe404800b74 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerContainer-mbean.properties @@ -0,0 +1,2 @@ +HandlerContainer: Handler of multiple Handlers +childHandlers: MObject:RO:All contained handlers \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerWrapper-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerWrapper-mbean.properties new file mode 100644 index 00000000000..3395f649504 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/HandlerWrapper-mbean.properties @@ -0,0 +1,2 @@ +HandlerWrapper: Handler wrapping another Handler +handler: MObject:Wrapped handler diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/StatisticsHandler-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/StatisticsHandler-mbean.properties new file mode 100644 index 00000000000..76dd5084908 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/handler/management/StatisticsHandler-mbean.properties @@ -0,0 +1,17 @@ +StatisticsHandler: Request Statistics gathering +statsOnMs: Time in milliseconds stats have been collected for. +statsReset(): Reset statistics. +requests: Number of requests since statsReset() called. +requestsTimedout: Number of requests timed out since statsReset() called. +requestsResumed: Number of requests resumed since statsReset() called. +requestsActive: Number of requests currently active. +requestsActiveMin: Minimum number of active requests since statsReset() called. +requestsActiveMax: Maximum number of active requests since statsReset() called. +requestsDurationAve: Average duration of request handling in milliseconds since statsReset() called. +requestsDurationMin: Get minimum duration in milliseconds of request handling since statsReset() called. +requestsDurationMax: Get maximum duration in milliseconds of request handling since statsReset() called. +requestsDurationTotal: Get total duration in milliseconds of all request handling since statsReset() called. +requestsActiveDurationAve: Average duration of active request handling in milliseconds since statsReset() called. +requestsActiveDurationMin: Minimum duration of active request handling in milliseconds since statsReset() called. +requestsActiveDurationMax: Maximum duration of active request handling in milliseconds since statsReset() called. +requestsActiveDurationTotal: Total duration of active request handling in milliseconds since statsReset() called. \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/management/AbstractConnector-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/AbstractConnector-mbean.properties new file mode 100644 index 00000000000..5df8ca5b7c0 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/AbstractConnector-mbean.properties @@ -0,0 +1,18 @@ +AbstractConnector: Abstract implementation of the Connector interface. +acceptors: The number of acceptor threads. +acceptQueueSize: The size of the accept queue. +acceptorPriorityOffset: Priority offset of the acceptor threads. The priority is adjusted by this amount to either favor the acceptance of new threads and newly active connections or to favor the handling of already dispatched connections. +forwardedForHeader: The header name for forwarded for (default x-forwarded-for). +forwardedHostHeader: The header name for forwarded hosts (default x-forwarded-host) +forwardedServerHeader: The header name for forwarded server (default x-forwarded-server) +forwarded: Whether reverse proxy handling is on. True if this connector is checking the forwarded for/host/server headers. +host: Host name of the server. +hostHeader: Forced value for the host header. Only used if forwarded is true. +soLingerTime: Enable or disable SO_LINGER with the specified linger time in seconds. +reuseAddress: Whether the server socket will be opened in SO_REUSEADDR mode. +name: Name of the connector. +resolveNames: Whether or not to use DNS when handling forwards. +confidentialPort: Port to use for confidential redirections. +confidentialScheme: Scheme to use for confidential redirections. +integralPort: Port to use for integral redirections. +integralScheme: Scheme to use for integral redirections. diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Connector-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Connector-mbean.properties new file mode 100644 index 00000000000..818ede60667 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Connector-mbean.properties @@ -0,0 +1,29 @@ +Connector: HTTP Connector. +server: MObject:RO:The server for this connector +headerBufferSize: The size of the header buffer +requestBufferSize: The size of a request content buffer +responseBufferSize: The size of a response content buffer +integralPort: Port to use for integral redirections +integralScheme: Scheme to use for integral redirections +confidentialPort: Port to use for confidential redirections +confidentialScheme: Scheme to use for confidential redirections +host: Host name to accept connections on +port: TCP/IP port to accept connections on +maxIdleTime: Maximum time in ms that a connection can be idle before being closed +statsOn: True if statistics collection is turned on. +statsOnMs: Time in milliseconds stats have been collected for. +statsReset(): Reset statistics. +connections: Number of connections accepted by the server since statsReset() called. Undefined if setStatsOn(false). +connectionsOpen: Number of connections currently open that were opened since statsReset() called. Undefined if setStatsOn(false). +connectionsOpenMin: Minimim number of connections opened simultaneously since statsReset() called. Undefined if setStatsOn(false). +connectionsOpenMax: Maximum number of connections opened simultaneously since statsReset() called. Undefined if setStatsOn(false). +connectionsDurationAve: Average duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false). +connectionsDurationMin: Minimum duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false). +connectionsDurationMax: Maximum duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false). +connectionsDurationTotal: Total duration in milliseconds of all open connection since statsReset() called. Undefined if setStatsOn(false). +connectionsRequestsAve: Average number of requests per connection since statsReset() called. Undefined if setStatsOn(false). +connectionsRequestsMin: Minimum number of requests per connection since statsReset() called. Undefined if setStatsOn(false). +connectionsRequestsMax: Maximum number of requests per connection since statsReset() called. Undefined if setStatsOn(false). +requests: Number of requests since statsReset() called. Undefined if setStatsOn(false). +open(): Open the listening port +close(): Close the listening port (but allow existing connections to continue for graceful shutdown) \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Handler-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Handler-mbean.properties new file mode 100644 index 00000000000..eea633f9343 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Handler-mbean.properties @@ -0,0 +1,3 @@ +Handler: Jetty Handler. +server: MObject:RO:The Jetty server for this handler +destroy(): destroy associated resources (eg MBean) \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/management/NCSARequestLog-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/NCSARequestLog-mbean.properties new file mode 100644 index 00000000000..0c0e3400ffd --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/NCSARequestLog-mbean.properties @@ -0,0 +1,6 @@ +NCSARequestLog : NCSA standard format request log +filename : Filename of log +retainDays : Number of days that the log files are kept +append : Existing log files are appended to the new one +extended : Use the extended NCSA format +LogTimeZone : The timezone diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Server-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Server-mbean.properties new file mode 100644 index 00000000000..a633f3e995c --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/management/Server-mbean.properties @@ -0,0 +1,7 @@ +Server: Jetty HTTP Servlet server +connectors: MObject:HTTP Connectors for this server +version: RO: The version of this server +sendServerVersion: If true include the server version in HTTP headers +threadPool: MObject:The server Thread Pool +contexts: MMBean:RO:The contexts of this server +startupTime: MBean:RO:The startup time, in milliseconds, since January 1st 1970 diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/nio/management/SelectChannelConnector-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/nio/management/SelectChannelConnector-mbean.properties new file mode 100644 index 00000000000..b51fb28764f --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/nio/management/SelectChannelConnector-mbean.properties @@ -0,0 +1,3 @@ +SelectChannelConnector: HTTP connector using NIO ByteChannels and Selectors +lowResourcesConnections: The number of connections, which if exceeded represents low resources +lowResourcesMaxIdleTime: The period in ms that a connection may be idle when the connector has low resources, before it is closed. diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/AbstractSessionManager-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/AbstractSessionManager-mbean.properties new file mode 100644 index 00000000000..e54c1f608d4 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/AbstractSessionManager-mbean.properties @@ -0,0 +1,15 @@ +AbstractSessionManager: Abstract Session Manager +httpOnly: True if cookies use the http only flag +idManager: MObject:RO:The ID Manager instance +maxCookieAge: if greater than zero, the time in seconds a session cookie will last for +maxInactiveInterval: default maximim time in seconds a session may be idle +maxSessions: The maximum number of simultaneous sessions +minSessions: The minimum number of simultaneous sessions +refreshCookieAge: The time in seconds after which a session cookie is re-set +secureCookies: If true, the secure cookie flag is set on session cookies +sessionCookie: The set session cookie +sessionDomain: The domain of the session cookie or null for the default +sessionPath: The path of the session cookie or null for the default +sessions: The instantaneous number of sessions +sessionIdPathParameterName: The name to use for URL session tracking +resetStats(): Reset statistics diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/Context-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/Context-mbean.properties new file mode 100644 index 00000000000..5485f7672d3 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/Context-mbean.properties @@ -0,0 +1,4 @@ +Context: Servlet Context Handler +securityHandler: MObject: The context's security handler +servletHandler: MObject: The context's servlet handler +sessionHandler: MObject: The context's session handler diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/FilterMapping-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/FilterMapping-mbean.properties new file mode 100644 index 00000000000..afcbe2f032c --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/FilterMapping-mbean.properties @@ -0,0 +1,4 @@ +FilterMapping: Filter Mapping +filterName: RO:Filter Name +pathSpecs: RO:URL patterns +servletNames: RO:Servlet Names diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/Holder-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/Holder-mbean.properties new file mode 100644 index 00000000000..e45e145b391 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/Holder-mbean.properties @@ -0,0 +1,6 @@ +Holder: Holder +name: RO:Name +displayName: RO:Display Name +className: RO:Class Name +initParameters: RO:Initial parameters + diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletHandler-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletHandler-mbean.properties new file mode 100644 index 00000000000..9326c1906d6 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletHandler-mbean.properties @@ -0,0 +1,5 @@ +ServletHandler: Servlet Handler +servlets: MObject:RO:Servlets +servletMappings: MObject:RO:Mappings of servlets +filters: MObject:RO:Filters +filterMappings: MObject:RO:Mappings of filters diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletHolder-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletHolder-mbean.properties new file mode 100644 index 00000000000..38445467d3c --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletHolder-mbean.properties @@ -0,0 +1,4 @@ +ServletHolder: Servlet Holder +initOrder: Initialization order +runAsRole: Role to run servlet as +forcedPath: Forced servlet path diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletMapping-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletMapping-mbean.properties new file mode 100644 index 00000000000..efe07ff433c --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/servlet/management/ServletMapping-mbean.properties @@ -0,0 +1,3 @@ +ServletMapping: Servlet Mapping +servletName: RO:Servlet Name +pathSpecs: RO:URL patterns diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/management/LifeCycle-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/management/LifeCycle-mbean.properties new file mode 100644 index 00000000000..0c0ab61f3a9 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/management/LifeCycle-mbean.properties @@ -0,0 +1,9 @@ +LifeCycle: Startable object +start(): Starts the instance +stop(): Stops the instance +running: Instance is started or starting +started: Instance is started +starting: Instance is starting +stopping: Instance is stopping +stopped: Instance is stopped +failed: Instance is failed \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/Logger-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/Logger-mbean.properties new file mode 100644 index 00000000000..920cc192115 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/Logger-mbean.properties @@ -0,0 +1,2 @@ +Logger: Jetty Logging implementaton +debugEnabled: True if debug enabled diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/Slf4jLog-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/Slf4jLog-mbean.properties new file mode 100644 index 00000000000..58b10abec32 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/Slf4jLog-mbean.properties @@ -0,0 +1 @@ +Slf4jLog: SL4J log adapter diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/StdErrLog-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/StdErrLog-mbean.properties new file mode 100644 index 00000000000..16e56bb70be --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/management/StdErrLog-mbean.properties @@ -0,0 +1 @@ +StdErrLog: Log adapter that logs to stderr \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/BoundedThreadPool-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/BoundedThreadPool-mbean.properties new file mode 100644 index 00000000000..d460d5b47d6 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/BoundedThreadPool-mbean.properties @@ -0,0 +1,7 @@ +BoundedThreadPool: A thread pool with fixed bounds +minThreads: Minimum number of threads in the pool +maxThreads: Maximum number threads in the pool +name: Name of the thread pool +daemon: Are pool threads daemon threads +threadsPriority: Priority of threads +maxIdleTimeMs: Maximum time a thread may be idle in ms \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/QueuedThreadPool-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/QueuedThreadPool-mbean.properties new file mode 100644 index 00000000000..5b7ed50a06d --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/QueuedThreadPool-mbean.properties @@ -0,0 +1,9 @@ +QueuedThreadPool: A thread pool with no max bound by default +minThreads: Minimum number of threads in the pool +maxThreads: Maximum number threads in the pool +name: Name of the thread pool +daemon: Is pool thread using daemon thread +threadsPriority: The priority of threads in the pool +maxIdleTimeMs: Maximum time a thread may be idle in ms +queueSize: The pool threads size +spawnOrShrinkAt: The number of queued jobs (or idle threads) needed before the thread pool is grown (or shrunk) \ No newline at end of file diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/ThreadPool-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/ThreadPool-mbean.properties new file mode 100644 index 00000000000..9ee55bf71de --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/management/ThreadPool-mbean.properties @@ -0,0 +1,4 @@ +ThreadPool: Pool of threads +threads: RO:Number of Threads in pool +idleThreads: RO:Number of idle Threads in pool +lowOnThreads: RO:Indicates the pool is low on available threads. diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/webapp/management/WebAppContext-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/webapp/management/WebAppContext-mbean.properties new file mode 100644 index 00000000000..5a09672bff8 --- /dev/null +++ b/jetty-jmx/src/main/resources/org/eclipse/jetty/webapp/management/WebAppContext-mbean.properties @@ -0,0 +1,14 @@ +WebAppContext: Web Application ContextHandler +configurationClasses: Array of names of configuration classes +defaultsDescriptor: Default web.xml descriptor applied before standard web.xml +overrideDescriptor: Override web.xml descriptor applied after standard web.xml +serverClasses: Classes and packages hidden by the context classloader +systemClasses: Classes and packages given priority by context classloader +war: WAR file location +distributable: Is the web application distributable +extractWAR: Is the war file extraced on deploy +copyWebDir: Is the web application directory copied on deploy +parentLoaderPriority: Is the parent classloader given priority +descriptor: The standard web.xml descriptor +extraClasspath: Extra classpath for the context classloader +tempDirectory: Temporary directory location \ No newline at end of file diff --git a/jetty-jmx/src/test/java/com/acme/Base.java b/jetty-jmx/src/test/java/com/acme/Base.java new file mode 100644 index 00000000000..3c9d94305fe --- /dev/null +++ b/jetty-jmx/src/test/java/com/acme/Base.java @@ -0,0 +1,87 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; + +public class Base extends AbstractLifeCycle +{ + String name; + int value; + String[] messages; + + /* ------------------------------------------------------------ */ + /** + * @return Returns the messages. + */ + public String[] getMessages() + { + return messages; + } + /* ------------------------------------------------------------ */ + /** + * @param messages The messages to set. + */ + public void setMessages(String[] messages) + { + this.messages = messages; + } + /* ------------------------------------------------------------ */ + /** + * @return Returns the name. + */ + public String getName() + { + return name; + } + /* ------------------------------------------------------------ */ + /** + * @param name The name to set. + */ + public void setName(String name) + { + this.name = name; + } + /* ------------------------------------------------------------ */ + /** + * @return Returns the value. + */ + public int getValue() + { + return value; + } + + /* ------------------------------------------------------------ */ + /** + * @param value The value to set. + */ + public void setValue(int value) + { + this.value = value; + } + + /* ------------------------------------------------------------ */ + public void doSomething(int arg) + { + System.err.println("doSomething "+arg); + } + + /* ------------------------------------------------------------ */ + public String findSomething(int arg) + { + return ("found "+arg); + } + + +} diff --git a/jetty-jmx/src/test/java/com/acme/Derived.java b/jetty-jmx/src/test/java/com/acme/Derived.java new file mode 100644 index 00000000000..4e5bd41339d --- /dev/null +++ b/jetty-jmx/src/test/java/com/acme/Derived.java @@ -0,0 +1,45 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + + +public class Derived extends Base implements Signature +{ + String fname="Full Name"; + + public String getFullName() + { + return fname; + } + + public void setFullName(String name) + { + fname=name; + } + + public void publish() + { + System.err.println("publish"); + } + + public void doodle(String doodle) + { + System.err.println("doodle "+doodle); + } + + public void somethingElse() + { + + } +} diff --git a/jetty-jmx/src/test/java/com/acme/Signature.java b/jetty-jmx/src/test/java/com/acme/Signature.java new file mode 100644 index 00000000000..85528228dc6 --- /dev/null +++ b/jetty-jmx/src/test/java/com/acme/Signature.java @@ -0,0 +1,21 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + +public interface Signature +{ + String getFullName(); + + void publish(); +} diff --git a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java new file mode 100644 index 00000000000..28b52a60e85 --- /dev/null +++ b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java @@ -0,0 +1,59 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jmx; + + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Server; + +import com.acme.Derived; + +public class ObjectMBeanTest extends TestCase +{ + public ObjectMBeanTest(String arg0) + { + super(arg0); + } + + public static void main(String[] args) + { + junit.textui.TestRunner.run(ObjectMBeanTest.class); + } + + protected void setUp() throws Exception + { + super.setUp(); + } + + protected void tearDown() throws Exception + { + super.tearDown(); + } + + public void testMbeanInfo() + { + Derived derived = new Derived(); + ObjectMBean mbean = new ObjectMBean(derived); + assertTrue(mbean.getMBeanInfo()!=null); // TODO do more than just run it + } + + public void testMbeanFor() + { + Derived derived = new Derived(); + assertTrue(ObjectMBean.mbeanFor(derived)!=null); // TODO do more than just run it + Server server = new Server(); + assertTrue(ObjectMBean.mbeanFor(server)!=null); // TODO do more than just run it + } +} diff --git a/jetty-jmx/src/test/resources/com/acme/management/Derived-mbean.properties b/jetty-jmx/src/test/resources/com/acme/management/Derived-mbean.properties new file mode 100644 index 00000000000..8ebd577bde1 --- /dev/null +++ b/jetty-jmx/src/test/resources/com/acme/management/Derived-mbean.properties @@ -0,0 +1,5 @@ +Derived: Test the mbean stuff +fullName : The full name of something +publish() : publish something +doodle(java.lang.String) : ACTION:Doodle something +doodle(java.lang.String)[0] : argname: A description of the argument. \ No newline at end of file diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml new file mode 100644 index 00000000000..d22f77a7ec2 --- /dev/null +++ b/jetty-jndi/pom.xml @@ -0,0 +1,87 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-jndi + Jetty :: JNDI Naming + JNDI spi impl for java namespace. + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + J2SE-1.5 + !org.eclipse.jetty.jndi.*,* + org.eclipse.jetty.jndi + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + + + junit + junit + test + + + org.eclipse.jetty + jetty-webapp + ${project.version} + provided + + + javax.mail + mail + ${mail-version} + + + javax.activation + activation + + + + + + + below-jdk1.6 + + !1.6 + + + + javax.activation + activation + ${activation-version} + + + + + diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java new file mode 100644 index 00000000000..ca00153e1b0 --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java @@ -0,0 +1,192 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi; + + +import java.util.Hashtable; +import java.util.WeakHashMap; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.Reference; +import javax.naming.StringRefAddr; +import javax.naming.spi.ObjectFactory; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; + + + +/** + * ContextFactory.java + * + * This is an object factory that produces a jndi naming + * context based on a classloader. + * + * It is used for the java:comp context. + * + * This object factory is bound at java:comp. When a + * lookup arrives for java:comp, this object factory + * is invoked and will return a context specific to + * the caller's environment (so producing the java:comp/env + * specific to a webapp). + * + * The context selected is based on classloaders. First + * we try looking in at the classloader that is associated + * with the current webapp context (if there is one). If + * not, we use the thread context classloader. + * + * Created: Fri Jun 27 09:26:40 2003 + * + * + * + */ +public class ContextFactory implements ObjectFactory +{ + /** + * Map of classloaders to contexts. + */ + private static WeakHashMap _contextMap; + + /** + * Threadlocal for injecting a context to use + * instead of looking up the map. + */ + private static ThreadLocal _threadContext; + + static + { + _contextMap = new WeakHashMap(); + _threadContext = new ThreadLocal(); + } + + + + /** + * Find or create a context which pertains to a classloader. + * + * We use either the classloader for the current ContextHandler if + * we are handling a request, OR we use the thread context classloader + * if we are not processing a request. + * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable) + */ + public Object getObjectInstance (Object obj, + Name name, + Context nameCtx, + Hashtable env) + throws Exception + { + //First, see if we have had a context injected into us to use. + Context ctx = (Context)_threadContext.get(); + if (ctx != null) + { + if(Log.isDebugEnabled()) Log.debug("Using the Context that is bound on the thread"); + return ctx; + } + + // Next, see if we are in a webapp context, if we are, use + // the classloader of the webapp to find the right jndi comp context + ClassLoader loader = null; + if (ContextHandler.getCurrentContext() != null) + { + loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader(); + } + + + if (loader != null) + { + if (Log.isDebugEnabled()) Log.debug("Using classloader of current org.eclipse.jetty.server.handler.ContextHandler"); + } + else + { + //Not already in a webapp context, in that case, we try the + //curren't thread's classloader instead + loader = Thread.currentThread().getContextClassLoader(); + if (Log.isDebugEnabled()) Log.debug("Using thread context classloader"); + } + + //Get the context matching the classloader + ctx = (Context)_contextMap.get(loader); + + //The map does not contain an entry for this classloader + if (ctx == null) + { + //Check if a parent classloader has created the context + ctx = getParentClassLoaderContext(loader); + + //Didn't find a context to match any of the ancestors + //of the classloader, so make a context + if (ctx == null) + { + Reference ref = (Reference)obj; + StringRefAddr parserAddr = (StringRefAddr)ref.get("parser"); + String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent()); + NameParser parser = (NameParser)(parserClassName==null?null:loader.loadClass(parserClassName).newInstance()); + + ctx = new NamingContext (env, + name.get(0), + nameCtx, + parser); + if(Log.isDebugEnabled())Log.debug("No entry for classloader: "+loader); + _contextMap.put (loader, ctx); + } + } + + return ctx; + } + + /** + * Keep trying ancestors of the given classloader to find one to which + * the context is bound. + * @param loader + * @return + */ + public Context getParentClassLoaderContext (ClassLoader loader) + { + Context ctx = null; + ClassLoader cl = loader; + for (cl = cl.getParent(); (cl != null) && (ctx == null); cl = cl.getParent()) + { + ctx = (Context)_contextMap.get(cl); + } + + return ctx; + } + + + /** + * Associate the given Context with the current thread. + * resetComponentContext method should be called to reset the context. + * @param ctx the context to associate to the current thread. + * @return the previous context associated on the thread (can be null) + */ + public static Context setComponentContext(final Context ctx) + { + Context previous = (Context)_threadContext.get(); + _threadContext.set(ctx); + return previous; + } + + /** + * Set back the context with the given value. + * Don't return the previous context, use setComponentContext() method for this. + * @param ctx the context to associate to the current thread. + */ + public static void resetComponentContext(final Context ctx) + { + _threadContext.set(ctx); + } + +} diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/InitialContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/InitialContextFactory.java new file mode 100644 index 00000000000..e72ad69545d --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/InitialContextFactory.java @@ -0,0 +1,79 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi; + + +import java.util.Hashtable; +import java.util.Properties; + +import javax.naming.CompoundName; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.NamingException; + +import org.eclipse.jetty.jndi.local.localContextRoot; +import org.eclipse.jetty.util.log.Log; + + +/*------------------------------------------------*/ +/** + * InitialContextFactory.java + * + * Factory for the default InitialContext. + * Created: Tue Jul 1 19:08:08 2003 + * + * + * @version 1.0 + */ +public class InitialContextFactory implements javax.naming.spi.InitialContextFactory +{ + public static class DefaultParser implements NameParser + { + static Properties syntax = new Properties(); + static + { + syntax.put("jndi.syntax.direction", "left_to_right"); + syntax.put("jndi.syntax.separator", "/"); + syntax.put("jndi.syntax.ignorecase", "false"); + } + public Name parse (String name) + throws NamingException + { + return new CompoundName (name, syntax); + } + }; + + + + /*------------------------------------------------*/ + /** + * Get Context that has access to default Namespace. + * This method won't be called if a name URL beginning + * with java: is passed to an InitialContext. + * + * @see org.eclipse.jetty.jndi.java.javaURLContextFactory + * @param env a Hashtable value + * @return a Context value + */ + public Context getInitialContext(Hashtable env) + { + Log.debug("InitialContextFactory.getInitialContext()"); + + Context ctx = new localContextRoot(env); + if(Log.isDebugEnabled())Log.debug("Created initial context delegate for local namespace:"+ctx); + + return ctx; + } +} diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java new file mode 100644 index 00000000000..a8253a802c8 --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java @@ -0,0 +1,1446 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi; + + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.NoSuchElementException; + +import javax.naming.Binding; +import javax.naming.CompoundName; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.LinkRef; +import javax.naming.Name; +import javax.naming.NameAlreadyBoundException; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.NotContextException; +import javax.naming.OperationNotSupportedException; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.naming.spi.NamingManager; + +import org.eclipse.jetty.util.log.Log; + + +/*------------------------------------------------*/ +/** NamingContext + *

    Implementation of Context interface. + * + *

    Notes

    + *

    All Names are expected to be Compound, not Composite. + * + *

    Usage

    + *
    + */
    +/*
    +* 
    +* +* @see +* +* +* @version 1.0 +*/ +public class NamingContext implements Context, Cloneable +{ + + public static final String LOCK_PROPERTY = "org.eclipse.jndi.lock"; + public static final String UNLOCK_PROPERTY = "org.eclipse.jndi.unlock"; + + public static final Enumeration EMPTY_ENUM = new Enumeration () + { + public boolean hasMoreElements() + { + return false; + } + + public Object nextElement () + { + throw new NoSuchElementException(); + } + }; + + + protected Context _parent = null; + protected String _name = null; + protected Hashtable _env = null; + protected Hashtable _bindings = new Hashtable(); + protected NameParser _parser = null; + + + + /*------------------------------------------------*/ + /** NameEnumeration + *

    Implementation of NamingEnumeration interface. + * + *

    Notes

    + *

    Used for returning results of Context.list(); + * + *

    Usage

    + *
    +     */
    +    /*
    +     * 
    + * + * @see + * + */ + public class NameEnumeration implements NamingEnumeration + { + Enumeration _delegate; + + public NameEnumeration (Enumeration e) + { + _delegate = e; + } + + public void close() + throws NamingException + { + } + + public boolean hasMore () + throws NamingException + { + return _delegate.hasMoreElements(); + } + + public Object next() + throws NamingException + { + Binding b = (Binding)_delegate.nextElement(); + return new NameClassPair (b.getName(), b.getClassName(), true); + } + + public boolean hasMoreElements() + { + return _delegate.hasMoreElements(); + } + + public Object nextElement() + { + Binding b = (Binding)_delegate.nextElement(); + return new NameClassPair (b.getName(), b.getClassName(), true); + } + } + + + + + + + /*------------------------------------------------*/ + /** BindingEnumeration + *

    Implementation of NamingEnumeration + * + *

    Notes

    + *

    Used to return results of Context.listBindings(); + * + *

    Usage

    + *
    +     */
    +    /*
    +     * 
    + * + * @see + * + */ + public class BindingEnumeration implements NamingEnumeration + { + Enumeration _delegate; + + public BindingEnumeration (Enumeration e) + { + _delegate = e; + } + + public void close() + throws NamingException + { + } + + public boolean hasMore () + throws NamingException + { + return _delegate.hasMoreElements(); + } + + public Object next() + throws NamingException + { + Binding b = (Binding)_delegate.nextElement(); + return new Binding (b.getName(), b.getClassName(), b.getObject(), true); + } + + public boolean hasMoreElements() + { + return _delegate.hasMoreElements(); + } + + public Object nextElement() + { + Binding b = (Binding)_delegate.nextElement(); + return new Binding (b.getName(), b.getClassName(), b.getObject(),true); + } + } + + + + /*------------------------------------------------*/ + /** + * Constructor + * + * @param env environment properties + * @param name relative name of this context + * @param parent immediate ancestor Context (can be null) + * @param parser NameParser for this Context + */ + public NamingContext(Hashtable env, + String name, + Context parent, + NameParser parser) + { + if (env == null) + _env = new Hashtable(); + else + _env = new Hashtable(env); + _name = name; + _parent = parent; + _parser = parser; + } + + + + /*------------------------------------------------*/ + /** + * Creates a new NamingContext instance. + * + * @param env a Hashtable value + */ + public NamingContext (Hashtable env) + { + if (env == null) + _env = new Hashtable(); + else + _env = new Hashtable(env); + } + + + + + /*------------------------------------------------*/ + /** + * Constructor + * + */ + public NamingContext () + { + _env = new Hashtable(); + } + + + /*------------------------------------------------*/ + /** + * Clone this NamingContext + * + * @return copy of this NamingContext + * @exception CloneNotSupportedException if an error occurs + */ + public Object clone () + throws CloneNotSupportedException + { + NamingContext ctx = (NamingContext)super.clone(); + ctx._env = (Hashtable)_env.clone(); + ctx._bindings = (Hashtable)_bindings.clone(); + return ctx; + } + + + /*------------------------------------------------*/ + /** + * Getter for _name + * + * @return name of this Context (relative, not absolute) + */ + public String getName () + { + return _name; + } + + /*------------------------------------------------*/ + /** + * Getter for _parent + * + * @return parent Context + */ + public Context getParent() + { + return _parent; + } + + /*------------------------------------------------*/ + /** + * Setter for _parser + * + * + */ + public void setNameParser (NameParser parser) + { + _parser = parser; + } + + + + /*------------------------------------------------*/ + /** + * Bind a name to an object + * + * @param name Name of the object + * @param obj object to bind + * @exception NamingException if an error occurs + */ + public void bind(Name name, Object obj) + throws NamingException + { + if (isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just bind it + if (cname.size() == 1) + { + //get the object to be bound + Object objToBind = NamingManager.getStateToBind(obj, name,this, null); + // Check for Referenceable + if (objToBind instanceof Referenceable) + { + objToBind = ((Referenceable)objToBind).getReference(); + } + //anything else we should be able to bind directly + + Binding binding = getBinding (cname); + if (binding == null) + addBinding (cname, objToBind); + else + throw new NameAlreadyBoundException (cname.toString()); + } + else + { + if(Log.isDebugEnabled())Log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + //walk down the subcontext hierarchy + //need to ignore trailing empty "" name components + + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + + Binding binding = getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (firstComponent+ " is not bound"); + + ctx = binding.getObject(); + + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + + if (ctx instanceof Context) + { + ((Context)ctx).bind (cname.getSuffix(1), obj); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } + } + + + + /*------------------------------------------------*/ + /** + * Bind a name (as a String) to an object + * + * @param name a String value + * @param obj an Object value + * @exception NamingException if an error occurs + */ + public void bind(String name, Object obj) + throws NamingException + { + bind (_parser.parse(name), obj); + } + + + /*------------------------------------------------*/ + /** + * Create a context as a child of this one + * + * @param name a Name value + * @return a Context value + * @exception NamingException if an error occurs + */ + public Context createSubcontext (Name name) + throws NamingException + { + if (isLocked()) + { + NamingException ne = new NamingException ("This context is immutable"); + ne.setRemainingName(name); + throw ne; + } + + + + Name cname = toCanonicalName (name); + + if (cname == null) + throw new NamingException ("Name is null"); + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + if (cname.size() == 1) + { + //not permitted to bind if something already bound at that name + Binding binding = getBinding (cname); + if (binding != null) + throw new NameAlreadyBoundException (cname.toString()); + + Context ctx = new NamingContext ((Hashtable)_env.clone(), cname.get(0), this, _parser); + addBinding (cname, ctx); + return ctx; + } + + + //If the name has multiple subcontexts, walk the hierarchy by + //fetching the first one. All intermediate subcontexts in the + //name must already exist. + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (firstComponent + " is not bound"); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + if(Log.isDebugEnabled())Log.debug("Object bound at "+firstComponent +" is a Reference"); + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + return ((Context)ctx).createSubcontext (cname.getSuffix(1)); + } + else + throw new NotContextException (firstComponent +" is not a Context"); + } + + + /*------------------------------------------------*/ + /** + * Create a Context as a child of this one + * + * @param name a String value + * @return a Context value + * @exception NamingException if an error occurs + */ + public Context createSubcontext (String name) + throws NamingException + { + return createSubcontext(_parser.parse(name)); + } + + + + /*------------------------------------------------*/ + /** + * Not supported + * + * @param name name of subcontext to remove + * @exception NamingException if an error occurs + */ + public void destroySubcontext (String name) + throws NamingException + { + removeBinding(_parser.parse(name)); + } + + + + /*------------------------------------------------*/ + /** + * Not supported + * + * @param name name of subcontext to remove + * @exception NamingException if an error occurs + */ + public void destroySubcontext (Name name) + throws NamingException + { + removeBinding(name); + } + + /*------------------------------------------------*/ + /** + * Lookup a binding by name + * + * @param name name of bound object + * @exception NamingException if an error occurs + */ + public Object lookup(Name name) + throws NamingException + { + if(Log.isDebugEnabled())Log.debug("Looking up name=\""+name+"\""); + Name cname = toCanonicalName(name); + + if ((cname == null) || (cname.size() == 0)) + { + Log.debug("Null or empty name, returning copy of this context"); + NamingContext ctx = new NamingContext (_env, _name, _parent, _parser); + ctx._bindings = _bindings; + return ctx; + } + + + + if (cname.size() == 1) + { + Binding binding = getBinding (cname); + if (binding == null) + { + NameNotFoundException nnfe = new NameNotFoundException(); + nnfe.setRemainingName(cname); + throw nnfe; + } + + + Object o = binding.getObject(); + + //handle links by looking up the link + if (o instanceof LinkRef) + { + //if link name starts with ./ it is relative to current context + String linkName = ((LinkRef)o).getLinkName(); + if (linkName.startsWith("./")) + return this.lookup (linkName.substring(2)); + else + { + //link name is absolute + InitialContext ictx = new InitialContext(); + return ictx.lookup (linkName); + } + } + else if (o instanceof Reference) + { + //deference the object + try + { + return NamingManager.getObjectInstance(o, cname, this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + else + return o; + } + + //it is a multipart name, recurse to the first subcontext + + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + + Binding binding = getBinding (firstComponent); + if (binding == null) + { + NameNotFoundException nnfe = new NameNotFoundException(); + nnfe.setRemainingName(cname); + throw nnfe; + } + + //as we have bound a reference to an object factory + //for the component specific contexts + //at "comp" we need to resolve the reference + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).lookup (cname.getSuffix(1)); + } + + + /*------------------------------------------------*/ + /** + * Lookup binding of an object by name + * + * @param name name of bound object + * @return object bound to name + * @exception NamingException if an error occurs + */ + public Object lookup (String name) + throws NamingException + { + return lookup (_parser.parse(name)); + } + + + + /*------------------------------------------------*/ + /** + * Lookup link bound to name + * + * @param name name of link binding + * @return LinkRef or plain object bound at name + * @exception NamingException if an error occurs + */ + public Object lookupLink (Name name) + throws NamingException + { + Name cname = toCanonicalName(name); + + if (cname == null) + { + NamingContext ctx = new NamingContext (_env, _name, _parent, _parser); + ctx._bindings = _bindings; + return ctx; + } + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + if (cname.size() == 1) + { + Binding binding = getBinding (cname); + if (binding == null) + throw new NameNotFoundException(); + + Object o = binding.getObject(); + + //handle links by looking up the link + if (o instanceof Reference) + { + //deference the object + try + { + return NamingManager.getObjectInstance(o, cname.getPrefix(1), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + else + { + //object is either a LinkRef which we don't dereference + //or a plain object in which case spec says we return it + return o; + } + } + + + //it is a multipart name, recurse to the first subcontext + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).lookup (cname.getSuffix(1)); + } + + + /*------------------------------------------------*/ + /** + * Lookup link bound to name + * + * @param name name of link binding + * @return LinkRef or plain object bound at name + * @exception NamingException if an error occurs + */ + public Object lookupLink (String name) + throws NamingException + { + return lookupLink (_parser.parse(name)); + } + + + /*------------------------------------------------*/ + /** + * List all names bound at Context named by Name + * + * @param name a Name value + * @return a NamingEnumeration value + * @exception NamingException if an error occurs + */ + public NamingEnumeration list(Name name) + throws NamingException + { + if(Log.isDebugEnabled())Log.debug("list() on Context="+getName()+" for name="+name); + Name cname = toCanonicalName(name); + + + + if (cname == null) + { + return new NameEnumeration(EMPTY_ENUM); + } + + + if (cname.size() == 0) + { + return new NameEnumeration (_bindings.elements()); + } + + + + //multipart name + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + if(Log.isDebugEnabled())Log.debug("Dereferencing Reference for "+name.get(0)); + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).list (cname.getSuffix(1)); + } + + + /*------------------------------------------------*/ + /** + * List all names bound at Context named by Name + * + * @param name a Name value + * @return a NamingEnumeration value + * @exception NamingException if an error occurs + */ + public NamingEnumeration list(String name) + throws NamingException + { + return list(_parser.parse(name)); + } + + + + /*------------------------------------------------*/ + /** + * List all Bindings present at Context named by Name + * + * @param name a Name value + * @return a NamingEnumeration value + * @exception NamingException if an error occurs + */ + public NamingEnumeration listBindings(Name name) + throws NamingException + { + Name cname = toCanonicalName (name); + + if (cname == null) + { + return new BindingEnumeration(EMPTY_ENUM); + } + + if (cname.size() == 0) + { + return new BindingEnumeration (_bindings.elements()); + } + + + + //multipart name + String firstComponent = cname.get(0); + Object ctx = null; + + //if a name has a leading "/" it is parsed as "" so ignore it by staying + //at this level in the tree + if (firstComponent.equals("")) + ctx = this; + else + { + //it is a non-empty name component + Binding binding = getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).listBindings (cname.getSuffix(1)); + } + + + + /*------------------------------------------------*/ + /** + * List all Bindings at Name + * + * @param name a String value + * @return a NamingEnumeration value + * @exception NamingException if an error occurs + */ + public NamingEnumeration listBindings(String name) + throws NamingException + { + return listBindings (_parser.parse(name)); + } + + + /*------------------------------------------------*/ + /** + * Overwrite or create a binding + * + * @param name a Name value + * @param obj an Object value + * @exception NamingException if an error occurs + */ + public void rebind(Name name, + Object obj) + throws NamingException + { + if (isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just bind it + if (cname.size() == 1) + { + //check if it is a Referenceable + Object objToBind = NamingManager.getStateToBind(obj, name, this, null); + if (objToBind instanceof Referenceable) + { + objToBind = ((Referenceable)objToBind).getReference(); + } + addBinding (cname, objToBind); + } + else + { + //walk down the subcontext hierarchy + if(Log.isDebugEnabled())Log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + String firstComponent = cname.get(0); + Object ctx = null; + + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = getBinding (name.get(0)); + if (binding == null) + throw new NameNotFoundException (name.get(0)+ " is not bound"); + + ctx = binding.getObject(); + + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + ((Context)ctx).rebind (cname.getSuffix(1), obj); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } + } + + + /*------------------------------------------------*/ + /** + * Overwrite or create a binding from Name to Object + * + * @param name a String value + * @param obj an Object value + * @exception NamingException if an error occurs + */ + public void rebind (String name, + Object obj) + throws NamingException + { + rebind (_parser.parse(name), obj); + } + + /*------------------------------------------------*/ + /** + * Not supported. + * + * @param name a String value + * @exception NamingException if an error occurs + */ + public void unbind (String name) + throws NamingException + { + unbind(_parser.parse(name)); + } + + /*------------------------------------------------*/ + /** + * Not supported. + * + * @param name a String value + * @exception NamingException if an error occurs + */ + public void unbind (Name name) + throws NamingException + { + if (name.size() == 0) + return; + + + if (isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just unbind it + if (cname.size() == 1) + { + removeBinding (cname); + } + else + { + //walk down the subcontext hierarchy + if(Log.isDebugEnabled())Log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + String firstComponent = cname.get(0); + Object ctx = null; + + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = getBinding (name.get(0)); + if (binding == null) + throw new NameNotFoundException (name.get(0)+ " is not bound"); + + ctx = binding.getObject(); + + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + ((Context)ctx).unbind (cname.getSuffix(1)); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } + + } + + /*------------------------------------------------*/ + /** + * Not supported + * + * @param oldName a Name value + * @param newName a Name value + * @exception NamingException if an error occurs + */ + public void rename(Name oldName, + Name newName) + throws NamingException + { + throw new OperationNotSupportedException(); + } + + + /*------------------------------------------------*/ + /** + * Not supported + * + * @param oldName a Name value + * @param newName a Name value + * @exception NamingException if an error occurs + */ public void rename(String oldName, + String newName) + throws NamingException + { + throw new OperationNotSupportedException(); + } + + + + /*------------------------------------------------*/ + /** Join two names together. These are treated as + * CompoundNames. + * + * @param name a Name value + * @param prefix a Name value + * @return a Name value + * @exception NamingException if an error occurs + */ + public Name composeName(Name name, + Name prefix) + throws NamingException + { + if (name == null) + throw new NamingException ("Name cannot be null"); + if (prefix == null) + throw new NamingException ("Prefix cannot be null"); + + Name compoundName = (CompoundName)prefix.clone(); + compoundName.addAll (name); + return compoundName; + } + + + + /*------------------------------------------------*/ + /** Join two names together. These are treated as + * CompoundNames. + * + * @param name a Name value + * @param prefix a Name value + * @return a Name value + * @exception NamingException if an error occurs + */ + public String composeName (String name, + String prefix) + throws NamingException + { + if (name == null) + throw new NamingException ("Name cannot be null"); + if (prefix == null) + throw new NamingException ("Prefix cannot be null"); + + Name compoundName = _parser.parse(prefix); + compoundName.add (name); + return compoundName.toString(); + } + + + /*------------------------------------------------*/ + /** + * Do nothing + * + * @exception NamingException if an error occurs + */ + public void close () + throws NamingException + { + + + } + + + /*------------------------------------------------*/ + /** + * Return a NameParser for this Context. + * + * @param name a Name value + * @return a NameParser value + */ + public NameParser getNameParser (Name name) + { + return _parser; + } + + /*------------------------------------------------*/ + /** + * Return a NameParser for this Context. + * + * @param name a Name value + * @return a NameParser value + */ + public NameParser getNameParser (String name) + { + return _parser; + } + + + /*------------------------------------------------*/ + /** + * Get the full name of this Context node + * by visiting it's ancestors back to root. + * + * NOTE: if this Context has a URL namespace then + * the URL prefix will be missing + * + * @return the full name of this Context + * @exception NamingException if an error occurs + */ + public String getNameInNamespace () + throws NamingException + { + Name name = _parser.parse(""); + + NamingContext c = this; + while (c != null) + { + String str = c.getName(); + if (str != null) + name.add(0, str); + c = (NamingContext)c.getParent(); + } + return name.toString(); + } + + + /*------------------------------------------------*/ + /** + * Add an environment setting to this Context + * + * @param propName name of the property to add + * @param propVal value of the property to add + * @return propVal or previous value of the property + * @exception NamingException if an error occurs + */ + public Object addToEnvironment(String propName, + Object propVal) + throws NamingException + { + if (isLocked() && !(propName.equals(UNLOCK_PROPERTY))) + throw new NamingException ("This context is immutable"); + + return _env.put (propName, propVal); + } + + + /*------------------------------------------------*/ + /** + * Remove a property from this Context's environment. + * + * @param propName name of property to remove + * @return value of property or null if it didn't exist + * @exception NamingException if an error occurs + */ + public Object removeFromEnvironment(String propName) + throws NamingException + { + if (isLocked()) + throw new NamingException ("This context is immutable"); + + return _env.remove (propName); + } + + + /*------------------------------------------------*/ + /** + * Get the environment of this Context. + * + * @return a copy of the environment of this Context. + */ + public Hashtable getEnvironment () + { + return (Hashtable)_env.clone(); + } + + + /*------------------------------------------------*/ + /** + * Add a name to object binding to this Context. + * + * @param name a Name value + * @param obj an Object value + */ + protected void addBinding (Name name, Object obj) + { + String key = name.toString(); + if(Log.isDebugEnabled())Log.debug("Adding binding with key="+key+" obj="+obj+" for context="+_name); + _bindings.put (key, new Binding (key, obj)); + } + + /*------------------------------------------------*/ + /** + * Get a name to object binding from this Context + * + * @param name a Name value + * @return a Binding value + */ + protected Binding getBinding (Name name) + { + if(Log.isDebugEnabled())Log.debug("Looking up binding for "+name.toString()+" for context="+_name); + return (Binding) _bindings.get(name.toString()); + } + + + /*------------------------------------------------*/ + /** + * Get a name to object binding from this Context + * + * @param name as a String + * @return null or the Binding + */ + protected Binding getBinding (String name) + { + if(Log.isDebugEnabled())Log.debug("Looking up binding for "+name+" for context="+_name); + return (Binding) _bindings.get(name); + } + + + protected void removeBinding (Name name) + { + String key = name.toString(); + if (Log.isDebugEnabled()) Log.debug("Removing binding with key="+key); + _bindings.remove(key); + } + + /*------------------------------------------------*/ + /** + * Remove leading or trailing empty components from + * name. Eg "/comp/env/" -> "comp/env" + * + * @param name the name to normalize + * @return normalized name + */ + public Name toCanonicalName (Name name) + { + Name canonicalName = name; + + if (name != null) + { + if (canonicalName.size() > 1) + { + if (canonicalName.get(0).equals("")) + canonicalName = canonicalName.getSuffix(1); + + + if (canonicalName.get(canonicalName.size()-1).equals("")) + canonicalName = canonicalName.getPrefix(canonicalName.size()-1); + + } + } + + return canonicalName; + } + + private boolean isLocked() + { + if ((_env.get(LOCK_PROPERTY) == null) && (_env.get(UNLOCK_PROPERTY) == null)) + return false; + + Object lockKey = _env.get(LOCK_PROPERTY); + Object unlockKey = _env.get(UNLOCK_PROPERTY); + + if ((lockKey != null) && (unlockKey != null) && (lockKey.equals(unlockKey))) + return false; + return true; + } + +} diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingUtil.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingUtil.java new file mode 100644 index 00000000000..df635a6eabc --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingUtil.java @@ -0,0 +1,139 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi; + +import java.util.HashMap; +import java.util.Map; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameNotFoundException; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +import org.eclipse.jetty.util.log.Log; + + +/** + * Util.java + * + * + * Created: Tue Jul 1 18:26:17 2003 + * + * + * @version 1.0 + */ +public class NamingUtil +{ + + /* ------------------------------------------------------------ */ + /** + * Bind an object to a context ensuring all subcontexts + * are created if necessary + * + * @param ctx the context into which to bind + * @param name the name relative to context to bind + * @param obj the object to be bound + * @exception NamingException if an error occurs + */ + public static Context bind (Context ctx, String nameStr, Object obj) + throws NamingException + { + Name name = ctx.getNameParser("").parse(nameStr); + + //no name, nothing to do + if (name.size() == 0) + return null; + + Context subCtx = ctx; + + //last component of the name will be the name to bind + for (int i=0; i < name.size() - 1; i++) + { + try + { + subCtx = (Context)subCtx.lookup (name.get(i)); + if(Log.isDebugEnabled())Log.debug("Subcontext "+name.get(i)+" already exists"); + } + catch (NameNotFoundException e) + { + subCtx = subCtx.createSubcontext(name.get(i)); + if(Log.isDebugEnabled())Log.debug("Subcontext "+name.get(i)+" created"); + } + } + + subCtx.rebind (name.get(name.size() - 1), obj); + if(Log.isDebugEnabled())Log.debug("Bound object to "+name.get(name.size() - 1)); + return subCtx; + + } + + + public static void unbind (Context ctx) + throws NamingException + { + //unbind everything in the context and all of its subdirectories + NamingEnumeration ne = ctx.listBindings(ctx.getNameInNamespace()); + + while (ne.hasMoreElements()) + { + Binding b = (Binding)ne.nextElement(); + if (b.getObject() instanceof Context) + { + unbind((Context)b.getObject()); + } + else + ctx.unbind(b.getName()); + } + } + + /** + * Do a deep listing of the bindings for a context. + * @param ctx the context containing the name for which to list the bindings + * @param name the name in the context to list + * @return map: key is fully qualified name, value is the bound object + * @throws NamingException + */ + public static Map flattenBindings (Context ctx, String name) + throws NamingException + { + HashMap map = new HashMap(); + + //the context representation of name arg + Context c = (Context)ctx.lookup (name); + NameParser parser = c.getNameParser(""); + NamingEnumeration enm = ctx.listBindings(name); + while (enm.hasMore()) + { + Binding b = (Binding)enm.next(); + + if (b.getObject() instanceof Context) + { + map.putAll(flattenBindings (c, b.getName())); + } + else + { + Name compoundName = parser.parse (c.getNameInNamespace()); + compoundName.add(b.getName()); + map.put (compoundName.toString(), b.getObject()); + } + + } + + return map; + } + +} diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java new file mode 100644 index 00000000000..80e95d677b5 --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java @@ -0,0 +1,183 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi.factories; + + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import javax.mail.Authenticator; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.RefAddr; +import javax.naming.Reference; +import javax.naming.StringRefAddr; +import javax.naming.spi.ObjectFactory; + +import org.eclipse.jetty.http.security.Password; + +/** + * MailSessionReference + * + * This is a subclass of javax.mail.Reference and an ObjectFactory for javax.mail.Session objects. + * + * The subclassing of Reference allows all of the setup for a javax.mail.Session + * to be captured without necessitating first instantiating a Session object. The + * reference is bound into JNDI and it is only when the reference is looked up that + * this object factory will create an instance of javax.mail.Session using the + * information captured in the Reference. + * + */ +public class MailSessionReference extends Reference implements ObjectFactory +{ + + + public static class PasswordAuthenticator extends Authenticator + { + PasswordAuthentication passwordAuthentication; + private String user; + private String password; + + public PasswordAuthenticator() + { + + } + + public PasswordAuthenticator(String user, String password) + { + passwordAuthentication = new PasswordAuthentication (user, (password.startsWith(Password.__OBFUSCATE)?Password.deobfuscate(password):password)); + } + + public PasswordAuthentication getPasswordAuthentication() + { + return passwordAuthentication; + } + + public void setUser (String user) + { + this.user = user; + } + public String getUser () + { + return this.user; + } + + public String getPassword () + { + return this.password; + } + + public void setPassword(String password) + { + this.password = password; + } + + + }; + + + + + + /** + * + */ + public MailSessionReference() + { + super ("javax.mail.Session", MailSessionReference.class.getName(), null); + } + + + /** + * Create a javax.mail.Session instance based on the information passed in the Reference + * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable) + * @param ref the Reference + * @param arg1 not used + * @param arg2 not used + * @param arg3 not used + * @return + * @throws Exception + */ + public Object getObjectInstance(Object ref, Name arg1, Context arg2, Hashtable arg3) throws Exception + { + if (ref == null) + return null; + + Reference reference = (Reference)ref; + + + Properties props = new Properties(); + String user = null; + String password = null; + + Enumeration refs = reference.getAll(); + while (refs.hasMoreElements()) + { + RefAddr refAddr = (RefAddr)refs.nextElement(); + String name = refAddr.getType(); + String value = (String)refAddr.getContent(); + if (name.equalsIgnoreCase("user")) + user = value; + else if (name.equalsIgnoreCase("pwd")) + password = value; + else + props.put(name, value); + } + + if (password == null) + return Session.getInstance(props); + else + return Session.getInstance(props, new PasswordAuthenticator(user, password)); + } + + + public void setUser (String user) + { + StringRefAddr addr = (StringRefAddr)get("user"); + if (addr != null) + { + throw new RuntimeException ("user already set on SessionReference, can't be changed"); + } + add(new StringRefAddr("user", user)); + } + + public void setPassword (String password) + { + StringRefAddr addr = (StringRefAddr)get("pwd"); + if (addr != null) + throw new RuntimeException ("password already set on SessionReference, can't be changed"); + add(new StringRefAddr ("pwd", password)); + } + + public void setProperties (Properties properties) + { + Iterator entries = properties.entrySet().iterator(); + while (entries.hasNext()) + { + Map.Entry e = (Map.Entry)entries.next(); + StringRefAddr sref = (StringRefAddr)get((String)e.getKey()); + if (sref != null) + throw new RuntimeException ("property "+e.getKey()+" already set on Session reference, can't be changed"); + add(new StringRefAddr((String)e.getKey(), (String)e.getValue())); + } + } + + + +} diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaNameParser.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaNameParser.java new file mode 100644 index 00000000000..8667207a741 --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaNameParser.java @@ -0,0 +1,52 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi.java; + +import java.util.Properties; + +import javax.naming.CompoundName; +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.NamingException; + + +/** + * javaNameParser + * + */ +public class javaNameParser implements NameParser +{ + + static Properties syntax = new Properties(); + + static + { + syntax.put("jndi.syntax.direction", "left_to_right"); + syntax.put("jndi.syntax.separator", "/"); + syntax.put("jndi.syntax.ignorecase", "false"); + } + + /** + * Parse a name into its components. + * @param name The non-null string name to parse. + * @return A non-null parsed form of the name using the naming convention + * of this parser. + * @exception NamingException If a naming exception was encountered. + */ + public Name parse(String name) throws NamingException + { + return new CompoundName(name, syntax); + } + +} diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java new file mode 100644 index 00000000000..e2409e73687 --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java @@ -0,0 +1,329 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi.java; + + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.naming.StringRefAddr; + +import org.eclipse.jetty.jndi.ContextFactory; +import org.eclipse.jetty.jndi.NamingContext; +import org.eclipse.jetty.util.log.Log; + + + + +/** javaRootURLContext + *

    This is the root of the java: url namespace + * + *

    Notes

    + *

    Thanks to Rickard Oberg for the idea of binding an ObjectFactory at "comp". + * + *

    Usage

    + *
    + */
    +/*
    +* 
    +* +* @see +* +* +* @version 1.0 +*/ +public class javaRootURLContext implements Context +{ + public static final String URL_PREFIX = "java:"; + + protected Hashtable _env; + + protected static NamingContext _nameRoot; + + protected static NameParser _javaNameParser; + + + static + { + try + { + _javaNameParser = new javaNameParser(); + _nameRoot = new NamingContext(); + _nameRoot.setNameParser(_javaNameParser); + + StringRefAddr parserAddr = new StringRefAddr("parser", _javaNameParser.getClass().getName()); + + Reference ref = new Reference ("javax.naming.Context", + parserAddr, + ContextFactory.class.getName(), + (String)null); + + //bind special object factory at comp + _nameRoot.bind ("comp", ref); + } + catch (Exception e) + { + Log.warn(e); + } + } + + + + /*------------------------------------------------*/ + /** + * Creates a new javaRootURLContext instance. + * + * @param env a Hashtable value + */ + public javaRootURLContext(Hashtable env) + { + _env = env; + } + + + public Object lookup(Name name) + throws NamingException + { + return getRoot().lookup(stripProtocol(name)); + } + + + public Object lookup(String name) + throws NamingException + { + return getRoot().lookup(stripProtocol(name)); + } + + public void bind(Name name, Object obj) + throws NamingException + { + getRoot().bind(stripProtocol(name), obj); + } + + public void bind(String name, Object obj) + throws NamingException + { + getRoot().bind(stripProtocol(name), obj); + } + + public void unbind (String name) + throws NamingException + { + getRoot().unbind(stripProtocol(name)); + } + + public void unbind (Name name) + throws NamingException + { + getRoot().unbind(stripProtocol(name)); + } + + public void rename (String oldStr, String newStr) + throws NamingException + { + getRoot().rename (stripProtocol(oldStr), stripProtocol(newStr)); + } + + public void rename (Name oldName, Name newName) + throws NamingException + { + getRoot().rename (stripProtocol(oldName), stripProtocol(newName)); + } + + public void rebind (Name name, Object obj) + throws NamingException + { + getRoot().rebind(stripProtocol(name), obj); + } + + public void rebind (String name, Object obj) + throws NamingException + { + getRoot().rebind(stripProtocol(name), obj); + } + + + public Object lookupLink (Name name) + throws NamingException + { + return getRoot().lookupLink(stripProtocol(name)); + } + + public Object lookupLink (String name) + throws NamingException + { + return getRoot().lookupLink(stripProtocol(name)); + } + + + public Context createSubcontext (Name name) + throws NamingException + { + return getRoot().createSubcontext(stripProtocol(name)); + } + + public Context createSubcontext (String name) + throws NamingException + { + return getRoot().createSubcontext(stripProtocol(name)); + } + + + public void destroySubcontext (Name name) + throws NamingException + { + getRoot().destroySubcontext(stripProtocol(name)); + } + + public void destroySubcontext (String name) + throws NamingException + { + getRoot().destroySubcontext(stripProtocol(name)); + } + + + public NamingEnumeration list(Name name) + throws NamingException + { + return getRoot().list(stripProtocol(name)); + } + + + public NamingEnumeration list(String name) + throws NamingException + { + return getRoot().list(stripProtocol(name)); + } + + public NamingEnumeration listBindings(Name name) + throws NamingException + { + return getRoot().listBindings(stripProtocol(name)); + } + + public NamingEnumeration listBindings(String name) + throws NamingException + { + return getRoot().listBindings(stripProtocol(name)); + } + + + public Name composeName (Name name, + Name prefix) + throws NamingException + { + return getRoot().composeName(name, prefix); + } + + public String composeName (String name, + String prefix) + throws NamingException + { + return getRoot().composeName(name, prefix); + } + + + public void close () + throws NamingException + { + } + + public String getNameInNamespace () + throws NamingException + { + return URL_PREFIX; + } + + public NameParser getNameParser (Name name) + throws NamingException + { + return _javaNameParser; + } + + public NameParser getNameParser (String name) + throws NamingException + { + return _javaNameParser; + } + + + public Object addToEnvironment(String propName, + Object propVal) + throws NamingException + { + return _env.put (propName,propVal); + } + + public Object removeFromEnvironment(String propName) + throws NamingException + { + return _env.remove (propName); + } + + public Hashtable getEnvironment () + { + return _env; + } + + + protected NamingContext getRoot () + { + return _nameRoot; + } + + + protected Name stripProtocol (Name name) + throws NamingException + { + if ((name != null) && (name.size() > 0)) + { + String head = name.get(0); + + if(Log.isDebugEnabled())Log.debug("Head element of name is: "+head); + + if (head.startsWith(URL_PREFIX)) + { + head = head.substring (URL_PREFIX.length()); + name.remove(0); + if (head.length() > 0) + name.add(0, head); + + if(Log.isDebugEnabled())Log.debug("name modified to "+name.toString()); + } + } + + return name; + } + + + + protected String stripProtocol (String name) + { + String newName = name; + + if ((name != null) && (!name.equals(""))) + { + if (name.startsWith(URL_PREFIX)) + newName = name.substring(URL_PREFIX.length()); + } + + return newName; + } + +} diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaURLContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaURLContextFactory.java new file mode 100644 index 00000000000..59e7aceb9cb --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaURLContextFactory.java @@ -0,0 +1,103 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi.java; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.spi.ObjectFactory; + +import org.eclipse.jetty.util.log.Log; + + +/** javaURLContextFactory + *

    This is the URL context factory for the java: URL. + * + *

    Notes

    + *

    + * + *

    Usage

    + *
    + */
    +/*
    +* 
    +* +* @see +* +* +* @version 1.0 +*/ +public class javaURLContextFactory implements ObjectFactory +{ + + /** + * Either return a new context or the resolution of a url. + * + * @param url an Object value + * @param name a Name value + * @param ctx a Context value + * @param env a Hashtable value + * @return a new context or the resolved object for the url + * @exception Exception if an error occurs + */ + public Object getObjectInstance(Object url, Name name, Context ctx, Hashtable env) + throws Exception + { + // null object means return a root context for doing resolutions + if (url == null) + { + if(Log.isDebugEnabled())Log.debug(">>> new root context requested "); + return new javaRootURLContext(env); + } + + // return the resolution of the url + if (url instanceof String) + { + if(Log.isDebugEnabled())Log.debug(">>> resolution of url "+url+" requested"); + Context rootctx = new javaRootURLContext (env); + return rootctx.lookup ((String)url); + } + + // return the resolution of at least one of the urls + if (url instanceof String[]) + { + if(Log.isDebugEnabled())Log.debug(">>> resolution of array of urls requested"); + String[] urls = (String[])url; + Context rootctx = new javaRootURLContext (env); + Object object = null; + NamingException e = null; + for (int i=0;(i< urls.length) && (object == null); i++) + { + try + { + object = rootctx.lookup (urls[i]); + } + catch (NamingException x) + { + e = x; + } + } + + if (object == null) + throw e; + else + return object; + } + + if(Log.isDebugEnabled())Log.debug(">>> No idea what to do, so return a new root context anyway"); + return new javaRootURLContext (env); + } +}; diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java new file mode 100644 index 00000000000..0e5dc5a1c1e --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java @@ -0,0 +1,435 @@ +// ======================================================================== +// Copyright (c) 2000-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi.local; + +import java.util.Hashtable; +import java.util.Properties; + +import javax.naming.CompoundName; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +import org.eclipse.jetty.jndi.NamingContext; + +/** + * + * localContext + * + * + * @version $Revision: 4780 $ $Date: 2009-03-17 16:36:08 +0100 (Tue, 17 Mar 2009) $ + * + */ +public class localContextRoot implements Context +{ + private static final NamingContext _root; + + private final Hashtable _env; + + // make a root for the static namespace local: + static + { + _root = new NamingContext(); + _root.setNameParser(new LocalNameParser()); + } + + static class LocalNameParser implements NameParser + { + Properties syntax = new Properties(); + + LocalNameParser() + { + syntax.put("jndi.syntax.direction", "left_to_right"); + syntax.put("jndi.syntax.separator", "/"); + syntax.put("jndi.syntax.ignorecase", "false"); + } + + public Name parse(String name) throws NamingException + { + return new CompoundName(name, syntax); + } + } + + public localContextRoot(Hashtable env) + { + _env = new Hashtable(env); + } + + /** + * + * + * @see javax.naming.Context#close() + */ + public void close() throws NamingException + { + + } + + /** + * + * + * @see javax.naming.Context#getNameInNamespace() + */ + public String getNameInNamespace() throws NamingException + { + return ""; + } + + /** + * + * + * @see javax.naming.Context#destroySubcontext(java.lang.String) + */ + public void destroySubcontext(String name) throws NamingException + { + synchronized (_root) + { + _root.destroySubcontext(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#unbind(java.lang.String) + */ + public void unbind(String name) throws NamingException + { + synchronized (_root) + { + _root.unbind(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#getEnvironment() + */ + public Hashtable getEnvironment() throws NamingException + { + return _env; + } + + /** + * + * + * @see javax.naming.Context#destroySubcontext(javax.naming.Name) + */ + public void destroySubcontext(Name name) throws NamingException + { + synchronized (_root) + { + _root.destroySubcontext(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#unbind(javax.naming.Name) + */ + public void unbind(Name name) throws NamingException + { + synchronized (_root) + { + _root.unbind(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#lookup(java.lang.String) + */ + public Object lookup(String name) throws NamingException + { + synchronized (_root) + { + return _root.lookup(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#lookupLink(java.lang.String) + */ + public Object lookupLink(String name) throws NamingException + { + synchronized (_root) + { + return _root.lookupLink(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#removeFromEnvironment(java.lang.String) + */ + public Object removeFromEnvironment(String propName) throws NamingException + { + return _env.remove(propName); + } + + /** + * + * + * @see javax.naming.Context#bind(java.lang.String, java.lang.Object) + */ + public void bind(String name, Object obj) throws NamingException + { + synchronized (_root) + { + _root.bind(getSuffix(name), obj); + } + } + + /** + * + * + * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object) + */ + public void rebind(String name, Object obj) throws NamingException + { + synchronized (_root) + { + _root.rebind(getSuffix(name), obj); + } + } + + /** + * + * + * @see javax.naming.Context#lookup(javax.naming.Name) + */ + public Object lookup(Name name) throws NamingException + { + synchronized (_root) + { + return _root.lookup(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#lookupLink(javax.naming.Name) + */ + public Object lookupLink(Name name) throws NamingException + { + synchronized (_root) + { + return _root.lookupLink(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object) + */ + public void bind(Name name, Object obj) throws NamingException + { + synchronized (_root) + { + _root.bind(getSuffix(name), obj); + } + } + + /** + * + * + * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object) + */ + public void rebind(Name name, Object obj) throws NamingException + { + synchronized (_root) + { + _root.rebind(getSuffix(name), obj); + } + } + + /** + * + * + * @see javax.naming.Context#rename(java.lang.String, java.lang.String) + */ + public void rename(String oldName, String newName) throws NamingException + { + synchronized (_root) + { + _root.rename(getSuffix(oldName), getSuffix(newName)); + } + } + + /** + * + * + * @see javax.naming.Context#createSubcontext(java.lang.String) + */ + public Context createSubcontext(String name) throws NamingException + { + synchronized (_root) + { + return _root.createSubcontext(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#createSubcontext(javax.naming.Name) + */ + public Context createSubcontext(Name name) throws NamingException + { + synchronized (_root) + { + return _root.createSubcontext(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) + */ + public void rename(Name oldName, Name newName) throws NamingException + { + synchronized (_root) + { + _root.rename(getSuffix(oldName), getSuffix(newName)); + } + } + + /** + * + * + * @see javax.naming.Context#getNameParser(java.lang.String) + */ + public NameParser getNameParser(String name) throws NamingException + { + return _root.getNameParser(name); + } + + /** + * + * + * @see javax.naming.Context#getNameParser(javax.naming.Name) + */ + public NameParser getNameParser(Name name) throws NamingException + { + return _root.getNameParser(name); + } + + /** + * + * + * @see javax.naming.Context#list(java.lang.String) + */ + public NamingEnumeration list(String name) throws NamingException + { + synchronized (_root) + { + return _root.list(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#listBindings(java.lang.String) + */ + public NamingEnumeration listBindings(String name) throws NamingException + { + synchronized (_root) + { + return _root.listBindings(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#list(javax.naming.Name) + */ + public NamingEnumeration list(Name name) throws NamingException + { + synchronized (_root) + { + return _root.list(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#listBindings(javax.naming.Name) + */ + public NamingEnumeration listBindings(Name name) throws NamingException + { + synchronized (_root) + { + return _root.listBindings(getSuffix(name)); + } + } + + /** + * + * + * @see javax.naming.Context#addToEnvironment(java.lang.String, + * java.lang.Object) + */ + public Object addToEnvironment(String propName, Object propVal) + throws NamingException + { + return _env.put(propName, propVal); + } + + /** + * + * + * @see javax.naming.Context#composeName(java.lang.String, java.lang.String) + */ + public String composeName(String name, String prefix) + throws NamingException + { + return _root.composeName(name, prefix); + } + + /** + * + * + * @see javax.naming.Context#composeName(javax.naming.Name, + * javax.naming.Name) + */ + public Name composeName(Name name, Name prefix) throws NamingException + { + return _root.composeName(name, prefix); + } + + protected String getSuffix(String url) throws NamingException + { + return url; + } + + protected Name getSuffix(Name name) throws NamingException + { + return name; + } + +} diff --git a/jetty-jndi/src/main/resources/jndi.properties b/jetty-jndi/src/main/resources/jndi.properties new file mode 100644 index 00000000000..61a95a63ab0 --- /dev/null +++ b/jetty-jndi/src/main/resources/jndi.properties @@ -0,0 +1,2 @@ +java.naming.factory.url.pkgs=org.eclipse.jetty.jndi +java.naming.factory.initial=org.eclipse.jetty.jndi.InitialContextFactory diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/factories/TestMailSessionReference.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/factories/TestMailSessionReference.java new file mode 100644 index 00000000000..69330052256 --- /dev/null +++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/factories/TestMailSessionReference.java @@ -0,0 +1,72 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi.factories; + +import java.util.Properties; + +import javax.mail.Session; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.LinkRef; +import javax.naming.Name; +import javax.naming.NameParser; + +import junit.framework.TestCase; + +import org.eclipse.jetty.jndi.NamingUtil; + +/** + * TestMailSessionReference + * + * + */ +public class TestMailSessionReference extends TestCase +{ + public void testMailSessionReference () + throws Exception + { + InitialContext icontext = new InitialContext(); + MailSessionReference sref = new MailSessionReference(); + sref.setUser("janb"); + sref.setPassword("OBF:1xmk1w261z0f1w1c1xmq"); + Properties props = new Properties (); + props.put("mail.smtp.host", "xxx"); + props.put("mail.debug", "true"); + sref.setProperties(props); + NamingUtil.bind(icontext, "mail/Session", sref); + Object x = icontext.lookup("mail/Session"); + assertNotNull(x); + assertTrue(x instanceof javax.mail.Session); + javax.mail.Session session = (javax.mail.Session)x; + Properties sessionProps = session.getProperties(); + assertEquals(props, sessionProps); + assertTrue (session.getDebug()); + + Context foo = icontext.createSubcontext("foo"); + NameParser parser = icontext.getNameParser(""); + Name objectNameInNamespace = parser.parse(icontext.getNameInNamespace()); + objectNameInNamespace.addAll(parser.parse("mail/Session")); + + NamingUtil.bind(foo, "mail/Session", new LinkRef(objectNameInNamespace.toString())); + + Object o = foo.lookup("mail/Session"); + assertNotNull(o); + Session fooSession = (Session)o; + assertEquals(props, fooSession.getProperties()); + assertTrue(fooSession.getDebug()); + + icontext.destroySubcontext("mail"); + icontext.destroySubcontext("foo"); + } +} diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java new file mode 100644 index 00000000000..614a0ac3f4c --- /dev/null +++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java @@ -0,0 +1,295 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi.java; + + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.LinkRef; +import javax.naming.Name; +import javax.naming.NameAlreadyBoundException; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.naming.StringRefAddr; +import javax.naming.spi.ObjectFactory; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import org.eclipse.jetty.jndi.NamingContext; +import org.eclipse.jetty.util.log.Log; + + + +public class TestJNDI extends TestCase +{ + + + public static class MyObjectFactory implements ObjectFactory + { + public static String myString = "xxx"; + + public Object getObjectInstance(Object obj, + Name name, + Context nameCtx, + Hashtable environment) + throws Exception + { + return myString; + } + } + + public TestJNDI (String name) + { + super (name); + } + + + public static Test suite () + { + return new TestSuite (TestJNDI.class); + } + + public void setUp () + throws Exception + { + } + + + public void tearDown () + throws Exception + { + } + + public void testIt () + throws Exception + { + try + { + //set up some classloaders + Thread currentThread = Thread.currentThread(); + ClassLoader currentLoader = currentThread.getContextClassLoader(); + ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader); + ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader); + + //set the current thread's classloader + currentThread.setContextClassLoader(childLoader1); + + InitialContext initCtxA = new InitialContext(); + initCtxA.bind ("blah", "123"); + assertEquals ("123", initCtxA.lookup("blah")); + + + + + InitialContext initCtx = new InitialContext(); + Context sub0 = (Context)initCtx.lookup("java:"); + + if(Log.isDebugEnabled())Log.debug("------ Looked up java: --------------"); + + Name n = sub0.getNameParser("").parse("/red/green/"); + + + if(Log.isDebugEnabled())Log.debug("get(0)="+n.get(0)); + if(Log.isDebugEnabled())Log.debug("getPrefix(1)="+n.getPrefix(1)); + n = n.getSuffix(1); + if(Log.isDebugEnabled())Log.debug("getSuffix(1)="+n); + if(Log.isDebugEnabled())Log.debug("get(0)="+n.get(0)); + if(Log.isDebugEnabled())Log.debug("getPrefix(1)="+n.getPrefix(1)); + n = n.getSuffix(1); + if(Log.isDebugEnabled())Log.debug("getSuffix(1)="+n); + if(Log.isDebugEnabled())Log.debug("get(0)="+n.get(0)); + if(Log.isDebugEnabled())Log.debug("getPrefix(1)="+n.getPrefix(1)); + n = n.getSuffix(1); + if(Log.isDebugEnabled())Log.debug("getSuffix(1)="+n); + + n = sub0.getNameParser("").parse("pink/purple/"); + if(Log.isDebugEnabled())Log.debug("get(0)="+n.get(0)); + if(Log.isDebugEnabled())Log.debug("getPrefix(1)="+n.getPrefix(1)); + n = n.getSuffix(1); + if(Log.isDebugEnabled())Log.debug("getSuffix(1)="+n); + if(Log.isDebugEnabled())Log.debug("get(0)="+n.get(0)); + if(Log.isDebugEnabled())Log.debug("getPrefix(1)="+n.getPrefix(1)); + + NamingContext ncontext = (NamingContext)sub0; + + Name nn = ncontext.toCanonicalName(ncontext.getNameParser("").parse("/yellow/blue/")); + Log.debug(nn.toString()); + assertEquals (2, nn.size()); + + nn = ncontext.toCanonicalName(ncontext.getNameParser("").parse("/yellow/blue")); + Log.debug(nn.toString()); + assertEquals (2, nn.size()); + + nn = ncontext.toCanonicalName(ncontext.getNameParser("").parse("/")); + if(Log.isDebugEnabled())Log.debug("/ parses as: "+nn+" with size="+nn.size()); + Log.debug(nn.toString()); + assertEquals (1, nn.size()); + + nn = ncontext.toCanonicalName(ncontext.getNameParser("").parse("")); + Log.debug(nn.toString()); + assertEquals (0, nn.size()); + + Context fee = ncontext.createSubcontext("fee"); + fee.bind ("fi", "88"); + assertEquals("88", initCtxA.lookup("java:/fee/fi")); + assertEquals("88", initCtxA.lookup("java:/fee/fi/")); + assertTrue (initCtxA.lookup("java:/fee/") instanceof javax.naming.Context); + + try + { + Context sub1 = sub0.createSubcontext ("comp"); + fail("Comp should already be bound"); + } + catch (NameAlreadyBoundException e) + { + //expected exception + } + + + + //check bindings at comp + Context sub1 = (Context)initCtx.lookup("java:comp"); + + Context sub2 = sub1.createSubcontext ("env"); + + initCtx.bind ("java:comp/env/rubbish", "abc"); + assertEquals ("abc", (String)initCtx.lookup("java:comp/env/rubbish")); + + + + //check binding LinkRefs + LinkRef link = new LinkRef ("java:comp/env/rubbish"); + initCtx.bind ("java:comp/env/poubelle", link); + assertEquals ("abc", (String)initCtx.lookup("java:comp/env/poubelle")); + + //check binding References + StringRefAddr addr = new StringRefAddr("blah", "myReferenceable"); + Reference ref = new Reference (java.lang.String.class.getName(), + addr, + MyObjectFactory.class.getName(), + (String)null); + + initCtx.bind ("java:comp/env/quatsch", ref); + assertEquals (MyObjectFactory.myString, (String)initCtx.lookup("java:comp/env/quatsch")); + + //test binding something at java: + Context sub3 = initCtx.createSubcontext("java:zero"); + initCtx.bind ("java:zero/one", "ONE"); + assertEquals ("ONE", initCtx.lookup("java:zero/one")); + + + + + //change the current thread's classloader to check distinct naming + currentThread.setContextClassLoader(childLoader2); + + Context otherSub1 = (Context)initCtx.lookup("java:comp"); + assertTrue (!(sub1 == otherSub1)); + try + { + initCtx.lookup("java:comp/env/rubbish"); + } + catch (NameNotFoundException e) + { + //expected + } + + + //put the thread's classloader back + currentThread.setContextClassLoader(childLoader1); + + //test rebind with existing binding + initCtx.rebind("java:comp/env/rubbish", "xyz"); + assertEquals ("xyz", initCtx.lookup("java:comp/env/rubbish")); + + //test rebind with no existing binding + initCtx.rebind ("java:comp/env/mullheim", "hij"); + assertEquals ("hij", initCtx.lookup("java:comp/env/mullheim")); + + //test that the other bindings are already there + assertEquals ("xyz", (String)initCtx.lookup("java:comp/env/poubelle")); + + //test java:/comp/env/stuff + assertEquals ("xyz", (String)initCtx.lookup("java:/comp/env/poubelle/")); + + //test list Names + NamingEnumeration nenum = initCtx.list ("java:comp/env"); + HashMap results = new HashMap(); + while (nenum.hasMore()) + { + NameClassPair ncp = (NameClassPair)nenum.next(); + results.put (ncp.getName(), ncp.getClassName()); + } + + assertEquals (4, results.size()); + + assertEquals ("java.lang.String", (String)results.get("rubbish")); + assertEquals ("javax.naming.LinkRef", (String)results.get("poubelle")); + assertEquals ("java.lang.String", (String)results.get("mullheim")); + assertEquals ("javax.naming.Reference", (String)results.get("quatsch")); + + //test list Bindings + NamingEnumeration benum = initCtx.list("java:comp/env"); + assertEquals (4, results.size()); + + //test NameInNamespace + assertEquals ("comp/env", sub2.getNameInNamespace()); + + //test close does nothing + Context closeCtx = (Context)initCtx.lookup("java:comp/env"); + closeCtx.close(); + + + //test what happens when you close an initial context + InitialContext closeInit = new InitialContext(); + closeInit.close(); + + + + //check locking the context + Context ectx = (Context)initCtx.lookup("java:comp"); + ectx.bind("crud", "xxx"); + ectx.addToEnvironment("org.eclipse.jndi.immutable", "TRUE"); + assertEquals ("xxx", (String)initCtx.lookup("java:comp/crud")); + try + { + ectx.bind("crud2", "xxx2"); + } + catch (NamingException ne) + { + //expected failure to modify immutable context + } + + //test what happens when you close an initial context that was used + initCtx.close(); + } + finally + { + InitialContext ic = new InitialContext(); + Context comp = (Context)ic.lookup("java:comp"); + comp.destroySubcontext("env"); + } + } + +} diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java new file mode 100644 index 00000000000..23a9955c439 --- /dev/null +++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java @@ -0,0 +1,88 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.jndi.java; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.Name; +import javax.naming.NameNotFoundException; +import javax.naming.NameParser; + +import junit.framework.TestCase; + +import org.eclipse.jetty.jndi.NamingUtil; + + +public class TestLocalJNDI extends TestCase +{ + + + public void setUp () + throws Exception + { + } + + + public void tearDown () + throws Exception + { + InitialContext ic = new InitialContext(); + ic.destroySubcontext("a"); + } + + + public void testLocal () + throws Exception + { + + InitialContext ic = new InitialContext(); + NameParser parser = ic.getNameParser(""); + ic.bind("foo", "xxx"); + + Object o = ic.lookup("foo"); + assertNotNull(o); + assertEquals("xxx", (String)o); + + ic.unbind("foo"); + try + { + ic.lookup("foo"); + fail("Foo exists"); + } + catch (NameNotFoundException e) + { + //expected + } + Name name = parser.parse("a"); + name.addAll(parser.parse("b/c/d")); + NamingUtil.bind(ic, name.toString(), "333"); + assertNotNull(ic.lookup("a")); + assertNotNull(ic.lookup("a/b")); + assertNotNull(ic.lookup("a/b/c")); + Context c = (Context)ic.lookup("a/b/c"); + o = c.lookup("d"); + assertNotNull(o); + assertEquals("333", (String)o); + assertEquals("333", ic.lookup(name)); + ic.destroySubcontext("a"); + + name = parser.parse(""); + name.add("x"); + Name suffix = parser.parse("y/z"); + name.addAll(suffix); + NamingUtil.bind(ic, name.toString(), "555"); + assertEquals("555", ic.lookup(name)); + ic.destroySubcontext("x"); + } + +} diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml new file mode 100644 index 00000000000..2705d90484c --- /dev/null +++ b/jetty-plus/pom.xml @@ -0,0 +1,103 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-plus + Jetty :: Plus + Jetty JavaEE style services + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + J2SE-1.5 + !org.eclipse.jetty.plus.*,* + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + config.xml + + + + + + + + + + org.apache.derby + derby + 10.1.2.1 + test + + + junit + junit + test + + + org.apache.geronimo.specs + geronimo-jta_1.1_spec + + + org.eclipse.jetty + jetty-webapp + ${project.version} + + + org.eclipse.jetty + jetty-jndi + ${project.version} + + + diff --git a/jetty-plus/src/main/config/etc/jetty-plus.xml b/jetty-plus/src/main/config/etc/jetty-plus.xml new file mode 100644 index 00000000000..64662ba5321 --- /dev/null +++ b/jetty-plus/src/main/config/etc/jetty-plus.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.eclipse.jetty.webapp.WebInfConfiguration + org.eclipse.jetty.plus.webapp.EnvConfiguration + org.eclipse.jetty.plus.webapp.Configuration + org.eclipse.jetty.webapp.JettyWebXmlConfiguration + org.eclipse.jetty.webapp.TagLibConfiguration + + + + + + + + + + + + diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java new file mode 100644 index 00000000000..34acce9d583 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java @@ -0,0 +1,236 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.annotation; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.eclipse.jetty.util.IntrospectionUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * Injection + * + * Represents the injection of a resource into a target (method or field). + * The injection is performed by doing an ENC lookup using the jndi + * name provided, and setting the object obtained on the target. + * + */ +public class Injection +{ + private Class _targetClass; + private String _jndiName; + private String _mappingName; + private Member _target; + + + public Injection () + {} + + + /** + * @return the _className + */ + public Class getTargetClass() + { + return _targetClass; + } + + + /** + * @param name the _className to set + */ + public void setTargetClass(Class clazz) + { + _targetClass = clazz; + } + + /** + * @return the jndiName + */ + public String getJndiName() + { + return _jndiName; + } + /** + * @param jndiName the jndiName to set + */ + public void setJndiName(String jndiName) + { + this._jndiName = jndiName; + } + /** + * @return the mappingName + */ + public String getMappingName() + { + return _mappingName; + } + /** + * @param mappingName the mappingName to set + */ + public void setMappingName(String mappingName) + { + this._mappingName = mappingName; + } + + /** + * @return the target + */ + public Member getTarget() + { + return _target; + } + + /** + * @param target the target to set + */ + public void setTarget(Member target) + { + this._target = target; + } + + //TODO: define an equals method + + public void setTarget (Class clazz, String targetName, Class targetType) + { + //first look for a javabeans style setter matching the targetName + String setter = "set"+targetName.substring(0,1).toUpperCase()+targetName.substring(1); + try + { + Log.debug("Looking for method for setter: "+setter+" with arg "+targetType); + _target = IntrospectionUtil.findMethod(clazz, setter, new Class[] {targetType}, true, false); + _targetClass = clazz; + } + catch (NoSuchMethodException me) + { + //try as a field + try + { + _target = IntrospectionUtil.findField(clazz, targetName, targetType, true, false); + _targetClass = clazz; + } + catch (NoSuchFieldException fe) + { + throw new IllegalArgumentException("No such field or method "+targetName+" on class "+_targetClass); + } + } + } + + + /** + * Inject a value for a Resource from JNDI into an object + * @param injectable + * @throws Exception + */ + public void inject (Object injectable) + { + Member theTarget = getTarget(); + if (theTarget instanceof Field) + { + injectField((Field)theTarget, injectable); + } + else if (theTarget instanceof Method) + { + injectMethod((Method)theTarget, injectable); + } + } + + + /** + * The Resource must already exist in the ENC of this webapp. + * @return + * @throws Exception + */ + public Object lookupInjectedValue () + throws NamingException + { + InitialContext context = new InitialContext(); + return context.lookup("java:comp/env/"+getJndiName()); + } + + + + /** + * Inject value from jndi into a field of an instance + * @param field + * @param injectable + */ + public void injectField (Field field, Object injectable) + { + try + { + //validateInjection(field, injectable); + boolean accessibility = field.isAccessible(); + field.setAccessible(true); + field.set(injectable, lookupInjectedValue()); + field.setAccessible(accessibility); + } + catch (Exception e) + { + Log.warn(e); + throw new IllegalStateException("Inject failed for field "+field.getName()); + } + } + + /** + * Inject value from jndi into a setter method of an instance + * @param method + * @param injectable + */ + public void injectMethod (Method method, Object injectable) + { + //validateInjection(method, injectable); + try + { + boolean accessibility = method.isAccessible(); + method.setAccessible(true); + method.invoke(injectable, new Object[] {lookupInjectedValue()}); + method.setAccessible(accessibility); + } + catch (Exception e) + { + Log.warn(e); + throw new IllegalStateException("Inject failed for method "+method.getName()); + } + } + + + + + private void validateInjection (Method method, Object injectable) + throws NoSuchMethodException + { + if ((injectable==null) || (method==null)) + return; + //check the injection target actually has a matching method + //TODO: think about this, they have to be assignable + injectable.getClass().getMethod(method.getName(), method.getParameterTypes()); + } + + private void validateInjection (Field field, Object injectable) + throws NoSuchFieldException + { + if ((field==null) || (injectable==null)) + return; + + Field f = injectable.getClass().getField(field.getName()); + if (!f.getType().isAssignableFrom(field.getType())) + throw new NoSuchFieldException("Mismatching type of field: "+f.getType().getName()+" v "+field.getType().getName()); + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/InjectionCollection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/InjectionCollection.java new file mode 100644 index 00000000000..49a94f46e9c --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/InjectionCollection.java @@ -0,0 +1,152 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.annotation; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jetty.util.log.Log; + +/** + * InjectionCollection + * + * + */ +public class InjectionCollection +{ + private HashMap, List> fieldInjectionsMap = new HashMap, List>();//map of classname to field injections + private HashMap, List> methodInjectionsMap = new HashMap, List>();//map of classname to method injections + + + public void add (Injection injection) + { + if ((injection==null) || (injection.getTarget()==null) || (injection.getTargetClass()==null)) + return; + + if (Log.isDebugEnabled()) + Log.debug("Adding injection for class="+injection.getTargetClass()+ " on a "+injection.getTarget()); + Map, List> injectionsMap = null; + if (injection.getTarget() instanceof Field) + injectionsMap = fieldInjectionsMap; + if (injection.getTarget() instanceof Method) + injectionsMap = methodInjectionsMap; + + List injections = (List)injectionsMap.get(injection.getTargetClass()); + if (injections==null) + { + injections = new ArrayList(); + injectionsMap.put(injection.getTargetClass(), injections); + } + + injections.add(injection); + } + + public List getFieldInjections (Class clazz) + { + if (clazz==null) + return null; + List list = (List)fieldInjectionsMap.get(clazz); + if (list == null) + list = Collections.emptyList(); + return list; + } + + public List getMethodInjections (Class clazz) + { + if (clazz==null) + return null; + List list = (List)methodInjectionsMap.get(clazz); + if (list == null) + list = Collections.emptyList(); + return list; + } + + public List getInjections (Class clazz) + { + if (clazz==null) + return null; + + List results = new ArrayList (); + results.addAll(getFieldInjections(clazz)); + results.addAll(getMethodInjections(clazz)); + return results; + } + + public Injection getInjection (Class clazz, Member member) + { + if (clazz==null) + return null; + if (member==null) + return null; + Map, List> map = null; + if (member instanceof Field) + map = fieldInjectionsMap; + else if (member instanceof Method) + map = methodInjectionsMap; + + if (map==null) + return null; + + List injections = (List)map.get(clazz); + Injection injection = null; + for (int i=0;injections!=null && i clazz = injectable.getClass(); + + + if (injectable instanceof PojoWrapper) + { + injectable = ((PojoWrapper)injectable).getPojo(); + clazz = injectable.getClass(); + } + + + while (clazz != null) + { + //Do field injections + List injections = getFieldInjections(clazz); + for (Injection i : injections) + i.inject(injectable); + + //Do method injections + injections = getMethodInjections(clazz); + for (Injection i : injections) + i.inject(injectable); + + clazz = clazz.getSuperclass(); + } + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallback.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallback.java new file mode 100644 index 00000000000..5497fab03c0 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallback.java @@ -0,0 +1,170 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.annotation; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.eclipse.jetty.util.IntrospectionUtil; + + + +/** + * LifeCycleCallback + * + * + */ +public abstract class LifeCycleCallback +{ + public static final Object[] __EMPTY_ARGS = new Object[] {}; + private Method _target; + private Class _targetClass; + + + public LifeCycleCallback() + { + } + + + /** + * @return the _targetClass + */ + public Class getTargetClass() + { + return _targetClass; + } + + + /** + * @param name the class to set + */ + public void setTargetClass(Class clazz) + { + _targetClass = clazz; + } + + + /** + * @return the target + */ + public Method getTarget() + { + return _target; + } + + /** + * @param target the target to set + */ + public void setTarget(Method target) + { + this._target = target; + } + + + + + public void setTarget (Class clazz, String methodName) + { + try + { + Method method = IntrospectionUtil.findMethod(clazz, methodName, null, true, true); + validate(clazz, method); + _target = method; + _targetClass = clazz; + } + catch (NoSuchMethodException e) + { + throw new IllegalArgumentException ("Method "+methodName+" not found on class "+clazz.getName()); + } + } + + + + + public void callback (Object instance) + throws Exception + { + if (getTarget() != null) + { + boolean accessibility = getTarget().isAccessible(); + getTarget().setAccessible(true); + getTarget().invoke(instance, __EMPTY_ARGS); + getTarget().setAccessible(accessibility); + } + } + + + + /** + * Find a method of the given name either directly in the given + * class, or inherited. + * + * @param pack the package of the class under inspection + * @param clazz the class under inspection + * @param methodName the method to find + * @param checkInheritance false on first entry, true if a superclass is being introspected + * @return + */ + public Method findMethod (Package pack, Class clazz, String methodName, boolean checkInheritance) + { + if (clazz == null) + return null; + + try + { + Method method = clazz.getDeclaredMethod(methodName, null); + if (checkInheritance) + { + int modifiers = method.getModifiers(); + if (Modifier.isProtected(modifiers) || Modifier.isPublic(modifiers) || (!Modifier.isPrivate(modifiers)&&(pack.equals(clazz.getPackage())))) + return method; + else + return findMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, true); + } + return method; + } + catch (NoSuchMethodException e) + { + return findMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, true); + } + } + + public boolean equals (Object o) + { + if (o==null) + return false; + if (!(o instanceof LifeCycleCallback)) + return false; + LifeCycleCallback callback = (LifeCycleCallback)o; + + if (callback.getTargetClass()==null) + { + if (getTargetClass() != null) + return false; + } + else if(!callback.getTargetClass().equals(getTargetClass())) + return false; + if (callback.getTarget()==null) + { + if (getTarget() != null) + return false; + } + else if (!callback.getTarget().equals(getTarget())) + return false; + + return true; + } + + public abstract void validate (Class clazz, Method m); +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollection.java new file mode 100644 index 00000000000..a8c521ff68c --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollection.java @@ -0,0 +1,159 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.annotation; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jetty.util.log.Log; + + +/** + * LifeCycleCallbackCollection + * + * + */ +public class LifeCycleCallbackCollection +{ + private HashMap postConstructCallbacksMap = new HashMap(); + private HashMap preDestroyCallbacksMap = new HashMap(); + + + + + + /** + * Add a Callback to the list of callbacks. + * + * @param callback + */ + public void add (LifeCycleCallback callback) + { + if ((callback==null) || (callback.getTargetClass()==null) || (callback.getTarget()==null)) + return; + + if (Log.isDebugEnabled()) + Log.debug("Adding callback for class="+callback.getTargetClass()+ " on "+callback.getTarget()); + Map map = null; + if (callback instanceof PreDestroyCallback) + map = preDestroyCallbacksMap; + if (callback instanceof PostConstructCallback) + map = postConstructCallbacksMap; + + if (map == null) + throw new IllegalArgumentException ("Unsupported lifecycle callback type: "+callback); + + + List callbacks = (List)map.get(callback.getTargetClass()); + if (callbacks==null) + { + callbacks = new ArrayList(); + map.put(callback.getTargetClass(), callbacks); + } + + //don't add another callback for exactly the same method + if (!callbacks.contains(callback)) + callbacks.add(callback); + } + + public List getPreDestroyCallbacks (Object o) + { + if (o == null) + return null; + + Class clazz = o.getClass(); + + if (o instanceof PojoWrapper) + { + o = ((PojoWrapper)o).getPojo(); + clazz = o.getClass(); + } + + return (List)preDestroyCallbacksMap.get(clazz); + } + + public List getPostConstructCallbacks (Object o) + { + if (o == null) + return null; + + Class clazz = o.getClass(); + + if (o instanceof PojoWrapper) + { + o = ((PojoWrapper)o).getPojo(); + clazz = o.getClass(); + } + + return (List)postConstructCallbacksMap.get(clazz); + } + + /** + * Call the method, if one exists, that is annotated with PostConstruct + * or with <post-construct> in web.xml + * @param o the object on which to attempt the callback + * @throws Exception + */ + public void callPostConstructCallback (Object o) + throws Exception + { + if (o == null) + return; + + Class clazz = o.getClass(); + + if (o instanceof PojoWrapper) + { + o = ((PojoWrapper)o).getPojo(); + clazz = o.getClass(); + } + List callbacks = (List)postConstructCallbacksMap.get(clazz); + + if (callbacks == null) + return; + + for (int i=0;i 0) + throw new IllegalArgumentException(clazz.getName()+"."+method.getName()+ " cannot not throw a checked exception"); + + if (!method.getReturnType().equals(Void.TYPE)) + throw new IllegalArgumentException(clazz.getName()+"."+method.getName()+ " cannot not have a return type"); + + if (Modifier.isStatic(method.getModifiers())) + throw new IllegalArgumentException(clazz.getName()+"."+method.getName()+ " cannot be static"); + } + + + public void callback (Object instance) + throws Exception + { + super.callback(instance); + } + + public boolean equals (Object o) + { + if (super.equals(o) && (o instanceof PostConstructCallback)) + return true; + return false; + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PreDestroyCallback.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PreDestroyCallback.java new file mode 100644 index 00000000000..60b87952bf3 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/PreDestroyCallback.java @@ -0,0 +1,70 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.annotation; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.eclipse.jetty.util.log.Log; + +/** + * PreDestroyCallback + * + * + */ +public class PreDestroyCallback extends LifeCycleCallback +{ + + /** + * Commons Annotations Specification section 2.6: + * - no params to method + * - returns void + * - no checked exceptions + * - not static + * @see org.eclipse.jetty.plus.annotation.LifeCycleCallback#validate(java.lang.Class, java.lang.reflect.Method) + */ + public void validate(Class clazz, Method method) + { + + if (method.getExceptionTypes().length > 0) + throw new IllegalArgumentException(clazz.getName()+"."+method.getName()+ " cannot not throw a checked exception"); + + if (!method.getReturnType().equals(Void.TYPE)) + throw new IllegalArgumentException(clazz.getName()+"."+method.getName()+ " cannot not have a return type"); + + if (Modifier.isStatic(method.getModifiers())) + throw new IllegalArgumentException(clazz.getName()+"."+method.getName()+ " cannot be static"); + + } + + + public void callback(Object instance) + { + try + { + super.callback(instance); + } + catch (Exception e) + { + Log.warn("Ignoring exception thrown on preDestroy call to "+getTargetClass()+"."+getTarget().getName(), e); + } + } + + public boolean equals(Object o) + { + if (super.equals(o) && (o instanceof PreDestroyCallback)) + return true; + return false; + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java new file mode 100644 index 00000000000..46f9069c5e6 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java @@ -0,0 +1,75 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.annotation; + +import javax.servlet.ServletException; + +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +/** + * RunAs + *

    + * Represents a <run-as> element in web.xml, or a runAs annotation. + */ +public class RunAs +{ + private Class _targetClass; + private String _roleName; + + public RunAs() + {} + + + public void setTargetClass (Class clazz) + { + _targetClass=clazz; + } + + public Class getTargetClass () + { + return _targetClass; + } + + public void setRoleName (String roleName) + { + _roleName = roleName; + } + + public String getRoleName () + { + return _roleName; + } + + + public void setRunAs (ServletHolder holder, SecurityHandler securityHandler) + throws ServletException + { + if (holder == null) + return; + String className = getServletClassNameForHolder(holder); + + if (className.equals(_targetClass.getName())) + holder.setRunAsRole(_roleName); + } + + public static String getServletClassNameForHolder (ServletHolder holder) + throws ServletException + { + if (PojoServlet.class.getName().equals(holder.getClassName())) + return ((PojoWrapper)holder.getServlet()).getPojo().getClass().getName(); + return holder.getClassName(); + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java new file mode 100644 index 00000000000..57534bb924c --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java @@ -0,0 +1,79 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.annotation; + +import java.util.HashMap; + +import javax.servlet.ServletException; + +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.log.Log; + + +/** + * RunAsCollection + * + * + */ +public class RunAsCollection +{ + private HashMap _runAsMap = new HashMap();//map of classname to run-as + + + public void add (RunAs runAs) + { + if ((runAs==null) || (runAs.getTargetClass()==null)) + return; + + if (Log.isDebugEnabled()) + Log.debug("Adding run-as for class="+runAs.getTargetClass()); + _runAsMap.put(runAs.getTargetClass().getName(), runAs); + } + + public RunAs getRunAs (Object o) + throws ServletException + { + if (o==null) + return null; + + if (!(o instanceof ServletHolder)) + return null; + + ServletHolder holder = (ServletHolder)o; + + String className = RunAs.getServletClassNameForHolder(holder); + return (RunAs)_runAsMap.get(className); + } + + public void setRunAs(Object o, SecurityHandler securityHandler) + throws ServletException + { + if (o==null) + return; + + if (!(o instanceof ServletHolder)) + return; + + ServletHolder holder = (ServletHolder)o; + + String className = RunAs.getServletClassNameForHolder(holder); + RunAs runAs = (RunAs)_runAsMap.get(className); + if (runAs == null) + return; + + runAs.setRunAs(holder, securityHandler); + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASGroup.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASGroup.java new file mode 100644 index 00000000000..7942619df87 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASGroup.java @@ -0,0 +1,147 @@ +// ======================================================================== +// Copyright (c) 2002-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas; + +import java.security.Principal; +import java.security.acl.Group; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; + + +public class JAASGroup implements Group +{ + public static final String ROLES = "__roles__"; + + private String _name = null; + private HashSet _members = null; + + + + public JAASGroup(String n) + { + this._name = n; + this._members = new HashSet(); + } + + /* ------------------------------------------------------------ */ + /** + * + * @param principal + * @return + */ + public synchronized boolean addMember(Principal principal) + { + return _members.add(principal); + } + + /** + * + * @param principal + * @return + */ + public synchronized boolean removeMember(Principal principal) + { + return _members.remove(principal); + } + + /** + * + * @param principal + * @return + */ + public boolean isMember(Principal principal) + { + return _members.contains(principal); + } + + + + /** + * + * @return + */ + public Enumeration members() + { + + class MembersEnumeration implements Enumeration + { + private Iterator itor; + + public MembersEnumeration (Iterator itor) + { + this.itor = itor; + } + + public boolean hasMoreElements () + { + return this.itor.hasNext(); + } + + + public Object nextElement () + { + return this.itor.next(); + } + + } + + return new MembersEnumeration (_members.iterator()); + } + + + /** + * + * @return + */ + public int hashCode() + { + return getName().hashCode(); + } + + + + /** + * + * @param object + * @return + */ + public boolean equals(Object object) + { + if (! (object instanceof JAASGroup)) + return false; + + return ((JAASGroup)object).getName().equals(getName()); + } + + /** + * + * @return + */ + public String toString() + { + return getName(); + } + + /** + * + * @return + */ + public String getName() + { + + return _name; + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASLoginService.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASLoginService.java new file mode 100644 index 00000000000..dfcbbffc555 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASLoginService.java @@ -0,0 +1,280 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas; + +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.eclipse.jetty.plus.jaas.callback.ObjectCallback; +import org.eclipse.jetty.security.DefaultIdentityService; +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.ServerAuthException; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + +/* ---------------------------------------------------- */ +/** JAASLoginService + * + * @org.apache.xbean.XBean element="jaasUserRealm" description="Creates a UserRealm suitable for use with JAAS" + */ +public class JAASLoginService extends AbstractLifeCycle implements LoginService +{ + public static String DEFAULT_ROLE_CLASS_NAME = "org.eclipse.jetty.plus.jaas.JAASRole"; + public static String[] DEFAULT_ROLE_CLASS_NAMES = {DEFAULT_ROLE_CLASS_NAME}; + + protected String[] _roleClassNames = DEFAULT_ROLE_CLASS_NAMES; + protected String _callbackHandlerClass; + protected String _realmName; + protected String _loginModuleName; + protected JAASUserPrincipal _defaultUser = new JAASUserPrincipal(null, null, null); + protected IdentityService _identityService; + + /* ---------------------------------------------------- */ + /** + * Constructor. + * + */ + public JAASLoginService() + { + } + + + /* ---------------------------------------------------- */ + /** + * Constructor. + * + * @param name the name of the realm + */ + public JAASLoginService(String name) + { + this(); + _realmName = name; + _loginModuleName = name; + } + + + /* ---------------------------------------------------- */ + /** + * Get the name of the realm. + * + * @return name or null if not set. + */ + public String getName() + { + return _realmName; + } + + + /* ---------------------------------------------------- */ + /** + * Set the name of the realm + * + * @param name a String value + */ + public void setName (String name) + { + _realmName = name; + } + + + + /* ------------------------------------------------------------ */ + /** Get the identityService. + * @return the identityService + */ + public IdentityService getIdentityService() + { + return _identityService; + } + + + /* ------------------------------------------------------------ */ + /** Set the identityService. + * @param identityService the identityService to set + */ + public void setIdentityService(IdentityService identityService) + { + _identityService = identityService; + } + + + /** + * Set the name to use to index into the config + * file of LoginModules. + * + * @param name a String value + */ + public void setLoginModuleName (String name) + { + _loginModuleName = name; + } + + + public void setCallbackHandlerClass (String classname) + { + _callbackHandlerClass = classname; + } + + public void setRoleClassNames (String[] classnames) + { + ArrayList tmp = new ArrayList(); + + if (classnames != null) + tmp.addAll(Arrays.asList(classnames)); + + if (!tmp.contains(DEFAULT_ROLE_CLASS_NAME)) + tmp.add(DEFAULT_ROLE_CLASS_NAME); + _roleClassNames = tmp.toArray(new String[tmp.size()]); + } + + public String[] getRoleClassNames() + { + return _roleClassNames; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + if (_identityService==null) + _identityService=new DefaultIdentityService(); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + public UserIdentity login(final String username,final Object credentials) + { + try + { + CallbackHandler callbackHandler = new CallbackHandler() + { + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for (Callback callback: callbacks) + { + if (callback instanceof NameCallback) + { + ((NameCallback)callback).setName(username); + } + else if (callback instanceof PasswordCallback) + { + ((PasswordCallback)callback).setPassword((char[]) credentials.toString().toCharArray()); + } + else if (callback instanceof ObjectCallback) + { + ((ObjectCallback)callback).setObject(credentials); + } + } + } + }; + //set up the login context + //TODO jaspi requires we provide the Configuration parameter + Subject subject = new Subject(); + LoginContext loginContext = new LoginContext(_loginModuleName, subject, callbackHandler); + + loginContext.login(); + + //login success + JAASUserPrincipal userPrincipal = new JAASUserPrincipal(getUserName(callbackHandler), subject, loginContext); + subject.getPrincipals().add(userPrincipal); + + return _identityService.newUserIdentity(subject,userPrincipal,getGroups(subject)); + } + catch (LoginException e) + { + Log.warn(e); + } + catch (IOException e) + { + Log.warn(e); + } + catch (UnsupportedCallbackException e) + { + Log.warn(e); + } + return null; + } + + private String getUserName(CallbackHandler callbackHandler) throws IOException, UnsupportedCallbackException + { + NameCallback nameCallback = new NameCallback("foo"); + callbackHandler.handle(new Callback[] {nameCallback}); + return nameCallback.getName(); + } + + public void logout(Subject subject) throws ServerAuthException + { +// loginCallback.clearPassword(); + Set userPrincipals = subject.getPrincipals(JAASUserPrincipal.class); + if (userPrincipals.size() != 1) + { + throw new ServerAuthException("logout implausible, wrong number of user principals: " + userPrincipals); + } + LoginContext loginContext = userPrincipals.iterator().next().getLoginContext(); + try + { + loginContext.logout(); + } + catch (LoginException e) + { + throw new ServerAuthException("Failed to log out: "+e.getMessage()); + } + } + + + private String[] getGroups (Subject subject) + { + //get all the roles of the various types + String[] roleClassNames = getRoleClassNames(); + Collection groups = new LinkedHashSet(); + try + { + for (String roleClassName : roleClassNames) + { + Class load_class = Thread.currentThread().getContextClassLoader().loadClass(roleClassName); + Set rolesForType = subject.getPrincipals(load_class); + for (Principal principal : rolesForType) + { + groups.add(principal.getName()); + } + } + + return groups.toArray(new String[groups.size()]); + } + catch (ClassNotFoundException e) + { + throw new RuntimeException(e); + } + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASPrincipal.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASPrincipal.java new file mode 100644 index 00000000000..3fb74eb4723 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASPrincipal.java @@ -0,0 +1,79 @@ +// ======================================================================== +// Copyright (c) 2002-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas; + +import java.io.Serializable; +import java.security.Principal; + + + +/* ---------------------------------------------------- */ +/** JAASPrincipal + *

    Impl class of Principal interface. + * + *

    Notes

    + *

    + * + *

    Usage

    + *
    + */
    +/*
    + * 
    + * + * @see + * @version 1.0 Tue Apr 15 2003 + * + */ +public class JAASPrincipal implements Principal, Serializable +{ + private String _name = null; + + + public JAASPrincipal(String userName) + { + this._name = userName; + } + + + public boolean equals (Object p) + { + if (! (p instanceof JAASPrincipal)) + return false; + + return getName().equals(((JAASPrincipal)p).getName()); + } + + + public int hashCode () + { + return getName().hashCode(); + } + + + public String getName () + { + return this._name; + } + + + public String toString () + { + return getName(); + } + + + +} + + diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASRole.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASRole.java new file mode 100644 index 00000000000..860b3d594a1 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASRole.java @@ -0,0 +1,32 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas; + + +public class JAASRole extends JAASPrincipal +{ + + public JAASRole(String name) + { + super (name); + } + + public boolean equals (Object o) + { + if (! (o instanceof JAASRole)) + return false; + + return getName().equals(((JAASRole)o).getName()); + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASUserPrincipal.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASUserPrincipal.java new file mode 100644 index 00000000000..cb9b3369738 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASUserPrincipal.java @@ -0,0 +1,74 @@ +// ======================================================================== +// Copyright (c) 2002-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas; + +import java.security.Principal; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; + + + +/* ---------------------------------------------------- */ +/** JAASUserPrincipal + *

    Implements the JAAS version of the + * org.eclipse.http.UserPrincipal interface. + * + * @version $Id: JAASUserPrincipal.java 4780 2009-03-17 15:36:08Z jesse $ + * + */ +public class JAASUserPrincipal implements Principal +{ + private final String _name; + private final Subject _subject; + private final LoginContext _loginContext; + + /* ------------------------------------------------ */ + + public JAASUserPrincipal(String name, Subject subject, LoginContext loginContext) + { + this._name = name; + this._subject = subject; + this._loginContext = loginContext; + } + + /* ------------------------------------------------ */ + /** Get the name identifying the user + */ + public String getName () + { + return _name; + } + + + /* ------------------------------------------------ */ + /** Provide access to the Subject + * @return subject + */ + public Subject getSubject () + { + return this._subject; + } + + LoginContext getLoginContext () + { + return this._loginContext; + } + + public String toString() + { + return getName(); + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/RoleCheckPolicy.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/RoleCheckPolicy.java new file mode 100644 index 00000000000..6727842f059 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/RoleCheckPolicy.java @@ -0,0 +1,31 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas; + +import java.security.Principal; +import java.security.acl.Group; + + +public interface RoleCheckPolicy +{ + /* ------------------------------------------------ */ + /** Check if a role is either a runAsRole or in a set of roles + * @param roleName the role to check + * @param runAsRole a pushed role (can be null) + * @param roles a Group whose Principals are role names + * @return true if role equals runAsRole or is a member of roles. + */ + public boolean checkRole (String roleName, Principal runAsRole, Group roles); + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/StrictRoleCheckPolicy.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/StrictRoleCheckPolicy.java new file mode 100644 index 00000000000..b69ba9b0b27 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/StrictRoleCheckPolicy.java @@ -0,0 +1,58 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas; + +import java.security.Principal; +import java.security.acl.Group; +import java.util.Enumeration; + + +/* ---------------------------------------------------- */ +/** StrictRoleCheckPolicy + *

    Enforces that if a runAsRole is present, then the + * role to check must be the same as that runAsRole and + * the set of static roles is ignored. + * + * + * + * @org.apache.xbean.XBean description ="Check only topmost role in stack of roles for user" + */ +public class StrictRoleCheckPolicy implements RoleCheckPolicy +{ + + public boolean checkRole (String roleName, Principal runAsRole, Group roles) + { + //check if this user has had any temporary role pushed onto + //them. If so, then only check if the user has that role. + if (runAsRole != null) + { + return (roleName.equals(runAsRole.getName())); + } + else + { + if (roles == null) + return false; + Enumeration rolesEnum = roles.members(); + boolean found = false; + while (rolesEnum.hasMoreElements() && !found) + { + Principal p = (Principal)rolesEnum.nextElement(); + found = roleName.equals(p.getName()); + } + return found; + } + + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/AbstractCallbackHandler.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/AbstractCallbackHandler.java new file mode 100644 index 00000000000..33151dac563 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/AbstractCallbackHandler.java @@ -0,0 +1,55 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.callback; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + + +public abstract class AbstractCallbackHandler implements CallbackHandler +{ + protected String _userName; + protected Object _credential; + + public void setUserName (String userName) + { + _userName = userName; + } + + public String getUserName () + { + return _userName; + } + + + public void setCredential (Object credential) + { + _credential = credential; + } + + public Object getCredential () + { + return _credential; + } + + public void handle (Callback[] callbacks) + throws IOException, UnsupportedCallbackException + { + } + + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java new file mode 100644 index 00000000000..35ed91e5d07 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java @@ -0,0 +1,92 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.callback; + +import java.io.IOException; +import java.util.Arrays; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.server.Request; + + + +/* ---------------------------------------------------- */ +/** DefaultUsernameCredentialCallbackHandler + *

    + * + *

    Notes

    + *

    + * + *

    Usage

    + *
    + */
    +/*
    + * 
    + * + * @see + * @version 1.0 Tue Apr 15 2003 + * + */ +public class DefaultCallbackHandler extends AbstractCallbackHandler +{ + + private Request _request; + + public void setRequest (Request request) + { + this._request = request; + } + + public void handle (Callback[] callbacks) + throws IOException, UnsupportedCallbackException + { + for (int i=0; i < callbacks.length; i++) + { + if (callbacks[i] instanceof NameCallback) + { + ((NameCallback)callbacks[i]).setName(getUserName()); + } + else if (callbacks[i] instanceof ObjectCallback) + { + ((ObjectCallback)callbacks[i]).setObject(getCredential()); + } + else if (callbacks[i] instanceof PasswordCallback) + { + if (getCredential() instanceof Password) + ((PasswordCallback)callbacks[i]).setPassword (((Password)getCredential()).toString().toCharArray()); + else if (getCredential() instanceof String) + { + ((PasswordCallback)callbacks[i]).setPassword (((String)getCredential()).toCharArray()); + } + else + throw new UnsupportedCallbackException (callbacks[i], "User supplied credentials cannot be converted to char[] for PasswordCallback: try using an ObjectCallback instead"); + } + else if (callbacks[i] instanceof RequestParameterCallback) + { + RequestParameterCallback callback = (RequestParameterCallback)callbacks[i]; + callback.setParameterValues(Arrays.asList(_request.getParameterValues(callback.getParameterName()))); + } + else + throw new UnsupportedCallbackException(callbacks[i]); + } + + } + +} + diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/ObjectCallback.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/ObjectCallback.java new file mode 100644 index 00000000000..19fd0f656ee --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/ObjectCallback.java @@ -0,0 +1,62 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.callback; + +import javax.security.auth.callback.Callback; + + +/* ---------------------------------------------------- */ +/** ObjectCallback + * + *

    Can be used as a LoginModule Callback to + * obtain a user's credential as an Object, rather than + * a char[], to which some credentials may not be able + * to be converted + * + *

    Notes

    + *

    + * + *

    Usage

    + *
    + */
    +/*
    + * 
    + * + * @see + * @version 1.0 Tue Apr 15 2003 + * + */ +public class ObjectCallback implements Callback +{ + + protected Object _object; + + public void setObject(Object o) + { + _object = o; + } + + public Object getObject () + { + return _object; + } + + + public void clearObject () + { + _object = null; + } + + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/RequestParameterCallback.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/RequestParameterCallback.java new file mode 100644 index 00000000000..bd7d278a74e --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/RequestParameterCallback.java @@ -0,0 +1,56 @@ +// ======================================================================== +// Copyright (c) 2000-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.callback; + +import java.util.List; + +import javax.security.auth.callback.Callback; + + +/** + * + * RequestParameterCallback + * + * Allows a JAAS callback handler to access any parameter from the j_security_check FORM. + * This means that a LoginModule can access form fields other than the j_username and j_password + * fields, and use it, for example, to authenticate a user. + * + * + * @version $Revision: 4780 $ $Date: 2009-03-17 16:36:08 +0100 (Tue, 17 Mar 2009) $ + * + */ +public class RequestParameterCallback implements Callback +{ + private String _paramName; + private List _paramValues; + + public void setParameterName (String name) + { + _paramName = name; + } + public String getParameterName () + { + return _paramName; + } + + public void setParameterValues (List values) + { + _paramValues = values; + } + + public List getParameterValues () + { + return _paramValues; + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java new file mode 100644 index 00000000000..a5e153bcbf7 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java @@ -0,0 +1,136 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.spi; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; + +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.log.Log; + +/** + * AbstractDatabaseLoginModule + * + * Abstract base class for LoginModules that interact with a + * database to retrieve authentication and authorization information. + * Used by the JDBCLoginModule and DataSourceLoginModule. + * + */ +public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule +{ + private String userQuery; + private String rolesQuery; + private String dbUserTable; + private String dbUserTableUserField; + private String dbUserTableCredentialField; + private String dbUserRoleTable; + private String dbUserRoleTableUserField; + private String dbUserRoleTableRoleField; + + + + + /** + * @return a java.sql.Connection from the database + * @throws Exception + */ + public abstract Connection getConnection () throws Exception; + + + + /* ------------------------------------------------ */ + /** Load info from database + * @param userName user info to load + * @exception SQLException + */ + public UserInfo getUserInfo (String userName) + throws Exception + { + Connection connection = null; + + try + { + connection = getConnection(); + + //query for credential + PreparedStatement statement = connection.prepareStatement (userQuery); + statement.setString (1, userName); + ResultSet results = statement.executeQuery(); + String dbCredential = null; + if (results.next()) + { + dbCredential = results.getString(1); + } + results.close(); + statement.close(); + + //query for role names + statement = connection.prepareStatement (rolesQuery); + statement.setString (1, userName); + results = statement.executeQuery(); + List roles = new ArrayList(); + + while (results.next()) + { + String roleName = results.getString (1); + roles.add (roleName); + } + + results.close(); + statement.close(); + + return dbCredential==null ? null : new UserInfo (userName, + Credential.getCredential(dbCredential), roles); + } + finally + { + if (connection != null) connection.close(); + } + } + + + public void initialize(Subject subject, + CallbackHandler callbackHandler, + Map sharedState, + Map options) + { + super.initialize(subject, callbackHandler, sharedState, options); + + //get the user credential query out of the options + dbUserTable = (String)options.get("userTable"); + dbUserTableUserField = (String)options.get("userField"); + dbUserTableCredentialField = (String)options.get("credentialField"); + + userQuery = "select "+dbUserTableCredentialField+" from "+dbUserTable+" where "+dbUserTableUserField+"=?"; + + + //get the user roles query out of the options + dbUserRoleTable = (String)options.get("userRoleTable"); + dbUserRoleTableUserField = (String)options.get("userRoleUserField"); + dbUserRoleTableRoleField = (String)options.get("userRoleRoleField"); + + rolesQuery = "select "+dbUserRoleTableRoleField+" from "+dbUserRoleTable+" where "+dbUserRoleTableUserField+"=?"; + + if(Log.isDebugEnabled())Log.debug("userQuery = "+userQuery); + if(Log.isDebugEnabled())Log.debug("rolesQuery = "+rolesQuery); + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractLoginModule.java new file mode 100644 index 00000000000..b531f7cc02b --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractLoginModule.java @@ -0,0 +1,278 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.spi; + +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +import org.eclipse.jetty.plus.jaas.JAASPrincipal; +import org.eclipse.jetty.plus.jaas.JAASRole; +import org.eclipse.jetty.plus.jaas.callback.ObjectCallback; + +/** + * AbstractLoginModule + * + * Abstract base class for all LoginModules. Subclasses should + * just need to implement getUserInfo method. + * + */ +public abstract class AbstractLoginModule implements LoginModule +{ + private CallbackHandler callbackHandler; + + private boolean authState = false; + private boolean commitState = false; + private JAASUserInfo currentUser; + private Subject subject; + + public class JAASUserInfo + { + private UserInfo user; + private Principal principal; + private List roles; + + public JAASUserInfo (UserInfo u) + { + setUserInfo(u); + } + + public String getUserName () + { + return this.user.getUserName(); + } + + public Principal getPrincipal() + { + return this.principal; + } + + public void setUserInfo (UserInfo u) + { + this.user = u; + this.principal = new JAASPrincipal(u.getUserName()); + this.roles = new ArrayList(); + if (u.getRoleNames() != null) + { + Iterator itor = u.getRoleNames().iterator(); + while (itor.hasNext()) + this.roles.add(new JAASRole((String)itor.next())); + } + } + + public void setJAASInfo (Subject subject) + { + subject.getPrincipals().add(this.principal); + subject.getPrivateCredentials().add(this.user.getCredential()); + subject.getPrincipals().addAll(roles); + } + + public void unsetJAASInfo (Subject subject) + { + subject.getPrincipals().remove(this.principal); + subject.getPrivateCredentials().remove(this.user.getCredential()); + subject.getPrincipals().removeAll(this.roles); + } + + public boolean checkCredential (Object suppliedCredential) + { + return this.user.checkCredential(suppliedCredential); + } + } + + + + public Subject getSubject () + { + return this.subject; + } + + public void setSubject (Subject s) + { + this.subject = s; + } + + public JAASUserInfo getCurrentUser() + { + return this.currentUser; + } + + public void setCurrentUser (JAASUserInfo u) + { + this.currentUser = u; + } + + public CallbackHandler getCallbackHandler() + { + return this.callbackHandler; + } + + public void setCallbackHandler(CallbackHandler h) + { + this.callbackHandler = h; + } + + public boolean isAuthenticated() + { + return this.authState; + } + + public boolean isCommitted () + { + return this.commitState; + } + + public void setAuthenticated (boolean authState) + { + this.authState = authState; + } + + public void setCommitted (boolean commitState) + { + this.commitState = commitState; + } + /** + * @see javax.security.auth.spi.LoginModule#abort() + * @throws LoginException + */ + public boolean abort() throws LoginException + { + this.currentUser = null; + return (isAuthenticated() && isCommitted()); + } + + /** + * @see javax.security.auth.spi.LoginModule#commit() + * @return + * @throws LoginException + */ + public boolean commit() throws LoginException + { + + if (!isAuthenticated()) + { + currentUser = null; + setCommitted(false); + return false; + } + + setCommitted(true); + currentUser.setJAASInfo(subject); + return true; + } + + + public Callback[] configureCallbacks () + { + + Callback[] callbacks = new Callback[2]; + callbacks[0] = new NameCallback("Enter user name"); + callbacks[1] = new ObjectCallback(); + return callbacks; + } + + + + public abstract UserInfo getUserInfo (String username) throws Exception; + + + + /** + * @see javax.security.auth.spi.LoginModule#login() + * @return + * @throws LoginException + */ + public boolean login() throws LoginException + { + try + { + if (callbackHandler == null) + throw new LoginException ("No callback handler"); + + Callback[] callbacks = configureCallbacks(); + callbackHandler.handle(callbacks); + + String webUserName = ((NameCallback)callbacks[0]).getName(); + Object webCredential = ((ObjectCallback)callbacks[1]).getObject(); + + if ((webUserName == null) || (webCredential == null)) + { + setAuthenticated(false); + return isAuthenticated(); + } + + UserInfo userInfo = getUserInfo(webUserName); + + if (userInfo == null) + { + setAuthenticated(false); + return isAuthenticated(); + } + + currentUser = new JAASUserInfo(userInfo); + setAuthenticated(currentUser.checkCredential(webCredential)); + return isAuthenticated(); + } + catch (IOException e) + { + throw new LoginException (e.toString()); + } + catch (UnsupportedCallbackException e) + { + throw new LoginException (e.toString()); + } + catch (Exception e) + { + e.printStackTrace(); + throw new LoginException (e.toString()); + } + } + + /** + * @see javax.security.auth.spi.LoginModule#logout() + * @return + * @throws LoginException + */ + public boolean logout() throws LoginException + { + this.currentUser.unsetJAASInfo(this.subject); + return true; + } + + /** + * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) + * @param subject + * @param callbackHandler + * @param sharedState + * @param options + */ + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) + { + this.callbackHandler = callbackHandler; + this.subject = subject; + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/DataSourceLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/DataSourceLoginModule.java new file mode 100644 index 00000000000..668c01916fc --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/DataSourceLoginModule.java @@ -0,0 +1,85 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.spi; +import java.sql.Connection; +import java.util.Map; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.sql.DataSource; + +/** + * DataSourceLoginModule + * + * A LoginModule that uses a DataSource to retrieve user authentication + * and authorisation information. + * + * @see org.eclipse.jetty.server.server.plus.jaas.spi.JDBCLoginModule + * + */ +public class DataSourceLoginModule extends AbstractDatabaseLoginModule +{ + + private String dbJNDIName; + private DataSource dataSource; + + /* ------------------------------------------------ */ + /** Init LoginModule. + * Called once by JAAS after new instance created. + * @param subject + * @param callbackHandler + * @param sharedState + * @param options + */ + public void initialize(Subject subject, + CallbackHandler callbackHandler, + Map sharedState, + Map options) + { + try + { + super.initialize(subject, callbackHandler, sharedState, options); + + //get the datasource jndi name + dbJNDIName = (String)options.get("dbJNDIName"); + + InitialContext ic = new InitialContext(); + dataSource = (DataSource)ic.lookup("java:comp/env/"+dbJNDIName); + } + catch (NamingException e) + { + throw new IllegalStateException (e.toString()); + } + } + + + /** + * Get a connection from the DataSource + * @see org.eclipse.jetty.server.server.plus.jaas.spi.AbstractDatabaseLoginModule#getConnection() + * @return + * @throws Exception + */ + public Connection getConnection () + throws Exception + { + return dataSource.getConnection(); + } + + + + + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/JDBCLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/JDBCLoginModule.java new file mode 100644 index 00000000000..52421486548 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/JDBCLoginModule.java @@ -0,0 +1,127 @@ +// ======================================================================== +// Copyright (c) 2002-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.spi; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; + +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.log.Log; + + + +/* ---------------------------------------------------- */ +/** JDBCLoginModule + *

    JAAS LoginModule to retrieve user information from + * a database and authenticate the user. + * + *

    Notes

    + *

    This version uses plain old JDBC connections NOT + * Datasources. + * + *

    Usage

    + *
    + */
    +/*
    + * 
    + * + * @see + * @version 1.0 Tue Apr 15 2003 + * + */ +public class JDBCLoginModule extends AbstractDatabaseLoginModule +{ + private String dbDriver; + private String dbUrl; + private String dbUserName; + private String dbPassword; + + + + + + + /** + * Get a connection from the DriverManager + * @see org.eclipse.jetty.server.server.plus.jaas.spi.AbstractDatabaseLoginModule#getConnection() + * @return + * @throws Exception + */ + public Connection getConnection () + throws Exception + { + if (!((dbDriver != null) + && + (dbUrl != null))) + throw new IllegalStateException ("Database connection information not configured"); + + if(Log.isDebugEnabled())Log.debug("Connecting using dbDriver="+dbDriver+"+ dbUserName="+dbUserName+", dbPassword="+dbUrl); + + return DriverManager.getConnection (dbUrl, + dbUserName, + dbPassword); + } + + + + /* ------------------------------------------------ */ + /** Init LoginModule. + * Called once by JAAS after new instance created. + * @param subject + * @param callbackHandler + * @param sharedState + * @param options + */ + public void initialize(Subject subject, + CallbackHandler callbackHandler, + Map sharedState, + Map options) + { + try + { + super.initialize(subject, callbackHandler, sharedState, options); + + //get the jdbc username/password, jdbc url out of the options + dbDriver = (String)options.get("dbDriver"); + dbUrl = (String)options.get("dbUrl"); + dbUserName = (String)options.get("dbUserName"); + dbPassword = (String)options.get("dbPassword"); + + if (dbUserName == null) + dbUserName = ""; + + if (dbPassword == null) + dbPassword = ""; + + if (dbDriver != null) + Loader.loadClass(this.getClass(), dbDriver).newInstance(); + } + catch (ClassNotFoundException e) + { + throw new IllegalStateException (e.toString()); + } + catch (InstantiationException e) + { + throw new IllegalStateException (e.toString()); + } + catch (IllegalAccessException e) + { + throw new IllegalStateException (e.toString()); + } + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java new file mode 100644 index 00000000000..061136624c3 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java @@ -0,0 +1,680 @@ +// ======================================================================== +// Copyright (c) 2007-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.plus.jaas.spi; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginException; + +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.plus.jaas.callback.ObjectCallback; +import org.eclipse.jetty.util.log.Log; + +/** + * A LdapLoginModule for use with JAAS setups + *

    + * The jvm should be started with the following parameter: + *

    + * + * -Djava.security.auth.login.config=etc/ldap-loginModule.conf + * + *

    + * and an example of the ldap-loginModule.conf would be: + *

    + *

    + * ldaploginmodule {
    + *    org.eclipse.jetty.server.server.plus.jaas.spi.LdapLoginModule required
    + *    debug="true"
    + *    contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
    + *    hostname="ldap.example.com"
    + *    port="389"
    + *    bindDn="cn=Directory Manager"
    + *    bindPassword="directory"
    + *    authenticationMethod="simple"
    + *    forceBindingLogin="false"
    + *    userBaseDn="ou=people,dc=alcatel"
    + *    userRdnAttribute="uid"
    + *    userIdAttribute="uid"
    + *    userPasswordAttribute="userPassword"
    + *    userObjectClass="inetOrgPerson"
    + *    roleBaseDn="ou=groups,dc=example,dc=com"
    + *    roleNameAttribute="cn"
    + *    roleMemberAttribute="uniqueMember"
    + *    roleObjectClass="groupOfUniqueNames";
    + *    };
    + *  
    + * + * + * + * + */ +public class LdapLoginModule extends AbstractLoginModule +{ + /** + * hostname of the ldap server + */ + private String _hostname; + + /** + * port of the ldap server + */ + private int _port; + + /** + * Context.SECURITY_AUTHENTICATION + */ + private String _authenticationMethod; + + /** + * Context.INITIAL_CONTEXT_FACTORY + */ + private String _contextFactory; + + /** + * root DN used to connect to + */ + private String _bindDn; + + /** + * password used to connect to the root ldap context + */ + private String _bindPassword; + + /** + * object class of a user + */ + private String _userObjectClass = "inetOrgPerson"; + + /** + * attribute that the principal is located + */ + private String _userRdnAttribute = "uid"; + + /** + * attribute that the principal is located + */ + private String _userIdAttribute = "cn"; + + /** + * name of the attribute that a users password is stored under + *

    + * NOTE: not always accessible, see force binding login + */ + private String _userPasswordAttribute = "userPassword"; + + /** + * base DN where users are to be searched from + */ + private String _userBaseDn; + + /** + * base DN where role membership is to be searched from + */ + private String _roleBaseDn; + + /** + * object class of roles + */ + private String _roleObjectClass = "groupOfUniqueNames"; + + /** + * name of the attribute that a username would be under a role class + */ + private String _roleMemberAttribute = "uniqueMember"; + + /** + * the name of the attribute that a role would be stored under + */ + private String _roleNameAttribute = "roleName"; + + private boolean _debug; + + /** + * if the getUserInfo can pull a password off of the user then + * password comparison is an option for authn, to force binding + * login checks, set this to true + */ + private boolean _forceBindingLogin = false; + + private DirContext _rootContext; + + /** + * get the available information about the user + *

    + * for this LoginModule, the credential can be null which will result in a + * binding ldap authentication scenario + *

    + * roles are also an optional concept if required + * + * @param username + * @return + * @throws Exception + */ + public UserInfo getUserInfo(String username) throws Exception + { + String pwdCredential = getUserCredentials(username); + + if (pwdCredential == null) + { + return null; + } + + pwdCredential = convertCredentialLdapToJetty(pwdCredential); + + //String md5Credential = Credential.MD5.digest("foo"); + //byte[] ba = digestMD5("foo"); + //System.out.println(md5Credential + " " + ba ); + Credential credential = Credential.getCredential(pwdCredential); + List roles = getUserRoles(_rootContext, username); + + return new UserInfo(username, credential, roles); + } + + protected String doRFC2254Encoding(String inputString) + { + StringBuffer buf = new StringBuffer(inputString.length()); + for (int i = 0; i < inputString.length(); i++) + { + char c = inputString.charAt(i); + switch (c) + { + case '\\': + buf.append("\\5c"); + break; + case '*': + buf.append("\\2a"); + break; + case '(': + buf.append("\\28"); + break; + case ')': + buf.append("\\29"); + break; + case '\0': + buf.append("\\00"); + break; + default: + buf.append(c); + break; + } + } + return buf.toString(); + } + + /** + * attempts to get the users credentials from the users context + *

    + * NOTE: this is not an user authenticated operation + * + * @param username + * @return + * @throws LoginException + */ + private String getUserCredentials(String username) throws LoginException + { + String ldapCredential = null; + + SearchControls ctls = new SearchControls(); + ctls.setCountLimit(1); + ctls.setDerefLinkFlag(true); + ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + String filter = "(&(objectClass={0})({1}={2}))"; + + Log.debug("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn); + + try + { + Object[] filterArguments = {_userObjectClass, _userIdAttribute, username}; + NamingEnumeration results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls); + + Log.debug("Found user?: " + results.hasMoreElements()); + + if (!results.hasMoreElements()) + { + throw new LoginException("User not found."); + } + + SearchResult result = findUser(username); + + Attributes attributes = result.getAttributes(); + + Attribute attribute = attributes.get(_userPasswordAttribute); + if (attribute != null) + { + try + { + byte[] value = (byte[]) attribute.get(); + + ldapCredential = new String(value); + } + catch (NamingException e) + { + Log.debug("no password available under attribute: " + _userPasswordAttribute); + } + } + } + catch (NamingException e) + { + throw new LoginException("Root context binding failure."); + } + + Log.debug("user cred is: " + ldapCredential); + + return ldapCredential; + } + + /** + * attempts to get the users roles from the root context + *

    + * NOTE: this is not an user authenticated operation + * + * @param dirContext + * @param username + * @return + * @throws LoginException + */ + private List getUserRoles(DirContext dirContext, String username) throws LoginException, NamingException + { + String userDn = _userRdnAttribute + "=" + username + "," + _userBaseDn; + + return getUserRolesByDn(dirContext, userDn); + } + + private List getUserRolesByDn(DirContext dirContext, String userDn) throws LoginException, NamingException + { + ArrayList roleList = new ArrayList(); + + if (dirContext == null || _roleBaseDn == null || _roleMemberAttribute == null || _roleObjectClass == null) + { + return roleList; + } + + SearchControls ctls = new SearchControls(); + ctls.setDerefLinkFlag(true); + ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + String filter = "(&(objectClass={0})({1}={2}))"; + Object[] filterArguments = {_roleObjectClass, _roleMemberAttribute, userDn}; + NamingEnumeration results = dirContext.search(_roleBaseDn, filter, filterArguments, ctls); + + Log.debug("Found user roles?: " + results.hasMoreElements()); + + while (results.hasMoreElements()) + { + SearchResult result = (SearchResult) results.nextElement(); + + Attributes attributes = result.getAttributes(); + + if (attributes == null) + { + continue; + } + + Attribute roleAttribute = attributes.get(_roleNameAttribute); + + if (roleAttribute == null) + { + continue; + } + + NamingEnumeration roles = roleAttribute.getAll(); + while (roles.hasMore()) + { + roleList.add(roles.next()); + } + } + + return roleList; + } + + + /** + * since ldap uses a context bind for valid authentication checking, we override login() + *

    + * if credentials are not available from the users context or if we are forcing the binding check + * then we try a binding authentication check, otherwise if we have the users encoded password then + * we can try authentication via that mechanic + * + * @return + * @throws LoginException + */ + public boolean login() throws LoginException + { + try + { + if (getCallbackHandler() == null) + { + throw new LoginException("No callback handler"); + } + + Callback[] callbacks = configureCallbacks(); + getCallbackHandler().handle(callbacks); + + String webUserName = ((NameCallback) callbacks[0]).getName(); + Object webCredential = ((ObjectCallback) callbacks[1]).getObject(); + + if (webUserName == null || webCredential == null) + { + setAuthenticated(false); + return isAuthenticated(); + } + + if (_forceBindingLogin) + { + return bindingLogin(webUserName, webCredential); + } + + // This sets read and the credential + UserInfo userInfo = getUserInfo(webUserName); + + if (userInfo == null) + { + setAuthenticated(false); + return false; + } + + setCurrentUser(new JAASUserInfo(userInfo)); + + if (webCredential instanceof String) + { + return credentialLogin(Credential.getCredential((String) webCredential)); + } + + return credentialLogin(webCredential); + } + catch (UnsupportedCallbackException e) + { + throw new LoginException("Error obtaining callback information."); + } + catch (IOException e) + { + if (_debug) + { + e.printStackTrace(); + } + throw new LoginException("IO Error performing login."); + } + catch (Exception e) + { + if (_debug) + { + e.printStackTrace(); + } + throw new LoginException("Error obtaining user info."); + } + } + + /** + * password supplied authentication check + * + * @param webCredential + * @return + * @throws LoginException + */ + protected boolean credentialLogin(Object webCredential) throws LoginException + { + setAuthenticated(getCurrentUser().checkCredential(webCredential)); + return isAuthenticated(); + } + + /** + * binding authentication check + * This methode of authentication works only if the user branch of the DIT (ldap tree) + * has an ACI (acces control instruction) that allow the access to any user or at least + * for the user that logs in. + * + * @param username + * @param password + * @return + * @throws LoginException + */ + public boolean bindingLogin(String username, Object password) throws LoginException, NamingException + { + SearchResult searchResult = findUser(username); + + String userDn = searchResult.getNameInNamespace(); + + Log.info("Attempting authentication: " + userDn); + + Hashtable environment = getEnvironment(); + environment.put(Context.SECURITY_PRINCIPAL, userDn); + environment.put(Context.SECURITY_CREDENTIALS, password); + + DirContext dirContext = new InitialDirContext(environment); + List roles = getUserRolesByDn(dirContext, userDn); + + UserInfo userInfo = new UserInfo(username, null, roles); + + setCurrentUser(new JAASUserInfo(userInfo)); + + setAuthenticated(true); + + return true; + } + + private SearchResult findUser(String username) throws NamingException, LoginException + { + SearchControls ctls = new SearchControls(); + ctls.setCountLimit(1); + ctls.setDerefLinkFlag(true); + ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + String filter = "(&(objectClass={0})({1}={2}))"; + + Log.info("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn); + + Object[] filterArguments = new Object[]{ + _userObjectClass, + _userIdAttribute, + username + }; + NamingEnumeration results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls); + + Log.info("Found user?: " + results.hasMoreElements()); + + if (!results.hasMoreElements()) + { + throw new LoginException("User not found."); + } + + return (SearchResult) results.nextElement(); + } + + + /** + * Init LoginModule. + * Called once by JAAS after new instance is created. + * + * @param subject + * @param callbackHandler + * @param sharedState + * @param options + */ + public void initialize(Subject subject, + CallbackHandler callbackHandler, + Map sharedState, + Map options) + { + + super.initialize(subject, callbackHandler, sharedState, options); + + _hostname = (String) options.get("hostname"); + _port = Integer.parseInt((String) options.get("port")); + _contextFactory = (String) options.get("contextFactory"); + _bindDn = (String) options.get("bindDn"); + _bindPassword = (String) options.get("bindPassword"); + _authenticationMethod = (String) options.get("authenticationMethod"); + + _userBaseDn = (String) options.get("userBaseDn"); + + _roleBaseDn = (String) options.get("roleBaseDn"); + + if (options.containsKey("forceBindingLogin")) + { + _forceBindingLogin = Boolean.parseBoolean((String) options.get("forceBindingLogin")); + } + + _userObjectClass = getOption(options, "userObjectClass", _userObjectClass); + _userRdnAttribute = getOption(options, "userRdnAttribute", _userRdnAttribute); + _userIdAttribute = getOption(options, "userIdAttribute", _userIdAttribute); + _userPasswordAttribute = getOption(options, "userPasswordAttribute", _userPasswordAttribute); + _roleObjectClass = getOption(options, "roleObjectClass", _roleObjectClass); + _roleMemberAttribute = getOption(options, "roleMemberAttribute", _roleMemberAttribute); + _roleNameAttribute = getOption(options, "roleNameAttribute", _roleNameAttribute); + _debug = Boolean.parseBoolean(String.valueOf(getOption(options, "debug", Boolean.toString(_debug)))); + + try + { + _rootContext = new InitialDirContext(getEnvironment()); + } + catch (NamingException ex) + { + throw new IllegalStateException("Unable to establish root context", ex); + } + } + + public boolean commit() throws LoginException { + + try + { + _rootContext.close(); + } + catch (NamingException e) + { + throw new LoginException( "error closing root context: " + e.getMessage() ); + } + + return super.commit(); + } + + public boolean abort() throws LoginException { + try + { + _rootContext.close(); + } + catch (NamingException e) + { + throw new LoginException( "error closing root context: " + e.getMessage() ); + } + + return super.abort(); + } + + private String getOption(Map options, String key, String defaultValue) + { + Object value = options.get(key); + + if (value == null) + { + return defaultValue; + } + + return (String) value; + } + + /** + * get the context for connection + * + * @return + */ + public Hashtable getEnvironment() + { + Properties env = new Properties(); + + env.put(Context.INITIAL_CONTEXT_FACTORY, _contextFactory); + + if (_hostname != null) + { + if (_port != 0) + { + env.put(Context.PROVIDER_URL, "ldap://" + _hostname + ":" + _port + "/"); + } + else + { + env.put(Context.PROVIDER_URL, "ldap://" + _hostname + "/"); + } + } + + if (_authenticationMethod != null) + { + env.put(Context.SECURITY_AUTHENTICATION, _authenticationMethod); + } + + if (_bindDn != null) + { + env.put(Context.SECURITY_PRINCIPAL, _bindDn); + } + + if (_bindPassword != null) + { + env.put(Context.SECURITY_CREDENTIALS, _bindPassword); + } + + return env; + } + + public static String convertCredentialJettyToLdap(String encryptedPassword) + { + if ("MD5:".startsWith(encryptedPassword.toUpperCase())) + { + return "{MD5}" + encryptedPassword.substring("MD5:".length(), encryptedPassword.length()); + } + + if ("CRYPT:".startsWith(encryptedPassword.toUpperCase())) + { + return "{CRYPT}" + encryptedPassword.substring("CRYPT:".length(), encryptedPassword.length()); + } + + return encryptedPassword; + } + + public static String convertCredentialLdapToJetty(String encryptedPassword) + { + if (encryptedPassword == null) + { + return encryptedPassword; + } + + if ("{MD5}".startsWith(encryptedPassword.toUpperCase())) + { + return "MD5:" + encryptedPassword.substring("{MD5}".length(), encryptedPassword.length()); + } + + if ("{CRYPT}".startsWith(encryptedPassword.toUpperCase())) + { + return "CRYPT:" + encryptedPassword.substring("{CRYPT}".length(), encryptedPassword.length()); + } + + return encryptedPassword; + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java new file mode 100644 index 00000000000..c2f4ee935d0 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java @@ -0,0 +1,156 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.spi; + +import java.io.File; +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; + +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.log.Log; + +/** + * PropertyFileLoginModule + * + * + */ +public class PropertyFileLoginModule extends AbstractLoginModule +{ + public static final String DEFAULT_FILENAME = "realm.properties"; + public static final Map fileMap = new HashMap(); + + private String propertyFileName; + + + + /** + * Read contents of the configured property file. + * + * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) + * @param subject + * @param callbackHandler + * @param sharedState + * @param options + */ + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) + { + super.initialize(subject, callbackHandler, sharedState, options); + loadProperties((String)options.get("file")); + } + + + + public void loadProperties (String filename) + { + File propsFile; + + if (filename == null) + { + propsFile = new File(System.getProperty("user.dir"), DEFAULT_FILENAME); + //look for a file called realm.properties in the current directory + //if that fails, look for a file called realm.properties in $jetty.home/etc + if (!propsFile.exists()) + propsFile = new File(System.getProperty("jetty.home"), DEFAULT_FILENAME); + } + else + { + propsFile = new File(filename); + } + + //give up, can't find a property file to load + if (!propsFile.exists()) + { + Log.warn("No property file found"); + throw new IllegalStateException ("No property file specified in login module configuration file"); + } + + + + try + { + this.propertyFileName = propsFile.getCanonicalPath(); + if (fileMap.get(propertyFileName) != null) + { + if (Log.isDebugEnabled()) {Log.debug("Properties file "+propertyFileName+" already in cache, skipping load");} + return; + } + + Map userInfoMap = new HashMap(); + Properties props = new Properties(); + props.load(new FileInputStream(propsFile)); + Iterator iter = props.entrySet().iterator(); + while(iter.hasNext()) + { + + Map.Entry entry = (Map.Entry)iter.next(); + String username=entry.getKey().toString().trim(); + String credentials=entry.getValue().toString().trim(); + String roles=null; + int c=credentials.indexOf(','); + if (c>0) + { + roles=credentials.substring(c+1).trim(); + credentials=credentials.substring(0,c).trim(); + } + + if (username!=null && username.length()>0 && + credentials!=null && credentials.length()>0) + { + ArrayList roleList = new ArrayList(); + if(roles!=null && roles.length()>0) + { + StringTokenizer tok = new StringTokenizer(roles,", "); + + while (tok.hasMoreTokens()) + roleList.add(tok.nextToken()); + } + + userInfoMap.put(username, (new UserInfo(username, Credential.getCredential(credentials.toString()), roleList))); + } + } + + fileMap.put(propertyFileName, userInfoMap); + } + catch (Exception e) + { + Log.warn("Error loading properties from file", e); + throw new RuntimeException(e); + } + } + + /** + * Don't implement this as we want to pre-fetch all of the + * users. + * @see org.eclipse.jetty.plus.jaas.spi.AbstractLoginModule#lazyLoadUser(java.lang.String) + * @param username + * @throws Exception + */ + public UserInfo getUserInfo (String username) throws Exception + { + Map userInfoMap = (Map)fileMap.get(propertyFileName); + if (userInfoMap == null) + return null; + return (UserInfo)userInfoMap.get(username); + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java new file mode 100644 index 00000000000..c4e40d7648c --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java @@ -0,0 +1,66 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jaas.spi; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.http.security.Credential; + +/** + * UserInfo + * + * This is the information read from the external source + * about a user. + * + * Can be cached by a UserInfoCache implementation + */ +public class UserInfo +{ + + private String userName; + private Credential credential; + private List roleNames; + + + public UserInfo (String userName, Credential credential, List roleNames) + { + this.userName = userName; + this.credential = credential; + this.roleNames = new ArrayList(); + if (roleNames != null) + this.roleNames.addAll(roleNames); + } + + public String getUserName() + { + return this.userName; + } + + public List getRoleNames () + { + return new ArrayList(this.roleNames); + } + + public boolean checkCredential (Object suppliedCredential) + { + return this.credential.check(suppliedCredential); + } + + protected Credential getCredential () + { + return this.credential; + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/EnvEntry.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/EnvEntry.java new file mode 100644 index 00000000000..fcebcccf4b8 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/EnvEntry.java @@ -0,0 +1,56 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jndi; + + +import javax.naming.NamingException; + + +/** + * EnvEntry + * + * + */ +public class EnvEntry extends NamingEntry +{ + private boolean overrideWebXml; + + + public EnvEntry (Object scope, String jndiName, Object objToBind, boolean overrideWebXml) + throws NamingException + { + super (scope, jndiName, objToBind); + this.overrideWebXml = overrideWebXml; + } + + + public EnvEntry (String jndiName, Object objToBind, boolean overrideWebXml) + throws NamingException + { + super(jndiName, objToBind); + this.overrideWebXml = overrideWebXml; + } + + public EnvEntry (String jndiName, Object objToBind) + throws NamingException + { + this(jndiName, objToBind, false); + } + + + public boolean isOverrideWebXml () + { + return this.overrideWebXml; + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Link.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Link.java new file mode 100644 index 00000000000..06482a67852 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Link.java @@ -0,0 +1,43 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jndi; + +import javax.naming.NamingException; + + + +public class Link extends NamingEntry +{ + + public Link(Object scope, String jndiName, Object object) throws NamingException + { + //jndiName is the name according to the web.xml + //objectToBind is the name in the environment + super(scope, jndiName, object); + } + + public Link (String jndiName, Object object) throws NamingException + { + super(null, jndiName, object); + } + + + public void bindToENC(String localName) throws NamingException + { + throw new UnsupportedOperationException("Method not supported for Link objects"); + } + + + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntry.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntry.java new file mode 100644 index 00000000000..2fd2bcd8848 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntry.java @@ -0,0 +1,206 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jndi; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.LinkRef; +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.NamingException; + +import org.eclipse.jetty.jndi.NamingUtil; +import org.eclipse.jetty.util.log.Log; + + + +/** + * NamingEntry + * + * Base class for all jndi related entities. Instances of + * subclasses of this class are declared in jetty.xml or in a + * webapp's WEB-INF/jetty-env.xml file. + * + * NOTE: that all NamingEntries will be bound in a single namespace. + * The "global" level is just in the top level context. The "local" + * level is a context specific to a webapp. + */ +public abstract class NamingEntry +{ + public static final String __contextName = "__"; //all NamingEntries stored in context called "__" + protected String jndiName; //the name representing the object associated with the NamingEntry + protected Object objectToBind; //the object associated with the NamingEntry + protected String namingEntryNameString; //the name of the NamingEntry relative to the context it is stored in + protected String objectNameString; //the name of the object relative to the context it is stored in + + + + + + public NamingEntry (Object scope, String jndiName, Object object) + throws NamingException + { + this.jndiName = jndiName; + this.objectToBind = object; + save(scope); + } + + /** + * Create a NamingEntry. + * A NamingEntry is a name associated with a value which can later + * be looked up in JNDI by a webapp. + * + * We create the NamingEntry and put it into JNDI where it can + * be linked to the webapp's env-entry, resource-ref etc entries. + * + * @param jndiName the name of the object which will eventually be in java:comp/env + * @param object the object to be bound + * @throws NamingException + */ + public NamingEntry (String jndiName, Object object) + throws NamingException + { + this (null, jndiName, object); + } + + + + + /** + * Add a java:comp/env binding for the object represented by this NamingEntry, + * but bind it as the name supplied + * @throws NamingException + */ + public void bindToENC(String localName) + throws NamingException + { + //TODO - check on the whole overriding/non-overriding thing + InitialContext ic = new InitialContext(); + Context env = (Context)ic.lookup("java:comp/env"); + Log.debug("Binding java:comp/env/"+localName+" to "+objectNameString); + NamingUtil.bind(env, localName, new LinkRef(objectNameString)); + } + + /** + * Unbind this NamingEntry from a java:comp/env + */ + public void unbindENC () + { + try + { + InitialContext ic = new InitialContext(); + Context env = (Context)ic.lookup("java:comp/env"); + Log.debug("Unbinding java:comp/env/"+getJndiName()); + env.unbind(getJndiName()); + } + catch (NamingException e) + { + Log.warn(e); + } + } + + /** + * Unbind this NamingEntry entirely + */ + public void release () + { + try + { + InitialContext ic = new InitialContext(); + ic.unbind(objectNameString); + ic.unbind(namingEntryNameString); + this.jndiName=null; + this.namingEntryNameString=null; + this.objectNameString=null; + this.objectToBind=null; + } + catch (NamingException e) + { + Log.warn(e); + } + } + + /** + * Get the unique name of the object + * relative to the scope + * @return + */ + public String getJndiName () + { + return this.jndiName; + } + + /** + * Get the object that is to be bound + * @return + */ + public Object getObjectToBind() + throws NamingException + { + return this.objectToBind; + } + + + /** + * Get the name of the object, fully + * qualified with the scope + * @return + */ + public String getJndiNameInScope () + { + return objectNameString; + } + + + + /** + * Save the NamingEntry for later use. + * + * Saving is done by binding the NamingEntry + * itself, and the value it represents into + * JNDI. In this way, we can link to the + * value it represents later, but also + * still retrieve the NamingEntry itself too. + * + * The object is bound at the jndiName passed in. + * This NamingEntry is bound at __/jndiName. + * + * eg + * + * jdbc/foo : DataSource + * __/jdbc/foo : NamingEntry + * + * @throws NamingException + */ + protected void save (Object scope) + throws NamingException + { + InitialContext ic = new InitialContext(); + NameParser parser = ic.getNameParser(""); + Name prefix = NamingEntryUtil.getNameForScope(scope); + + //bind the NamingEntry into the context + Name namingEntryName = NamingEntryUtil.makeNamingEntryName(parser, getJndiName()); + namingEntryName.addAll(0, prefix); + namingEntryNameString = namingEntryName.toString(); + NamingUtil.bind(ic, namingEntryNameString, this); + + //bind the object as well + Name objectName = parser.parse(getJndiName()); + objectName.addAll(0, prefix); + objectNameString = objectName.toString(); + NamingUtil.bind(ic, objectNameString, objectToBind); + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntryUtil.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntryUtil.java new file mode 100644 index 00000000000..d492fd14123 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntryUtil.java @@ -0,0 +1,246 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.plus.jndi; + + + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.Name; +import javax.naming.NameNotFoundException; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +import org.eclipse.jetty.util.log.Log; + + +public class NamingEntryUtil +{ + + + /** + * Link a name in a webapp's java:/comp/evn namespace to a pre-existing + * resource. The pre-existing resource can be either in the webapp's + * naming environment, or in the container's naming environment. Webapp's + * environment takes precedence over the server's namespace. + * + * @param asName the name to bind as + * @param mappedName the name from the environment to link to asName + * @param namingEntryType + * @throws NamingException + */ + public static boolean bindToENC (Object scope, String asName, String mappedName) + throws NamingException + { + if (asName==null||asName.trim().equals("")) + throw new NamingException ("No name for NamingEntry"); + + if (mappedName==null || "".equals(mappedName)) + mappedName=asName; + + NamingEntry entry = lookupNamingEntry (scope, mappedName); + if (entry == null) + return false; + + entry.bindToENC(asName); + return true; + } + + + + + + /** + * Find a NamingEntry in the given scope. + * + * @param scope + * @param jndiName + * @return + * @throws NamingException + */ + public static NamingEntry lookupNamingEntry (Object scope, String jndiName) + throws NamingException + { + NamingEntry entry = null; + try + { + Name scopeName = getNameForScope(scope); + InitialContext ic = new InitialContext(); + NameParser parser = ic.getNameParser(""); + Name namingEntryName = makeNamingEntryName(parser, jndiName); + scopeName.addAll(namingEntryName); + entry = (NamingEntry)ic.lookup(scopeName); + } + catch (NameNotFoundException ee) + { + } + + return entry; + } + + + public static Object lookup (Object scope, String jndiName) + throws NamingException + { + Object o = null; + + Name scopeName = getNameForScope(scope); + InitialContext ic = new InitialContext(); + NameParser parser = ic.getNameParser(""); + scopeName.addAll(parser.parse(jndiName)); + return ic.lookup(scopeName); + } + + + /** + * Get all NameEntries of a certain type in the given naming + * environment scope (server-wide names or context-specific names) + * + * @param scope + * @param clazz the type of the entry + * @return + * @throws NamingException + */ + public static List lookupNamingEntries (Object scope, Class clazz) + throws NamingException + { + try + { + Context scopeContext = getContextForScope(scope); + Context namingEntriesContext = (Context)scopeContext.lookup(NamingEntry.__contextName); + ArrayList list = new ArrayList(); + lookupNamingEntries(list, namingEntriesContext, clazz); + return list; + } + catch (NameNotFoundException e) + { + return Collections.EMPTY_LIST; + } + } + + + public static Name makeNamingEntryName (NameParser parser, NamingEntry namingEntry) + throws NamingException + { + return makeNamingEntryName(parser, (namingEntry==null?null:namingEntry.getJndiName())); + } + + public static Name makeNamingEntryName (NameParser parser, String jndiName) + throws NamingException + { + if (jndiName==null) + return null; + + if (parser==null) + { + InitialContext ic = new InitialContext(); + parser = ic.getNameParser(""); + } + + Name name = parser.parse(""); + name.add(NamingEntry.__contextName); + name.addAll(parser.parse(jndiName)); + return name; + } + + + public static Name getNameForScope (Object scope) + { + try + { + InitialContext ic = new InitialContext(); + NameParser parser = ic.getNameParser(""); + Name name = parser.parse(""); + if (scope != null) + { + name.add(canonicalizeScope(scope)); + } + return name; + } + catch (NamingException e) + { + Log.warn(e); + return null; + } + } + + public static Context getContextForScope(Object scope) + throws NamingException + { + + InitialContext ic = new InitialContext(); + NameParser parser = ic.getNameParser(""); + Name name = parser.parse(""); + if (scope != null) + { + name.add(canonicalizeScope(scope)); + } + return (Context)ic.lookup(name); + } + + public static Context getContextForNamingEntries (Object scope) + throws NamingException + { + Context scopeContext = getContextForScope(scope); + return (Context)scopeContext.lookup(NamingEntry.__contextName); + } + + /** + * Build up a list of NamingEntry objects that are of a specific type. + * + * @param list + * @param context + * @param clazz + * @return + * @throws NamingException + */ + private static List lookupNamingEntries (List list, Context context, Class clazz) + throws NamingException + { + try + { + NamingEnumeration nenum = context.listBindings(""); + while (nenum.hasMoreElements()) + { + Binding binding = (Binding)nenum.next(); + if (binding.getObject() instanceof Context) + lookupNamingEntries (list, (Context)binding.getObject(), clazz); + else if (clazz.isInstance(binding.getObject())) + list.add(binding.getObject()); + } + } + catch (NameNotFoundException e) + { + Log.debug("No entries of type "+clazz.getName()+" in context="+context); + } + + return list; + } + + private static String canonicalizeScope(Object scope) + { + if (scope==null) + return ""; + + String str = scope.toString(); + str=str.replace('/', '_').replace(' ', '_'); + return str; + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Resource.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Resource.java new file mode 100644 index 00000000000..4ddfd6716b6 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Resource.java @@ -0,0 +1,45 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jndi; + + +import javax.naming.NamingException; + + + +/** + * Resource + * + * + */ +public class Resource extends NamingEntry +{ + + public Resource (Object scope, String jndiName, Object objToBind) + throws NamingException + { + super(scope, jndiName, objToBind); + } + + /** + * @param jndiName + * @param objToBind + */ + public Resource (String jndiName, Object objToBind) + throws NamingException + { + super(jndiName, objToBind); + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Transaction.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Transaction.java new file mode 100644 index 00000000000..39cefe456e8 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/Transaction.java @@ -0,0 +1,113 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.jndi; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.LinkRef; +import javax.naming.NameNotFoundException; +import javax.naming.NamingException; +import javax.transaction.UserTransaction; + +import org.eclipse.jetty.jndi.NamingUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * Transaction + * + * Class to represent a JTA UserTransaction impl. + * + * + */ +/** + * Transaction + * + * + */ +public class Transaction extends NamingEntry +{ + public static final String USER_TRANSACTION = "UserTransaction"; + + + public static void bindToENC () + throws NamingException + { + Transaction txEntry = (Transaction)NamingEntryUtil.lookupNamingEntry(null, Transaction.USER_TRANSACTION); + + if ( txEntry != null ) + { + txEntry.bindToComp(); + } + else + { + throw new NameNotFoundException( USER_TRANSACTION + " not found" ); + } + } + + + + public Transaction (UserTransaction userTransaction) + throws NamingException + { + super (USER_TRANSACTION, userTransaction); + } + + + /** + * Allow other bindings of UserTransaction. + * + * These should be in ADDITION to java:comp/UserTransaction + * @see org.eclipse.jetty.server.server.plus.jndi.NamingEntry#bindToENC(java.lang.String) + */ + public void bindToENC (String localName) + throws NamingException + { + InitialContext ic = new InitialContext(); + Context env = (Context)ic.lookup("java:comp/env"); + Log.debug("Binding java:comp/env"+getJndiName()+" to "+objectNameString); + NamingUtil.bind(env, localName, new LinkRef(objectNameString)); + } + + /** + * Insist on the java:comp/UserTransaction binding + * @throws NamingException + */ + private void bindToComp () + throws NamingException + { + //ignore the name, it is always bound to java:comp + InitialContext ic = new InitialContext(); + Context env = (Context)ic.lookup("java:comp"); + Log.debug("Binding java:comp/"+getJndiName()+" to "+objectNameString); + NamingUtil.bind(env, getJndiName(), new LinkRef(objectNameString)); + } + + /** + * Unbind this Transaction from a java:comp + */ + public void unbindENC () + { + try + { + InitialContext ic = new InitialContext(); + Context env = (Context)ic.lookup("java:comp"); + Log.debug("Unbinding java:comp/"+getJndiName()); + env.unbind(getJndiName()); + } + catch (NamingException e) + { + Log.warn(e); + } + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java new file mode 100644 index 00000000000..79728c1d8f2 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java @@ -0,0 +1,499 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.plus.security; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import javax.naming.InitialContext; +import javax.naming.NameNotFoundException; +import javax.naming.NamingException; +import javax.sql.DataSource; + +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.plus.jndi.NamingEntryUtil; +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.MappedLoginService; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.log.Log; + + +/** + * + * //TODO JASPI cf JDBCLoginService + * DataSourceUserRealm + * + * Obtain user/password/role information from a database + * via jndi DataSource. + */ +public class DataSourceLoginService extends MappedLoginService +{ + private String _jndiName = "javax.sql.DataSource/default"; + private DataSource _datasource; + private Server _server; + private String _userTableName = "users"; + private String _userTableKey = "id"; + private String _userTableUserField = "username"; + private String _userTablePasswordField = "pwd"; + private String _roleTableName = "roles"; + private String _roleTableKey = "id"; + private String _roleTableRoleField = "role"; + private String _userRoleTableName = "user_roles"; + private String _userRoleTableUserKey = "user_id"; + private String _userRoleTableRoleKey = "role_id"; + private int _cacheMs = 30000; + private long _lastHashPurge = 0; + private String _userSql; + private String _roleSql; + private boolean _createTables = false; + + /* ------------------------------------------------------------ */ + public DataSourceLoginService() + { + } + + /* ------------------------------------------------------------ */ + public DataSourceLoginService(String name) + { + setName(name); + } + + /* ------------------------------------------------------------ */ + public DataSourceLoginService(String name, IdentityService identityService) + { + setName(name); + setIdentityService(identityService); + } + + /* ------------------------------------------------------------ */ + public void setJndiName (String jndi) + { + _jndiName = jndi; + } + + /* ------------------------------------------------------------ */ + public String getJndiName () + { + return _jndiName; + } + + /* ------------------------------------------------------------ */ + public void setServer (Server server) + { + _server=server; + } + + /* ------------------------------------------------------------ */ + public Server getServer() + { + return _server; + } + + /* ------------------------------------------------------------ */ + public void setCreateTables(boolean createTables) + { + _createTables = createTables; + } + + /* ------------------------------------------------------------ */ + public boolean getCreateTables() + { + return _createTables; + } + + /* ------------------------------------------------------------ */ + public void setUserTableName (String name) + { + _userTableName=name; + } + + /* ------------------------------------------------------------ */ + public String getUserTableName() + { + return _userTableName; + } + + /* ------------------------------------------------------------ */ + public String getUserTableKey() + { + return _userTableKey; + } + + + /* ------------------------------------------------------------ */ + public void setUserTableKey(String tableKey) + { + _userTableKey = tableKey; + } + + + /* ------------------------------------------------------------ */ + public String getUserTableUserField() + { + return _userTableUserField; + } + + + /* ------------------------------------------------------------ */ + public void setUserTableUserField(String tableUserField) + { + _userTableUserField = tableUserField; + } + + + /* ------------------------------------------------------------ */ + public String getUserTablePasswordField() + { + return _userTablePasswordField; + } + + + /* ------------------------------------------------------------ */ + public void setUserTablePasswordField(String tablePasswordField) + { + _userTablePasswordField = tablePasswordField; + } + + + /* ------------------------------------------------------------ */ + public String getRoleTableName() + { + return _roleTableName; + } + + + /* ------------------------------------------------------------ */ + public void setRoleTableName(String tableName) + { + _roleTableName = tableName; + } + + + /* ------------------------------------------------------------ */ + public String getRoleTableKey() + { + return _roleTableKey; + } + + + /* ------------------------------------------------------------ */ + public void setRoleTableKey(String tableKey) + { + _roleTableKey = tableKey; + } + + + /* ------------------------------------------------------------ */ + public String getRoleTableRoleField() + { + return _roleTableRoleField; + } + + + /* ------------------------------------------------------------ */ + public void setRoleTableRoleField(String tableRoleField) + { + _roleTableRoleField = tableRoleField; + } + + + /* ------------------------------------------------------------ */ + public String getUserRoleTableName() + { + return _userRoleTableName; + } + + + /* ------------------------------------------------------------ */ + public void setUserRoleTableName(String roleTableName) + { + _userRoleTableName = roleTableName; + } + + + /* ------------------------------------------------------------ */ + public String getUserRoleTableUserKey() + { + return _userRoleTableUserKey; + } + + + /* ------------------------------------------------------------ */ + public void setUserRoleTableUserKey(String roleTableUserKey) + { + _userRoleTableUserKey = roleTableUserKey; + } + + + /* ------------------------------------------------------------ */ + public String getUserRoleTableRoleKey() + { + return _userRoleTableRoleKey; + } + + + /* ------------------------------------------------------------ */ + public void setUserRoleTableRoleKey(String roleTableRoleKey) + { + _userRoleTableRoleKey = roleTableRoleKey; + } + + /* ------------------------------------------------------------ */ + public void setCacheMs (int ms) + { + _cacheMs=ms; + } + + /* ------------------------------------------------------------ */ + public int getCacheMs () + { + return _cacheMs; + } + + /* ------------------------------------------------------------ */ + @Override + protected void loadUsers() + { + } + + /* ------------------------------------------------------------ */ + /** Load user's info from database. + * + * @param user + */ + @Override + protected UserIdentity loadUser (String userName) + { + Connection connection = null; + try + { + initDb(); + connection = getConnection(); + + PreparedStatement statement = connection.prepareStatement(_userSql); + statement.setObject(1, userName); + ResultSet rs = statement.executeQuery(); + + if (rs.next()) + { + int key = rs.getInt(_userTableKey); + String credentials = rs.getString(_userTablePasswordField); + statement.close(); + + statement = connection.prepareStatement(_roleSql); + statement.setInt(1, key); + rs = statement.executeQuery(); + List roles = new ArrayList(); + while (rs.next()) + roles.add(rs.getString(_roleTableRoleField)); + statement.close(); + return putUser(userName,new Password(credentials), roles.toArray(new String[roles.size()])); + } + } + catch (NamingException e) + { + Log.warn("No datasource for "+_jndiName, e); + } + catch (SQLException e) + { + Log.warn("Problem loading user info for "+userName, e); + } + finally + { + if (connection != null) + { + try + { + connection.close(); + } + catch (SQLException x) + { + Log.warn("Problem closing connection", x); + } + finally + { + connection = null; + } + } + } + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Lookup the datasource for the jndiName and formulate the + * necessary sql query strings based on the configured table + * and column names. + * + * @throws NamingException + */ + public void initDb() throws NamingException, SQLException + { + if (_datasource != null) + return; + + InitialContext ic = new InitialContext(); + + //TODO webapp scope? + + //try finding the datasource in the Server scope + if (_server != null) + { + try + { + _datasource = (DataSource)NamingEntryUtil.lookup(_server, _jndiName); + } + catch (NameNotFoundException e) + { + //next try the jvm scope + } + } + + + //try finding the datasource in the jvm scope + if (_datasource==null) + { + _datasource = (DataSource)NamingEntryUtil.lookup(null, _jndiName); + } + + // set up the select statements based on the table and column names configured + _userSql = "select " + _userTableKey + "," + _userTablePasswordField + + " from " + _userTableName + + " where "+ _userTableUserField + " = ?"; + + _roleSql = "select r." + _roleTableRoleField + + " from " + _roleTableName + " r, " + _userRoleTableName + + " u where u."+ _userRoleTableUserKey + " = ?" + + " and r." + _roleTableKey + " = u." + _userRoleTableRoleKey; + + prepareTables(); + } + + + + private void prepareTables() + throws NamingException, SQLException + { + Connection connection = null; + boolean autocommit = true; + + if (_createTables) + { + try + { + connection = getConnection(); + autocommit = connection.getAutoCommit(); + connection.setAutoCommit(false); + DatabaseMetaData metaData = connection.getMetaData(); + + //check if tables exist + String tableName = (metaData.storesLowerCaseIdentifiers()? _userTableName.toLowerCase(): (metaData.storesUpperCaseIdentifiers()?_userTableName.toUpperCase(): _userTableName)); + ResultSet result = metaData.getTables(null, null, tableName, null); + if (!result.next()) + { + //user table default + /* + * create table _userTableName (_userTableKey integer, + * _userTableUserField varchar(100) not null unique, + * _userTablePasswordField varchar(20) not null, primary key(_userTableKey)); + */ + connection.createStatement().executeUpdate("create table "+_userTableName+ "("+_userTableKey+" integer,"+ + _userTableUserField+" varchar(100) not null unique,"+ + _userTablePasswordField+" varchar(20) not null, primary key("+_userTableKey+"))"); + if (Log.isDebugEnabled()) Log.debug("Created table "+_userTableName); + } + + result.close(); + + tableName = (metaData.storesLowerCaseIdentifiers()? _roleTableName.toLowerCase(): (metaData.storesUpperCaseIdentifiers()?_roleTableName.toUpperCase(): _roleTableName)); + result = metaData.getTables(null, null, tableName, null); + if (!result.next()) + { + //role table default + /* + * create table _roleTableName (_roleTableKey integer, + * _roleTableRoleField varchar(100) not null unique, primary key(_roleTableKey)); + */ + String str = "create table "+_roleTableName+" ("+_roleTableKey+" integer, "+ + _roleTableRoleField+" varchar(100) not null unique, primary key("+_roleTableKey+"))"; + connection.createStatement().executeUpdate(str); + if (Log.isDebugEnabled()) Log.debug("Created table "+_roleTableName); + } + + result.close(); + + tableName = (metaData.storesLowerCaseIdentifiers()? _userRoleTableName.toLowerCase(): (metaData.storesUpperCaseIdentifiers()?_userRoleTableName.toUpperCase(): _userRoleTableName)); + result = metaData.getTables(null, null, tableName, null); + if (!result.next()) + { + //user-role table + /* + * create table _userRoleTableName (_userRoleTableUserKey integer, + * _userRoleTableRoleKey integer, + * primary key (_userRoleTableUserKey, _userRoleTableRoleKey)); + * + * create index idx_user_role on _userRoleTableName (_userRoleTableUserKey); + */ + connection.createStatement().executeUpdate("create table "+_userRoleTableName+" ("+_userRoleTableUserKey+" integer, "+ + _userRoleTableRoleKey+" integer, "+ + "primary key ("+_userRoleTableUserKey+", "+_userRoleTableRoleKey+"))"); + connection.createStatement().executeUpdate("create index indx_user_role on "+_userRoleTableName+"("+_userRoleTableUserKey+")"); + if (Log.isDebugEnabled()) Log.debug("Created table "+_userRoleTableName +" and index"); + } + + result.close(); + connection.commit(); + } + finally + { + if (connection != null) + { + try + { + connection.setAutoCommit(autocommit); + connection.close(); + } + catch (SQLException e) + { + if (Log.isDebugEnabled()) Log.debug("Prepare tables", e); + } + finally + { + connection = null; + } + } + } + } + else if (Log.isDebugEnabled()) + { + Log.debug("createTables false"); + } + } + + + private Connection getConnection () + throws NamingException, SQLException + { + initDb(); + return _datasource.getConnection(); + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java new file mode 100644 index 00000000000..4d54191565b --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java @@ -0,0 +1,123 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.servlet; + +import javax.servlet.Filter; +import javax.servlet.Servlet; + +import org.eclipse.jetty.plus.annotation.InjectionCollection; +import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection; + +/** + * ServletHandler + * + * + */ +public class ServletHandler extends org.eclipse.jetty.servlet.ServletHandler +{ + + private InjectionCollection _injections = null; + private LifeCycleCallbackCollection _callbacks = null; + + + + /** + * @return the callbacks + */ + public LifeCycleCallbackCollection getCallbacks() + { + return _callbacks; + } + + + + /** + * @param callbacks the callbacks to set + */ + public void setCallbacks(LifeCycleCallbackCollection callbacks) + { + this._callbacks = callbacks; + } + + + + /** + * @return the injections + */ + public InjectionCollection getInjections() + { + return _injections; + } + + + + /** + * @param injections the injections to set + */ + public void setInjections(InjectionCollection injections) + { + this._injections = injections; + } + + /** + * @see org.eclipse.jetty.servlet.ServletHandler#customizeFilter(javax.servlet.Filter) + */ + public Filter customizeFilter(Filter filter) throws Exception + { + if (_injections != null) + _injections.inject(filter); + + if (_callbacks != null) + _callbacks.callPostConstructCallback(filter); + return super.customizeFilter(filter); + } + + + + /** + * @see org.eclipse.jetty.servlet.ServletHandler#customizeServlet(javax.servlet.Servlet) + */ + public Servlet customizeServlet(Servlet servlet) throws Exception + { + if (_injections != null) + _injections.inject(servlet); + if (_callbacks != null) + _callbacks.callPostConstructCallback(servlet); + return super.customizeServlet(servlet); + } + + + + /** + * @see org.eclipse.jetty.servlet.servlet.ServletHandler#cusomizeFilterDestroy(javax.servlet.Filter) + */ + public Filter customizeFilterDestroy(Filter filter) throws Exception + { + if (_callbacks != null) + _callbacks.callPreDestroyCallback(filter); + return super.customizeFilterDestroy(filter); + } + + + + /** + * @see org.eclipse.jetty.servlet.servlet.ServletHandler#customizeServletDestroy(javax.servlet.Servlet) + */ + public Servlet customizeServletDestroy(Servlet servlet) throws Exception + { + if (_callbacks != null) + _callbacks.callPreDestroyCallback(servlet); + return super.customizeServletDestroy(servlet); + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/AbstractConfiguration.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/AbstractConfiguration.java new file mode 100644 index 00000000000..68913fd7f33 --- /dev/null +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/AbstractConfiguration.java @@ -0,0 +1,471 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.plus.webapp; + + +import java.util.EventListener; +import java.util.Iterator; + +import javax.servlet.UnavailableException; + +import org.eclipse.jetty.plus.annotation.Injection; +import org.eclipse.jetty.plus.annotation.InjectionCollection; +import org.eclipse.jetty.plus.annotation.LifeCycleCallback; +import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection; +import org.eclipse.jetty.plus.annotation.PostConstructCallback; +import org.eclipse.jetty.plus.annotation.PreDestroyCallback; +import org.eclipse.jetty.plus.annotation.RunAsCollection; +import org.eclipse.jetty.plus.servlet.ServletHandler; +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebXmlConfiguration; +import org.eclipse.jetty.xml.XmlParser; + + + +/** + * Configuration + * + * + */ +public abstract class AbstractConfiguration extends WebXmlConfiguration +{ + protected LifeCycleCallbackCollection _callbacks = new LifeCycleCallbackCollection(); + protected InjectionCollection _injections = new InjectionCollection(); + protected RunAsCollection _runAsCollection = new RunAsCollection(); + protected SecurityHandler _securityHandler; + + public abstract void bindEnvEntry (String name, Object value) throws Exception; + + public abstract void bindResourceRef (String name, Class type) throws Exception; + + public abstract void bindResourceEnvRef (String name, Class type) throws Exception; + + public abstract void bindUserTransaction () throws Exception; + + public abstract void bindMessageDestinationRef (String name, Class type) throws Exception; + + + /** + * @throws ClassNotFoundException + */ + public AbstractConfiguration() throws ClassNotFoundException + { + super(); + } + + + public void setWebAppContext (WebAppContext context) + { + super.setWebAppContext(context); + + //set up our special ServletHandler to remember injections and lifecycle callbacks + ServletHandler servletHandler = new ServletHandler(); + _securityHandler = getWebAppContext().getSecurityHandler(); + org.eclipse.jetty.servlet.ServletHandler existingHandler = getWebAppContext().getServletHandler(); + servletHandler.setFilterMappings(existingHandler.getFilterMappings()); + servletHandler.setFilters(existingHandler.getFilters()); + servletHandler.setServlets(existingHandler.getServlets()); + servletHandler.setServletMappings(existingHandler.getServletMappings()); + getWebAppContext().setServletHandler(servletHandler); + _securityHandler.setHandler(servletHandler); + } + + public void configureDefaults () + throws Exception + { + super.configureDefaults(); + } + + public void configureWebApp () + throws Exception + { + super.configureWebApp(); + bindUserTransaction(); + } + + public void deconfigureWebApp() + throws Exception + { + //call any preDestroy methods on the listeners + callPreDestroyCallbacks(); + + super.deconfigureWebApp(); + } + + public void configure(String webXml) + throws Exception + { + //parse web.xml + super.configure(webXml); + + //parse classes for annotations, if necessary + if (!_metaDataComplete) + { + if (Log.isDebugEnabled()) Log.debug("Processing annotations"); + parseAnnotations(); + } + //do any injects on the listeners that were created and then + //also callback any postConstruct lifecycle methods + injectAndCallPostConstructCallbacks(); + } + + + + + + protected void initialize(XmlParser.Node config) + throws ClassNotFoundException,UnavailableException + { + super.initialize(config); + + //configure injections and callbacks to be called by the FilterHolder and ServletHolder + //when they lazily instantiate the Filter/Servlet. + ((ServletHandler)getWebAppContext().getServletHandler()).setInjections(_injections); + ((ServletHandler)getWebAppContext().getServletHandler()).setCallbacks(_callbacks); + } + + + protected void initWebXmlElement(String element,XmlParser.Node node) throws Exception + { + if ("env-entry".equals(element)) + { + initEnvEntry (node); + } + else if ("resource-ref".equals(element)) + { + //resource-ref entries are ONLY for connection factories + //the resource-ref says how the app will reference the jndi lookup relative + //to java:comp/env, but it is up to the deployer to map this reference to + //a real resource in the environment. At the moment, we insist that the + //jetty.xml file name of the resource has to be exactly the same as the + //name in web.xml deployment descriptor, but it shouldn't have to be + initResourceRef(node); + } + else if ("resource-env-ref".equals(element)) + { + //resource-env-ref elements are a non-connection factory type of resource + //the app looks them up relative to java:comp/env + //again, need a way for deployer to link up app naming to real naming. + //Again, we insist now that the name of the resource in jetty.xml is + //the same as web.xml + initResourceEnvRef(node); + } + else if ("message-destination-ref".equals(element)) + { + initMessageDestinationRef(node); + } + else if ("post-construct".equals(element)) + { + //post-construct is the name of a class and method to call after all + //resources have been setup but before the class is put into use + initPostConstruct(node); + } + else if ("pre-destroy".equals(element)) + { + //pre-destroy is the name of a class and method to call just as + //the instance is being destroyed + initPreDestroy(node); + } + else + { + super.initWebXmlElement(element, node); + } + + } + + /** + * JavaEE 5.4.1.3 + * + * + * @param node + * @throws Exception + */ + protected void initEnvEntry (XmlParser.Node node) + throws Exception + { + String name=node.getString("env-entry-name",false,true); + String type = node.getString("env-entry-type",false,true); + String valueStr = node.getString("env-entry-value",false,true); + + //if there's no value there's no point in making a jndi entry + //nor processing injection entries + if (valueStr==null || valueStr.equals("")) + { + Log.warn("No value for env-entry-name "+name); + return; + } + + //the javaee_5.xsd says that the env-entry-type is optional + //if there is an element, because you can get + //type from the element, but what to do if there is more + //than one element, do you just pick the type + //of the first one? + + //check for elements + initInjection (node, name, TypeUtil.fromName(type)); + + //bind the entry into jndi + Object value = TypeUtil.valueOf(type,valueStr); + bindEnvEntry(name, value); + + } + + + /** + * Common Annotations Spec section 2.3: + * resource-ref is for: + * - javax.sql.DataSource + * - javax.jms.ConnectionFactory + * - javax.jms.QueueConnectionFactory + * - javax.jms.TopicConnectionFactory + * - javax.mail.Session + * - java.net.URL + * - javax.resource.cci.ConnectionFactory + * - org.omg.CORBA_2_3.ORB + * - any other connection factory defined by a resource adapter + * @param node + * @throws Exception + */ + protected void initResourceRef (XmlParser.Node node) + throws Exception + { + String jndiName = node.getString("res-ref-name",false,true); + String type = node.getString("res-type", false, true); + String auth = node.getString("res-auth", false, true); + String shared = node.getString("res-sharing-scope", false, true); + + //check for elements + Class typeClass = TypeUtil.fromName(type); + if (typeClass==null) + typeClass = getWebAppContext().loadClass(type); + initInjection (node, jndiName, typeClass); + + bindResourceRef(jndiName, typeClass); + } + + + /** + * Common Annotations Spec section 2.3: + * resource-env-ref is for: + * - javax.transaction.UserTransaction + * - javax.resource.cci.InteractionSpec + * - anything else that is not a connection factory + * @param node + * @throws Exception + */ + protected void initResourceEnvRef (XmlParser.Node node) + throws Exception + { + String jndiName = node.getString("resource-env-ref-name",false,true); + String type = node.getString("resource-env-ref-type", false, true); + + //check for elements + + //JavaEE Spec sec 5.7.1.3 says the resource-env-ref-type + //is mandatory, but the schema says it is optional! + Class typeClass = TypeUtil.fromName(type); + if (typeClass==null) + typeClass = getWebAppContext().loadClass(type); + initInjection (node, jndiName, typeClass); + + bindResourceEnvRef(jndiName, typeClass); + } + + + /** + * Common Annotations Spec section 2.3: + * message-destination-ref is for: + * - javax.jms.Queue + * - javax.jms.Topic + * @param node + * @throws Exception + */ + protected void initMessageDestinationRef (XmlParser.Node node) + throws Exception + { + String jndiName = node.getString("message-destination-ref-name",false,true); + String type = node.getString("message-destination-type",false,true); + String usage = node.getString("message-destination-usage",false,true); + + Class typeClass = TypeUtil.fromName(type); + if (typeClass==null) + typeClass = getWebAppContext().loadClass(type); + initInjection(node, jndiName, typeClass); + + bindMessageDestinationRef(jndiName, typeClass); + } + + + + /** + * Process <post-construct> + * @param node + */ + protected void initPostConstruct(XmlParser.Node node) + { + String className = node.getString("lifecycle-callback-class", false, true); + String methodName = node.getString("lifecycle-callback-method", false, true); + + if (className==null || className.equals("")) + { + Log.warn("No lifecycle-callback-class specified"); + return; + } + if (methodName==null || methodName.equals("")) + { + Log.warn("No lifecycle-callback-method specified for class "+className); + return; + } + + try + { + Class clazz = getWebAppContext().loadClass(className); + LifeCycleCallback callback = new PostConstructCallback(); + callback.setTarget(clazz, methodName); + _callbacks.add(callback); + } + catch (ClassNotFoundException e) + { + Log.warn("Couldn't load post-construct target class "+className); + } + } + + + /** + * Process <pre-destroy> + * @param node + */ + protected void initPreDestroy(XmlParser.Node node) + { + String className = node.getString("lifecycle-callback-class", false, true); + String methodName = node.getString("lifecycle-callback-method", false, true); + if (className==null || className.equals("")) + { + Log.warn("No lifecycle-callback-class specified for pre-destroy"); + return; + } + if (methodName==null || methodName.equals("")) + { + Log.warn("No lifecycle-callback-method specified for pre-destroy class "+className); + return; + } + + try + { + Class clazz = getWebAppContext().loadClass(className); + LifeCycleCallback callback = new PreDestroyCallback(); + callback.setTarget(clazz, methodName); + _callbacks.add(callback); + } + catch (ClassNotFoundException e) + { + Log.warn("Couldn't load pre-destory target class "+className); + } + } + + + /** + * Iterate over the <injection-target> entries for a node + * + * @param node + * @param jndiName + * @param valueClass + * @return the type of the injectable + */ + protected void initInjection (XmlParser.Node node, String jndiName, Class valueClass) + { + Iterator itor = node.iterator("injection-target"); + + while(itor.hasNext()) + { + XmlParser.Node injectionNode = (XmlParser.Node)itor.next(); + String targetClassName = injectionNode.getString("injection-target-class", false, true); + String targetName = injectionNode.getString("injection-target-name", false, true); + if ((targetClassName==null) || targetClassName.equals("")) + { + Log.warn("No classname found in injection-target"); + continue; + } + if ((targetName==null) || targetName.equals("")) + { + Log.warn("No field or method name in injection-target"); + continue; + } + + // comments in the javaee_5.xsd file specify that the targetName is looked + // for first as a java bean property, then if that fails, as a field + try + { + Class clazz = getWebAppContext().loadClass(targetClassName); + Injection injection = new Injection(); + injection.setTargetClass(clazz); + injection.setJndiName(jndiName); + injection.setTarget(clazz, targetName, valueClass); + _injections.add(injection); + } + catch (ClassNotFoundException e) + { + Log.warn("Couldn't load injection target class "+targetClassName); + } + } + } + + + /** + * Parse all classes that are mentioned in web.xml (servlets, filters, listeners) + * for annotations. + * + * + * + * @throws Exception + */ + protected abstract void parseAnnotations () throws Exception; + + + + protected void injectAndCallPostConstructCallbacks() + throws Exception + { + //look thru the servlets to apply any runAs annotations + //NOTE: that any run-as in web.xml will already have been applied + ServletHolder[] holders = getWebAppContext().getServletHandler().getServlets(); + for (int i=0;holders!=null && i + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-rewrite + Jetty :: Rewrite Handler + Jetty Rewrite Handler + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.rewrite + J2SE-1.5 + org.eclipse.jetty.rewrite.handler;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + config.xml + + + + + + + + + + junit + junit + test + + + org.eclipse.jetty + jetty-server + ${project.version} + + + org.mortbay.jetty + servlet-api + + + org.eclipse.jetty + jetty-servlet-tester + ${project.version} + test + + + diff --git a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml new file mode 100644 index 00000000000..ead36951354 --- /dev/null +++ b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + true + false + requestedPath + + + + + + + + + + + + + /favicon.ico + Cache-Control + Max-Age=3600,public + true + + + + + + + + /some/old/context/* + /test/dump/newcontext + + + /test/dump/rewrite/* + /test/dump/rewritten + + + /test/dump/rewrite/protect/* + + + + /test/* + + + + /* + /test + + + + + + + /test/dump/regex/([^/]*)/(.*) + /test/dump/$2/$1 + + + + + + + diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java new file mode 100644 index 00000000000..b2eb1181389 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java @@ -0,0 +1,81 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * Sets the cookie in the response whenever the rule finds a match. + * + * @see Cookie + */ +public class CookiePatternRule extends PatternRule +{ + private String _name; + private String _value; + + /* ------------------------------------------------------------ */ + public CookiePatternRule() + { + _handling = false; + _terminating = false; + } + + /* ------------------------------------------------------------ */ + /** + * Assigns the cookie name. + * + * @param name a String specifying the name of the cookie. + */ + public void setName(String name) + { + _name = name; + } + + /* ------------------------------------------------------------ */ + /** + * Assigns the cookie value. + * + * @param value a String specifying the value of the cookie + * @see Cookie#setValue(String) + */ + public void setValue(String value) + { + _value = value; + } + + /* ------------------------------------------------------------ */ + /* + * (non-Javadoc) + * @see org.eclipse.jetty.server.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.addCookie(new Cookie(_name, _value)); + return target; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the cookie contents. + */ + public String toString() + { + return super.toString()+"["+_name+","+_value + "]"; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java new file mode 100644 index 00000000000..f3502b4de09 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java @@ -0,0 +1,53 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; + + +/** + * Set the scheme for the request + * + * + * + */ +public class ForwardedSchemeHeaderRule extends HeaderRule { + private String _scheme="https"; + + /* ------------------------------------------------------------ */ + public String getScheme() + { + return _scheme; + } + + /* ------------------------------------------------------------ */ + /** + * @param scheme the scheme to set on the request. Defaults to "https" + */ + public void setScheme(String scheme) + { + _scheme = scheme; + } + + /* ------------------------------------------------------------ */ + protected String apply(String target, String value, HttpServletRequest request, HttpServletResponse response) + { + ((Request) request).setScheme(_scheme); + return target; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java new file mode 100644 index 00000000000..b0fbbcaf76a --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java @@ -0,0 +1,126 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * Sets the header in the response whenever the rule finds a match. + */ +public class HeaderPatternRule extends PatternRule +{ + private String _name; + private String _value; + private boolean _add=false; + + /* ------------------------------------------------------------ */ + public HeaderPatternRule() + { + _handling = false; + _terminating = false; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the header name. + * + * @param name name of the header field + */ + public void setName(String name) + { + _name = name; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the header value. The value can be either a String or int value. + * + * @param value of the header field + */ + public void setValue(String value) + { + _value = value; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the Add flag. + * @param add If true, the header is added to the response, otherwise the header it is set on the response. + */ + public void setAdd(boolean add) + { + _add = add; + } + + /* ------------------------------------------------------------ */ + /** + * Invokes this method when a match found. If the header had already been set, + * the new value overwrites the previous one. Otherwise, it adds the new + * header name and value. + * + *@see org.eclipse.jetty.server.server.rewrite.handler.Rule#matchAndApply(String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + // process header + if (_add) + response.addHeader(_name, _value); + else + response.setHeader(_name, _value); + return target; + } + + + + /* ------------------------------------------------------------ */ + /** + * Returns the header name. + * @return the header name. + */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the header value. + * @return the header value. + */ + public String getValue() + { + return _value; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the add flag value. + */ + public boolean isAdd() + { + return _add; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the header contents. + */ + public String toString() + { + return super.toString()+"["+_name+","+_value+"]"; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java new file mode 100644 index 00000000000..a57b906ea4e --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java @@ -0,0 +1,102 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Abstract rule that matches against request headers. + */ + +public abstract class HeaderRule extends Rule +{ + private String _header; + private String _headerValue; + + /* ------------------------------------------------------------ */ + public String getHeader() + { + return _header; + } + + /* ------------------------------------------------------------ */ + /** + * @param header + * the header name to check for + */ + public void setHeader(String header) + { + _header = header; + } + + /* ------------------------------------------------------------ */ + public String getHeaderValue() + { + return _headerValue; + } + + /* ------------------------------------------------------------ */ + /** + * @param headerValue + * the header value to match against. If null, then the + * presence of the header is enough to match + */ + public void setHeaderValue(String headerValue) + { + _headerValue = headerValue; + } + + /* ------------------------------------------------------------ */ + @Override + public String matchAndApply(String target, HttpServletRequest request, + HttpServletResponse response) throws IOException + { + String requestHeaderValue = request.getHeader(_header); + + if (requestHeaderValue != null) + if (_headerValue == null || _headerValue.equals(requestHeaderValue)) + apply(target, requestHeaderValue, request, response); + + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Apply the rule to the request + * + * @param target + * field to attempt match + * @param value + * header value found + * @param request + * request object + * @param response + * response object + * @return The target (possible updated) + * @throws IOException + * exceptions dealing with operating on request or response + * objects + */ + protected abstract String apply(String target, String value, HttpServletRequest request, HttpServletResponse response) throws IOException; + + /* ------------------------------------------------------------ */ + public String toString() + { + return super.toString() + "[" + _header + ":" + _headerValue + "]"; + } + +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/LegacyRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/LegacyRule.java new file mode 100644 index 00000000000..038b02dd3f3 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/LegacyRule.java @@ -0,0 +1,94 @@ +// ======================================================================== +// $Id$ +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.util.URIUtil; + +/** + * Rule implementing the legacy API of RewriteHandler + * + * + */ +public class LegacyRule extends Rule +{ + private PathMap _rewrite = new PathMap(true); + + public LegacyRule() + { + _handling = false; + _terminating = false; + } + + /* ------------------------------------------------------------ */ + public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + Map.Entry rewrite =_rewrite.getMatch(target); + + if (rewrite!=null && rewrite.getValue()!=null) + { + target=URIUtil.addPaths(rewrite.getValue().toString(), + PathMap.pathInfo(rewrite.getKey().toString(),target)); + + return target; + } + + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the map of rewriting rules. + * @return A {@link PathMap} of the rewriting rules. + */ + public PathMap getRewrite() + { + return _rewrite; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the map of rewriting rules. + * @param rewrite A {@link PathMap} of the rewriting rules. Only + * prefix paths should be included. + */ + public void setRewrite(PathMap rewrite) + { + _rewrite=rewrite; + } + + + /* ------------------------------------------------------------ */ + /** Add a path rewriting rule + * @param pattern The path pattern to match. The pattern must start with / and may use + * a trailing /* as a wildcard. + * @param prefix The path prefix which will replace the matching part of the path. + */ + public void addRewriteRule(String pattern, String prefix) + { + if (pattern==null || pattern.length()==0 || !pattern.startsWith("/")) + throw new IllegalArgumentException(); + if (_rewrite==null) + _rewrite=new PathMap(true); + _rewrite.put(pattern,prefix); + } + + +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java new file mode 100644 index 00000000000..68fc849ee72 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java @@ -0,0 +1,91 @@ +// ======================================================================== +// $Id$ +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.util.StringMap; + +/** + * MSIE (Microsoft Internet Explorer) SSL Rule. + * Disable keep alive for SSL from IE5 or IE6 on Windows 2000. + * + * + * + */ +public class MsieSslRule extends Rule +{ + private static final int IEv5 = '5'; + private static final int IEv6 = '6'; + private static StringMap __IE6_BadOS = new StringMap(); + { + __IE6_BadOS.put("NT 5.01", Boolean.TRUE); + __IE6_BadOS.put("NT 5.0",Boolean.TRUE); + __IE6_BadOS.put("NT 4.0",Boolean.TRUE); + __IE6_BadOS.put("98",Boolean.TRUE); + __IE6_BadOS.put("98; Win 9x 4.90",Boolean.TRUE); + __IE6_BadOS.put("95",Boolean.TRUE); + __IE6_BadOS.put("CE",Boolean.TRUE); + } + + public MsieSslRule() + { + _handling = false; + _terminating = false; + } + + public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + if (request.isSecure()) + { + String user_agent = request.getHeader(HttpHeaders.USER_AGENT); + + if (user_agent!=null) + { + int msie=user_agent.indexOf("MSIE"); + if (msie>0 && user_agent.length()-msie>5) + { + // Get Internet Explorer Version + int ieVersion = user_agent.charAt(msie+5); + + if ( ieVersion<=IEv5) + { + response.setHeader(HttpHeaders.CONNECTION, HttpHeaderValues.CLOSE); + return target; + } + + if (ieVersion==IEv6) + { + int windows = user_agent.indexOf("Windows",msie+5); + if (windows>0) + { + int end=user_agent.indexOf(')',windows+8); + if(end<0 || __IE6_BadOS.getEntry(user_agent,windows+8,end-windows-8)!=null) + { + response.setHeader(HttpHeaders.CONNECTION, HttpHeaderValues.CLOSE); + return target; + } + } + } + } + } + } + return null; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java new file mode 100644 index 00000000000..3d73a7ed26e --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java @@ -0,0 +1,78 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; + +/** + * Abstract rule that use a {@link PathMap} for pattern matching. It uses the + * servlet pattern syntax. + */ +public abstract class PatternRule extends Rule +{ + protected String _pattern; + + + /* ------------------------------------------------------------ */ + public String getPattern() + { + return _pattern; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the rule pattern. + * + * @param pattern the pattern + */ + public void setPattern(String pattern) + { + _pattern = pattern; + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.server.server.handler.rules.RuleBase#matchAndApply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + if (PathMap.match(_pattern, target)) + { + return apply(target,request, response); + } + return null; + } + + /* ------------------------------------------------------------ */ + /** Apply the rule to the request + * @param target field to attempt match + * @param request request object + * @param response response object + * @return The target (possible updated) + * @throws IOException exceptions dealing with operating on request or response objects + */ + protected abstract String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException; + + /** + * Returns the rule pattern. + */ + public String toString() + { + return super.toString()+"["+_pattern+"]"; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java new file mode 100644 index 00000000000..ed88d694bb4 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java @@ -0,0 +1,64 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Redirects the response whenever the rule finds a match. + */ +public class RedirectPatternRule extends PatternRule +{ + private String _location; + + /* ------------------------------------------------------------ */ + public RedirectPatternRule() + { + _handling = true; + _terminating = true; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the redirect location. + * + * @param value the location to redirect. + */ + public void setLocation(String value) + { + _location = value; + } + + /* ------------------------------------------------------------ */ + /* + * (non-Javadoc) + * @see org.eclipse.jetty.server.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.sendRedirect(_location); + return target; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the redirect location. + */ + public String toString() + { + return super.toString()+"["+_location+"]"; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java new file mode 100644 index 00000000000..975203d3871 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java @@ -0,0 +1,83 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * Abstract rule to use as a base class for rules that match with a regular expression. + */ +public abstract class RegexRule extends Rule +{ + protected Pattern _regex; + + /* ------------------------------------------------------------ */ + /** + * Sets the regular expression string used to match with string URI. + * + * @param regex the regular expression. + */ + public void setRegex(String regex) + { + _regex=Pattern.compile(regex); + } + + /* ------------------------------------------------------------ */ + /** + * @return get the regular expression + */ + public String getRegex() + { + return _regex==null?null:_regex.pattern(); + } + + + /* ------------------------------------------------------------ */ + public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + Matcher matcher=_regex.matcher(target); + boolean matches = matcher.matches(); + if (matches) + return apply(target,request,response, matcher); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Apply this rule to the request/response pair. + * Called by {@link #matchAndApply(String, HttpServletRequest, HttpServletResponse)} if the regex matches. + * @param target field to attempt match + * @param request request object + * @param response response object + * @param matcher The Regex matcher that matched the request (with capture groups available for replacement). + * @return The target (possible updated). + * @throws IOException exceptions dealing with operating on request or response objects + */ + protected abstract String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) throws IOException; + + + /* ------------------------------------------------------------ */ + /** + * Returns the regular expression string. + */ + public String toString() + { + return super.toString()+"["+_regex+"]"; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java new file mode 100644 index 00000000000..97de8bb9e98 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java @@ -0,0 +1,86 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Sends the response code whenever the rule finds a match. + */ +public class ResponsePatternRule extends PatternRule +{ + private String _code; + private String _reason = ""; + + /* ------------------------------------------------------------ */ + public ResponsePatternRule() + { + _handling = true; + _terminating = true; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the response status code. + * @param code response code + */ + public void setCode(String code) + { + _code = code; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the reason for the response status code. Reasons will only reflect + * if the code value is greater or equal to 400. + * + * @param reason + */ + public void setReason(String reason) + { + _reason = reason; + } + + /* ------------------------------------------------------------ */ + /* + * (non-Javadoc) + * @see org.eclipse.jetty.server.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + int code = Integer.parseInt(_code); + + // status code 400 and up are error codes + if (code >= 400) + { + response.sendError(code, _reason); + } + else + { + response.setStatus(code); + } + return target; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the code and reason string. + */ + public String toString() + { + return super.toString()+"["+_code+","+_reason+"]"; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java new file mode 100644 index 00000000000..020595c09bb --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java @@ -0,0 +1,339 @@ +// ======================================================================== +// $Id$ +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.server.handler.HandlerWrapper; + +/* ------------------------------------------------------------ */ +/** + *

    Rewrite handler is responsible for managing the rules. Its capabilities + * is not only limited for url rewrites such as RewritePatternRule or RewriteRegexRule. + * There is also handling for cookies, headers, redirection, setting status or error codes + * whenever the rule finds a match. + * + *

    The rules can be matched by the ff. options: pattern matching of PathMap + * (class PatternRule), regular expressions (class RegexRule) or certain conditions set + * (e.g. MsieSslRule - the requests must be in SSL mode). + * + * Here are the list of rules: + *

      + *
    • CookiePatternRule - adds a new cookie in response.
    • + *
    • HeaderPatternRule - adds/modifies the HTTP headers in response.
    • + *
    • RedirectPatternRule - sets the redirect location.
    • + *
    • ResponsePatternRule - sets the status/error codes.
    • + *
    • RewritePatternRule - rewrites the requested URI.
    • + *
    • RewriteRegexRule - rewrites the requested URI using regular expression for pattern matching.
    • + *
    • MsieSslRule - disables the keep alive on SSL for IE5 and IE6.
    • + *
    • LegacyRule - the old version of rewrite.
    • + *
    • ForwardedSchemeHeaderRule - set the scheme according to the headers present.
    • + *
    + * + *

    The rules can be grouped into rule containers (class RuleContainerRule), and will only + * be applied if the request matches the conditions for their container + * (e.g., by virtual host name) + * + * Here are a list of rule containers: + *

      + *
    • VirtualHostRuleContainerRule - checks whether the request matches one of a set of virtual host names.
    • + *
    + * + * Here is a typical jetty.xml configuration would be:
    + * 
    + *   <Set name="handler">
    + *     <New id="Handlers" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
    + *       <Set name="rules">
    + *         <Array type="org.eclipse.jetty.rewrite.handler.Rule">
    + *
    + *           <Item> 
    + *             <New id="rewrite" class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
    + *               <Set name="pattern">/*</Set>
    + *               <Set name="replacement">/test</Set>
    + *             </New>
    + *           </Item>
    + *
    + *           <Item> 
    + *             <New id="response" class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
    + *               <Set name="pattern">/session/</Set>
    + *               <Set name="code">400</Set>
    + *               <Set name="reason">Setting error code 400</Set>
    + *             </New>
    + *           </Item>
    + *
    + *           <Item> 
    + *             <New id="header" class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
    + *               <Set name="pattern">*.jsp</Set>
    + *               <Set name="name">server</Set>
    + *               <Set name="value">dexter webserver</Set>
    + *             </New>
    + *           </Item>
    + *
    + *           <Item> 
    + *             <New id="header" class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
    + *               <Set name="pattern">*.jsp</Set>
    + *               <Set name="name">title</Set>
    + *               <Set name="value">driven header purpose</Set>
    + *             </New>
    + *           </Item>
    + *
    + *           <Item> 
    + *             <New id="redirect" class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
    + *               <Set name="pattern">/test/dispatch</Set>
    + *               <Set name="location">http://jetty.eclipse.org</Set>
    + *             </New>
    + *           </Item>
    + *
    + *           <Item> 
    + *             <New id="regexRewrite" class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
    + *               <Set name="regex">/test-jaas/$</Set>
    + *               <Set name="replacement">/demo</Set>
    + *             </New>
    + *           </Item>
    + *           
    + *           <Item> 
    + *             <New id="forwardedHttps" class="org.eclipse.jetty.rewrite.handler.ForwardedSchemeHeaderRule">
    + *               <Set name="header">X-Forwarded-Scheme</Set>
    + *               <Set name="headerValue">https</Set>
    + *               <Set name="scheme">https</Set>
    + *             </New>
    + *           </Item>
    + *           
    + *           <Item>
    + *             <New id="virtualHost" class="org.eclipse.jetty.rewrite.handler.VirtualHostRuleContainer">
    + *
    + *               <Set name="virtualHosts">
    + *                 <Array type="java.lang.String">
    + *                   <Item>eclipse.com</Item>
    + *                   <Item>www.eclipse.com</Item>
    + *                   <Item>eclipse.org</Item>
    + *                   <Item>www.eclipse.org</Item>
    + *                 </Array>
    + *               </Set>
    + *
    + *               <Call name="addRule">
    + *                 <Arg>
    + *                   <New class="org.eclipse.jetty.rewrite.handler.CookiePatternRule">
    + *                     <Set name="pattern">/*</Set>
    + *                     <Set name="name">CookiePatternRule</Set>
    + *                     <Set name="value">1</Set>
    + *                   </New>
    + *                 </Arg>
    + *               </Call>
    + *    
    + *             </New>
    + *           </      Item>
    + * 
    + *         </Array>
    + *       </Set>
    + *
    + *       <Set name="handler">
    + *         <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
    + *           <Set name="handlers">
    + *            <Array type="org.eclipse.jetty.server.Handler">
    + *              <Item>
    + *                <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
    + *              </Item>
    + *              <Item>
    + *                <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
    + *              </Item>
    + *              <Item>
    + *                <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
    + *              </Item>
    + *            </Array>
    + *           </Set>
    + *         </New>
    + *       </Set>
    + *
    + *     </New>
    + *   </Set>
    + * 
    + * + */ +public class RewriteHandler extends HandlerWrapper +{ + + private RuleContainer _rules; + + /* ------------------------------------------------------------ */ + public RewriteHandler() + { + _rules = new RuleContainer(); + } + + /* ------------------------------------------------------------ */ + /** + * To enable configuration from jetty.xml on rewriteRequestURI, rewritePathInfo and + * originalPathAttribute + * + * @param legacyRule old style rewrite rule + */ + public void setLegacyRule(LegacyRule legacyRule) + { + _rules.setLegacyRule(legacyRule); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the list of rules. + * @return an array of {@link Rule}. + */ + public Rule[] getRules() + { + return _rules.getRules(); + } + + /* ------------------------------------------------------------ */ + /** + * Assigns the rules to process. + * @param rules an array of {@link Rule}. + */ + public void setRules(Rule[] rules) + { + _rules.setRules(rules); + } + + /*------------------------------------------------------------ */ + /** + * Assigns the rules to process. + * @param rules a {@link RuleContainer} containing other rules to process + */ + public void setRules(RuleContainer rules) + { + _rules = rules; + } + + /* ------------------------------------------------------------ */ + /** + * Add a Rule + * @param rule The rule to add to the end of the rules array + */ + public void addRule(Rule rule) + { + _rules.addRule(rule); + } + + + /* ------------------------------------------------------------ */ + /** + * @return the rewriteRequestURI If true, this handler will rewrite the value + * returned by {@link HttpServletRequest#getRequestURI()}. + */ + public boolean isRewriteRequestURI() + { + return _rules.isRewriteRequestURI(); + } + + /* ------------------------------------------------------------ */ + /** + * @param rewriteRequestURI true if this handler will rewrite the value + * returned by {@link HttpServletRequest#getRequestURI()}. + */ + public void setRewriteRequestURI(boolean rewriteRequestURI) + { + _rules.setRewriteRequestURI(rewriteRequestURI); + } + + /* ------------------------------------------------------------ */ + /** + * @return true if this handler will rewrite the value + * returned by {@link HttpServletRequest#getPathInfo()}. + */ + public boolean isRewritePathInfo() + { + return _rules.isRewritePathInfo(); + } + + /* ------------------------------------------------------------ */ + /** + * @param rewritePathInfo true if this handler will rewrite the value + * returned by {@link HttpServletRequest#getPathInfo()}. + */ + public void setRewritePathInfo(boolean rewritePathInfo) + { + _rules.setRewritePathInfo(rewritePathInfo); + } + + /* ------------------------------------------------------------ */ + /** + * @return the originalPathAttribte. If non null, this string will be used + * as the attribute name to store the original request path. + */ + public String getOriginalPathAttribute() + { + return _rules.getOriginalPathAttribute(); + } + + /* ------------------------------------------------------------ */ + /** + * @param originalPathAttribte If non null, this string will be used + * as the attribute name to store the original request path. + */ + public void setOriginalPathAttribute(String originalPathAttribute) + { + _rules.setOriginalPathAttribute(originalPathAttribute); + } + + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public PathMap getRewrite() + { + return _rules.getRewrite(); + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public void setRewrite(PathMap rewrite) + { + _rules.setRewrite(rewrite); + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public void addRewriteRule(String pattern, String prefix) + { + _rules.addRewriteRule(pattern,prefix); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (isStarted()) + { + String returned = _rules.matchAndApply(target, request, response); + target = (returned == null) ? target : returned; + + if (!_rules.isHandled()) + { + super.handle(target, request, response); + } + } + } + +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java new file mode 100644 index 00000000000..9e134acac9e --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java @@ -0,0 +1,67 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.util.URIUtil; + +/** + * Rewrite the URI by replacing the matched {@link PathMap} path with a fixed string. + */ +public class RewritePatternRule extends PatternRule +{ + private String _replacement; + + /* ------------------------------------------------------------ */ + public RewritePatternRule() + { + _handling = false; + _terminating = false; + } + + /* ------------------------------------------------------------ */ + /** + * Whenever a match is found, it replaces with this value. + * + * @param value the replacement string. + */ + public void setReplacement(String value) + { + _replacement = value; + } + + /* ------------------------------------------------------------ */ + /* + * (non-Javadoc) + * @see org.eclipse.jetty.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + target = URIUtil.addPaths(_replacement, PathMap.pathInfo(_pattern,target)); + return target; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the replacement string. + */ + public String toString() + { + return super.toString()+"["+_replacement+"]"; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java new file mode 100644 index 00000000000..20e31d2aede --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java @@ -0,0 +1,72 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import java.util.regex.Matcher; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Rewrite the URI by matching with a regular expression. + * The replacement string may use $n" to replace the nth capture group. + */ +public class RewriteRegexRule extends RegexRule +{ + private String _replacement; + + /* ------------------------------------------------------------ */ + public RewriteRegexRule() + { + _handling = false; + _terminating = false; + } + + /* ------------------------------------------------------------ */ + /** + * Whenever a match is found, it replaces with this value. + * + * @param replacement the replacement string. + */ + public void setReplacement(String replacement) + { + _replacement = replacement; + } + + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.server.handler.rules.RegexRule#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.util.regex.Matcher) + */ + public String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) throws IOException + { + target=_replacement; + for (int g=1;g<=matcher.groupCount();g++) + { + String group = matcher.group(g); + target=target.replaceAll("\\$"+g,group); + } + + return target; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the replacement string. + */ + public String toString() + { + return super.toString()+"["+_replacement+"]"; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java new file mode 100644 index 00000000000..d27fdbe188f --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java @@ -0,0 +1,88 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * An abstract rule for creating rewrite rules. + */ +public abstract class Rule +{ + protected boolean _terminating; + protected boolean _handling; + + /** + * This method calls tests the rule against the request/response pair and if the Rule + * applies, then the rule's action is triggered. + * @param target The target of the request + * @param request + * @param response + * + * @return The new target if the rule has matched, else null + * @throws IOException TODO + */ + public abstract String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException; + + /** + * Sets terminating to true or false. + * If true, this rule will terminate the loop if this rule has been applied. + * + * @param terminating + */ + public void setTerminating(boolean terminating) + { + _terminating = terminating; + } + + /** + * Returns the terminating flag value. + * + * @return true if the rule needs to terminate; false otherwise. + */ + public boolean isTerminating() + { + return _terminating; + } + + /** + * Returns the handling flag value. + * + * @return true if the rule handles the request and nested handlers should not be called. + */ + public boolean isHandling() + { + return _handling; + } + + /** + * Set the handling flag value. + * + * @param handling true if the rule handles the request and nested handlers should not be called. + */ + public void setHandling(boolean handling) + { + _handling=handling; + } + + /** + * Returns the handling and terminating flag values. + */ + public String toString() + { + return this.getClass().getName()+(_handling?"[H":"[h")+(_terminating?"T]":"t]"); + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java new file mode 100644 index 00000000000..e956d728f46 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java @@ -0,0 +1,281 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; + +/** + * Base container to group rules. Can be extended so that the contained rules + * will only be applied under certain conditions + * + * + */ + +public class RuleContainer extends Rule +{ + protected Rule[] _rules; + protected boolean _handled; + + protected String _originalPathAttribute; + protected boolean _rewriteRequestURI=true; + protected boolean _rewritePathInfo=true; + + protected LegacyRule _legacy; + + /* ------------------------------------------------------------ */ + private LegacyRule getLegacyRule() + { + if (_legacy==null) + { + _legacy= new LegacyRule(); + addRule(_legacy); + } + return _legacy; + } + + + /* ------------------------------------------------------------ */ + /** + * To enable configuration from jetty.xml on rewriteRequestURI, rewritePathInfo and + * originalPathAttribute + * + * @param legacyRule old style rewrite rule + */ + public void setLegacyRule(LegacyRule legacyRule) + { + _legacy = legacyRule; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the list of rules. + * @return an array of {@link Rule}. + */ + public Rule[] getRules() + { + return _rules; + } + + /* ------------------------------------------------------------ */ + /** + * Assigns the rules to process. + * @param rules an array of {@link Rule}. + */ + public void setRules(Rule[] rules) + { + if (_legacy==null) + _rules = rules; + else + { + _rules=null; + addRule(_legacy); + if (rules!=null) + for (Rule rule:rules) + addRule(rule); + } + } + + /* ------------------------------------------------------------ */ + /** + * Add a Rule + * @param rule The rule to add to the end of the rules array + */ + public void addRule(Rule rule) + { + _rules = (Rule[])LazyList.addToArray(_rules,rule,Rule.class); + } + + + /* ------------------------------------------------------------ */ + /** + * @return the rewriteRequestURI If true, this handler will rewrite the value + * returned by {@link HttpServletRequest#getRequestURI()}. + */ + public boolean isRewriteRequestURI() + { + return _rewriteRequestURI; + } + + /* ------------------------------------------------------------ */ + /** + * @param rewriteRequestURI true if this handler will rewrite the value + * returned by {@link HttpServletRequest#getRequestURI()}. + */ + public void setRewriteRequestURI(boolean rewriteRequestURI) + { + _rewriteRequestURI=rewriteRequestURI; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if this handler will rewrite the value + * returned by {@link HttpServletRequest#getPathInfo()}. + */ + public boolean isRewritePathInfo() + { + return _rewritePathInfo; + } + + /* ------------------------------------------------------------ */ + /** + * @param rewritePathInfo true if this handler will rewrite the value + * returned by {@link HttpServletRequest#getPathInfo()}. + */ + public void setRewritePathInfo(boolean rewritePathInfo) + { + _rewritePathInfo=rewritePathInfo; + } + + /* ------------------------------------------------------------ */ + /** + * @return the originalPathAttribte. If non null, this string will be used + * as the attribute name to store the original request path. + */ + public String getOriginalPathAttribute() + { + return _originalPathAttribute; + } + + /* ------------------------------------------------------------ */ + /** + * @param originalPathAttribte If non null, this string will be used + * as the attribute name to store the original request path. + */ + public void setOriginalPathAttribute(String originalPathAttribte) + { + _originalPathAttribute=originalPathAttribte; + } + + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public PathMap getRewrite() + { + return getLegacyRule().getRewrite(); + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public void setRewrite(PathMap rewrite) + { + getLegacyRule().setRewrite(rewrite); + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public void addRewriteRule(String pattern, String prefix) + { + getLegacyRule().addRewriteRule(pattern,prefix); + } + + + /** + * @return handled true if one of the rules within the rule container is handling the request + */ + public boolean isHandled() + { + return _handled; + } + + /*------------------------------------------------------------ */ + /** + * @param handled true if one of the rules within the rule container is handling the request + */ + public void setHandled(boolean handled) + { + _handled=handled; + } + + + /** + * Process the contained rules + * @param target target field to pass on to the contained rules + * @param request request object to pass on to the contained rules + * @param response response object to pass on to the contained rules + */ + @Override + public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + return apply(target, request, response); + } + + /** + * Process the contained rules (called by matchAndApply) + * @param target target field to pass on to the contained rules + * @param request request object to pass on to the contained rules + * @param response response object to pass on to the contained rules + */ + protected String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + _handled=false; + + boolean original_set=_originalPathAttribute==null; + + for (Rule rule : _rules) + { + String applied=rule.matchAndApply(target,request, response); + if (applied!=null) + { + Log.debug("applied {}",rule); + if (!target.equals(applied)) + { + Log.debug("rewrote {} to {}",target,applied); + if (!original_set) + { + original_set=true; + request.setAttribute(_originalPathAttribute, target); + } + + if (_rewriteRequestURI) + ((Request)request).setRequestURI(applied); + + if (_rewritePathInfo) + ((Request)request).setPathInfo(applied); + + target=applied; + } + + if (rule.isHandling()) + { + Log.debug("handling {}",rule); + _handled=true; + (request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest()).setHandled(true); + } + + if (rule.isTerminating()) + { + Log.debug("terminating {}",rule); + break; + } + } + } + + return target; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java new file mode 100644 index 00000000000..eabb07837e6 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java @@ -0,0 +1,111 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.util.LazyList; + +/** + * Groups rules that apply only to a specific virtual host + * or sets of virtual hosts + * + * + */ + +public class VirtualHostRuleContainer extends RuleContainer +{ + private String[] _virtualHosts; + + /* ------------------------------------------------------------ */ + /** Set the virtual hosts that the rules within this container will apply to + * @param virtualHosts Array of virtual hosts that the rules within this container are applied to. + * A null hostname or null/empty array means any hostname is acceptable. + */ + public void setVirtualHosts( String[] virtualHosts ) + { + if ( virtualHosts == null ) + { + _virtualHosts = virtualHosts; + } + else + { + _virtualHosts = new String[virtualHosts.length]; + for ( int i = 0; i < virtualHosts.length; i++ ) + _virtualHosts[i] = normalizeHostname( virtualHosts[i]); + } + } + + /* ------------------------------------------------------------ */ + /** Get the virtual hosts that the rules within this container will apply to + * @param virtualHosts Array of virtual hosts that the rules within this container are applied to. + * A null hostname or null/empty array means any hostname is acceptable. + */ + public String[] getVirtualHosts() + { + return _virtualHosts; + } + + /* ------------------------------------------------------------ */ + /** + * @param virtualHost add a virtual host to the existing list of virtual hosts + * A null hostname means any hostname is acceptable + */ + public void addVirtualHost(String virtualHost) + { + _virtualHosts = (String[])LazyList.addToArray(_virtualHosts,virtualHost,String.class); + } + + /** + * Process the contained rules if the request is applicable to the virtual hosts of this rule + * @param target target field to pass on to the contained rules + * @param request request object to pass on to the contained rules + * @param response response object to pass on to the contained rules + */ + @Override + public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + if(_virtualHosts != null && _virtualHosts.length > 0 ) + { + String requestHost = normalizeHostname( request.getServerName() ); + for( String ruleHost : _virtualHosts ) + { + if(ruleHost == null || ruleHost.equalsIgnoreCase(requestHost) + || (ruleHost.startsWith("*.") && ruleHost.regionMatches(true,2,requestHost,requestHost.indexOf(".")+1,ruleHost.length()-2))) + return apply(target, request, response); + } + } + else + { + return apply(target, request, response); + } + return null; + } + + /* ------------------------------------------------------------ */ + private String normalizeHostname( String host ) + { + if ( host == null ) + return null; + + if ( host.endsWith( "." ) ) + return host.substring( 0, host.length() -1); + + return host; + } + +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java new file mode 100644 index 00000000000..b2b818d95e4 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java @@ -0,0 +1,76 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import junit.framework.TestCase; + +import org.eclipse.jetty.io.bio.StringEndPoint; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; + +public abstract class AbstractRuleTestCase extends TestCase +{ + protected Server _server=new Server(); + protected LocalConnector _connector; + protected StringEndPoint _endpoint=new StringEndPoint(); + protected HttpConnection _connection; + + protected Request _request; + protected Response _response; + + protected boolean _isSecure = false; + + public void setUp() throws Exception + { + start(); + } + + public void tearDown() throws Exception + { + stop(); + } + + public void start() throws Exception + { + _connector = new LocalConnector() { + public boolean isConfidential(Request request) + { + return _isSecure; + } + }; + + _server.setConnectors(new Connector[]{_connector}); + _server.start(); + reset(); + } + + public void stop() throws Exception + { + _server.stop(); + _request = null; + _response = null; + } + + public void reset() + { + _connection = new HttpConnection(_connector,_endpoint,_server); + _request = new Request(_connection); + _response = new Response(_connection); + + _request.setRequestURI("/test/"); + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java new file mode 100644 index 00000000000..4eed73b6ac5 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java @@ -0,0 +1,81 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import java.util.Enumeration; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaders; + + +public class CookiePatternRuleTest extends AbstractRuleTestCase +{ + public void setUp() throws Exception + { + super.setUp(); + } + + public void testSingleCookie() throws IOException + { + String[][] cookie = { + {"cookie", "value"} + }; + + assertCookies(cookie); + } + + public void testMultipleCookies() throws IOException + { + String[][] cookies = { + {"cookie", "value"}, + {"name", "wolfgangpuck"}, + {"age", "28"} + }; + + assertCookies(cookies); + } + + private void assertCookies(String[][] cookies) throws IOException + { + for (int i = 0; i < cookies.length; i++) + { + String[] cookie = cookies[i]; + + // set cookie pattern + CookiePatternRule rule = new CookiePatternRule(); + rule.setPattern("*"); + rule.setName(cookie[0]); + rule.setValue(cookie[1]); + + System.out.println(rule.toString()); + + // apply cookie pattern + rule.apply(_request.getRequestURI(), _request, _response); + + // verify + HttpFields httpFields = _response.getHttpFields(); + Enumeration e = httpFields.getValues(HttpHeaders.SET_COOKIE_BUFFER); + int index = 0; + while (e.hasMoreElements()) + { + String[] result = ((String)e.nextElement()).split("="); + assertEquals(cookies[index][0], result[0]); + assertEquals(cookies[index][1], result[1]); + + // +1 cookies index + index++; + } + } + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java new file mode 100644 index 00000000000..97bd9175a62 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java @@ -0,0 +1,89 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + +import org.eclipse.jetty.http.HttpFields; + +public class ForwardedSchemeHeaderRuleTest extends AbstractRuleTestCase +{ + private ForwardedSchemeHeaderRule _rule; + private HttpFields _requestHeaderFields; + + public void setUp() throws Exception + { + super.setUp(); + _rule = new ForwardedSchemeHeaderRule(); + _requestHeaderFields = _connection.getRequestFields(); + _request.setScheme(null); + } + + public void testDefaultScheme() throws Exception + { + setRequestHeader("X-Forwarded-Scheme", "https"); + _rule.setHeader("X-Forwarded-Scheme"); + _rule.setHeaderValue("https"); + + _rule.matchAndApply("/", _request, _response); + assertEquals("https", _request.getScheme()); + } + + public void testScheme() throws Exception + { + setRequestHeader("X-Forwarded-Scheme", "https"); + _rule.setHeader("X-Forwarded-Scheme"); + _rule.setHeaderValue("https"); + _rule.setScheme("https"); + + _rule.matchAndApply("/", _request, _response); + assertEquals("https", _request.getScheme()); + + + _rule.setScheme("http"); + _rule.matchAndApply("/", _request, _response); + assertEquals("http", _request.getScheme()); + } + + public void testHeaderValue() throws Exception + { + setRequestHeader("Front-End-Https", "on"); + _rule.setHeader("Front-End-Https"); + _rule.setHeaderValue("on"); + _rule.setScheme("https"); + + _rule.matchAndApply("/",_request,_response); + assertEquals("https",_request.getScheme()); + _request.setScheme(null); + + + // header value doesn't match rule's value + setRequestHeader("Front-End-Https", "off"); + + _rule.matchAndApply("/",_request,_response); + assertEquals(null,_request.getScheme()); + _request.setScheme(null); + + + // header value can be any value + setRequestHeader("Front-End-Https", "any"); + _rule.setHeaderValue(null); + + _rule.matchAndApply("/",_request,_response); + assertEquals("https",_request.getScheme()); + } + + private void setRequestHeader(String header, String headerValue) + { + _requestHeaderFields.put(header, headerValue); + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java new file mode 100644 index 00000000000..7a6ada766f5 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java @@ -0,0 +1,105 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import java.util.Enumeration; + + +public class HeaderPatternRuleTest extends AbstractRuleTestCase +{ + private HeaderPatternRule _rule; + + public void setUp() throws Exception + { + super.setUp(); + + _rule = new HeaderPatternRule(); + _rule.setPattern("*"); + } + + public void testHeaderWithTextValues() throws IOException + { + // different keys + String headers[][] = { + { "hnum#1", "test1" }, + { "hnum#2", "2test2" }, + { "hnum#3", "test3" } + }; + + assertHeaders(headers); + } + + public void testHeaderWithNumberValues() throws IOException + { + String headers[][] = { + { "hello", "1" }, + { "hello", "-1" }, + { "hello", "100" }, + { "hello", "100" }, + { "hello", "100" }, + { "hello", "100" }, + { "hello", "100" }, + + { "hello1", "200" } + }; + + assertHeaders(headers); + } + + public void testHeaderOverwriteValues() throws IOException + { + String headers[][] = { + { "size", "100" }, + { "size", "200" }, + { "size", "300" }, + { "size", "400" }, + { "size", "500" }, + { "title", "abc" }, + { "title", "bac" }, + { "title", "cba" }, + { "title1", "abba" }, + { "title1", "abba1" }, + { "title1", "abba" }, + { "title1", "abba1" } + }; + + assertHeaders(headers); + + Enumeration e = _response.getHeaders("size"); + int count = 0; + while (e.hasMoreElements()) + { + e.nextElement(); + count++; + } + + assertEquals(1, count); + assertEquals("500", _response.getHeader("size")); + assertEquals("cba", _response.getHeader("title")); + assertEquals("abba1", _response.getHeader("title1")); + } + + private void assertHeaders(String headers[][]) throws IOException + { + for (int i = 0; i < headers.length; i++) + { + _rule.setName(headers[i][0]); + _rule.setValue(headers[i][1]); + + _rule.apply(null, _request, _response); + + assertEquals(headers[i][1], _response.getHeader(headers[i][0])); + } + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/LegacyRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/LegacyRuleTest.java new file mode 100644 index 00000000000..314fa08e080 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/LegacyRuleTest.java @@ -0,0 +1,62 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + + +public class LegacyRuleTest extends AbstractRuleTestCase +{ + private LegacyRule _rule; + + String[][] _tests= + { + {"/foo/bar","/*","/replace/foo/bar"}, + {"/foo/bar","/foo/*","/replace/bar"}, + {"/foo/bar","/foo/bar","/replace"} + }; + + public void setUp() throws Exception + { + super.setUp(); + _rule = new LegacyRule(); + } + + public void tearDown() + { + _rule = null; + } + + public void testMatchAndApply() throws Exception + { + for (int i=0;i<_tests.length;i++) + { + _rule.addRewriteRule(_tests[i][1], "/replace"); + + String result = _rule.matchAndApply(_tests[i][0], _request, _response); + + assertEquals(_tests[i][1], _tests[i][2], result); + } + } + + public void testAddRewrite() + { + try + { + _rule.addRewriteRule("*.txt", "/replace"); + fail(); + } + catch (IllegalArgumentException e) + { + } + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java new file mode 100644 index 00000000000..fab8cc28e0b --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java @@ -0,0 +1,222 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; + + +public class MsieSslRuleTest extends AbstractRuleTestCase +{ + private MsieSslRule _rule; + + public void setUp() throws Exception + { + // enable SSL + _isSecure = true; + + super.setUp(); + _rule = new MsieSslRule(); + } + + public void testWin2kWithIE5() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)"); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));; + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));; + } + + public void testWin2kWithIE6() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWin2kWithIE7() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.0)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(null, result); + assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWin2kSP1WithIE5() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.01)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.01)"); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.01)"); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWin2kSP1WithIE6() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.01)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWin2kSP1WithIE7() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.01)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(null, result); + assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWinXpWithIE5() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.1)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.1)"); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1)"); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWinXpWithIE6() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(null, result); + assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWinXpWithIE7() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(null, result); + assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWinVistaWithIE5() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 6.0)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 6.0)"); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.0)"); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWinVistaWithIE6() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(null, result); + assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWinVistaWithIE7() throws Exception + { + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(null, result); + assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION)); + } + + public void testWithoutSsl() throws Exception + { + // disable SSL + _isSecure = false; + super.stop(); + super.start(); + + HttpFields fields = _connection.getRequestFields(); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(null, result); + assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION)); + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java new file mode 100644 index 00000000000..fa6baebeb5f --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java @@ -0,0 +1,148 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Request; + +public class PatternRuleTest extends TestCase +{ + private PatternRule _rule; + + public void setUp() + { + _rule = new TestPatternRule(); + } + + public void tearDown() + { + _rule = null; + } + + public void testTrueMatch() throws IOException + { + String[][] matchCases = { + // index 0 - pattern + // index 1 - URI to match + + {"/abc", "/abc"}, + {"/abc/", "/abc/"}, + + {"/abc/path/longer", "/abc/path/longer"}, + {"/abc/path/longer/", "/abc/path/longer/"}, + + {"/abc/*", "/abc/hello.jsp"}, + {"/abc/*", "/abc/a"}, + {"/abc/*", "/abc/a/hello.jsp"}, + {"/abc/*", "/abc/a/b"}, + {"/abc/*", "/abc/a/b/hello.jsp"}, + {"/abc/*", "/abc/a/b/c"}, + {"/abc/*", "/abc/a/b/c/hello.jsp"}, + + {"/abc/def/*", "/abc/def/gf"}, + {"/abc/def/*", "/abc/def/gf.html"}, + {"/abc/def/*", "/abc/def/ghi"}, + {"/abc/def/*", "/abc/def/ghi/"}, + {"/abc/def/*", "/abc/def/ghi/hello.html"}, + + {"*.do", "/abc.do"}, + {"*.do", "/abc/hello.do"}, + {"*.do", "/abc/def/hello.do"}, + {"*.do", "/abc/def/ghi/hello.do"}, + + {"*.jsp", "/abc.jsp"}, + {"*.jsp", "/abc/hello.jsp"}, + {"*.jsp", "/abc/def/hello.jsp"}, + {"*.jsp", "/abc/def/ghi/hello.jsp"}, + + {"/", "/Other"}, + {"/", "/Other/hello.do"}, + {"/", "/Other/path"}, + {"/", "/Other/path/hello.do"}, + {"/", "/abc/def"}, + + {"/abc:/def", "/abc:/def"} + }; + + for (int i = 0; i < matchCases.length; i++) + { + String[] matchCase = matchCases[i]; + assertMatch(true, matchCase); + } + } + + public void testFalseMatch() throws IOException + { + String[][] matchCases = { + + {"/abc", "/abcd"}, + {"/abc/", "/abcd/"}, + + {"/abc/path/longer", "/abc/path/longer/"}, + {"/abc/path/longer", "/abc/path/longer1"}, + {"/abc/path/longer/", "/abc/path/longer"}, + {"/abc/path/longer/", "/abc/path/longer1/"}, + + {"/*.jsp", "/hello.jsp"}, + {"/abc/*.jsp", "/abc/hello.jsp"}, + + {"*.jsp", "/hello.1jsp"}, + {"*.jsp", "/hello.jsp1"}, + {"*.jsp", "/hello.do"}, + + {"*.jsp", "/abc/hello.do"}, + {"*.jsp", "/abc/def/hello.do"}, + {"*.jsp", "/abc.do"} + }; + + for (int i = 0; i < matchCases.length; i++) + { + String[] matchCase = matchCases[i]; + assertMatch(false, matchCase); + } + } + + private void assertMatch(boolean flag, String[] matchCase) throws IOException + { + _rule.setPattern(matchCase[0]); + final String uri=matchCase[1]; + String result = _rule.matchAndApply(uri, + new Request() + { + { + setRequestURI(uri); + } + }, null + ); + + assertEquals("pattern: " + matchCase[0] + " uri: " + matchCase[1], flag, result!=null); + } + + + private class TestPatternRule extends PatternRule + { + @Override + public String apply(String target, + HttpServletRequest request, HttpServletResponse response) throws IOException + { + return target; + } + + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java new file mode 100644 index 00000000000..5512fd8d28b --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java @@ -0,0 +1,46 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import org.eclipse.jetty.http.HttpHeaders; + + +public class RedirectPatternRuleTest extends AbstractRuleTestCase +{ + private RedirectPatternRule _rule; + + public void setUp() throws Exception + { + super.setUp(); + _rule = new RedirectPatternRule(); + _rule.setPattern("*"); + } + + public void tearDown() + { + _rule = null; + } + + public void testLocation() throws IOException + { + String location = "http://eclipse.com"; + + _rule.setLocation(location); + _rule.apply(null, _request, _response); + + assertEquals(location, _response.getHeader(HttpHeaders.LOCATION)); + } + +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java new file mode 100644 index 00000000000..ef5d20025f5 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java @@ -0,0 +1,110 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import java.util.regex.Matcher; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Request; + +public class RegexRuleTest extends TestCase +{ + private RegexRule _rule; + + public void setUp() + { + _rule = new TestRegexRule(); + + } + + public void tearDown() + { + _rule = null; + } + + public void testTrueMatch() throws IOException + { + String[][] matchCases = { + // regex: *.jsp + {"/.*.jsp", "/hello.jsp"}, + {"/.*.jsp", "/abc/hello.jsp"}, + + // regex: /abc or /def + {"/abc|/def", "/abc"}, + {"/abc|/def", "/def"}, + + // regex: *.do or *.jsp + {".*\\.do|.*\\.jsp", "/hello.do"}, + {".*\\.do|.*\\.jsp", "/hello.jsp"}, + {".*\\.do|.*\\.jsp", "/abc/hello.do"}, + {".*\\.do|.*\\.jsp", "/abc/hello.jsp"}, + + {"/abc/.*.htm|/def/.*.htm", "/abc/hello.htm"}, + {"/abc/.*.htm|/def/.*.htm", "/abc/def/hello.htm"}, + + // regex: /abc/*.jsp + {"/abc/.*.jsp", "/abc/hello.jsp"}, + {"/abc/.*.jsp", "/abc/def/hello.jsp"} + }; + + for (int i = 0; i < matchCases.length; i++) + { + String[] matchCase = matchCases[i]; + assertMatch(true, matchCase); + } + } + + public void testFalseMatch() throws IOException + { + String[][] matchCases = { + {"/abc/.*.jsp", "/hello.jsp"} + }; + + for (int i = 0; i < matchCases.length; i++) + { + String[] matchCase = matchCases[i]; + assertMatch(false, matchCase); + } + } + + private void assertMatch(boolean flag, String[] matchCase) throws IOException + { + _rule.setRegex(matchCase[0]); + final String uri=matchCase[1]; + String result = _rule.matchAndApply(uri, + new Request() + { + public String getRequestURI() + { + return uri; + } + }, null + ); + + assertEquals("regex: " + matchCase[0] + " uri: " + matchCase[1], flag, result!=null); + } + + private class TestRegexRule extends RegexRule + { + public String apply(String target,HttpServletRequest request,HttpServletResponse response, Matcher matcher) throws IOException + { + return target; + } + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java new file mode 100644 index 00000000000..0770c141ff4 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java @@ -0,0 +1,78 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +public class ResponsePatternRuleTest extends AbstractRuleTestCase +{ + private ResponsePatternRule _rule; + + public void setUp() throws Exception + { + super.setUp(); + _rule = new ResponsePatternRule(); + _rule.setPattern("/test"); + } + + public void testStatusCodeNoReason() throws IOException + { + for (int i = 1; i < 400; i++) + { + _rule.setCode("" + i); + _rule.apply(null, _request, _response); + + assertEquals(i, _response.getStatus()); + } + } + + public void testStatusCodeWithReason() throws IOException + { + for (int i = 1; i < 400; i++) + { + _rule.setCode("" + i); + _rule.setReason("reason" + i); + _rule.apply(null, _request, _response); + + assertEquals(i, _response.getStatus()); + assertEquals(null, _response.getReason()); + } + } + + public void testErrorStatusNoReason() throws IOException + { + for (int i = 400; i < 600; i++) + { + _rule.setCode("" + i); + _rule.apply(null, _request, _response); + + assertEquals(i, _response.getStatus()); + assertEquals("", _response.getReason()); + super.reset(); + } + } + + public void testErrorStatusWithReason() throws IOException + { + for (int i = 400; i < 600; i++) + { + _rule.setCode("" + i); + _rule.setReason("reason-" + i); + _rule.apply(null, _request, _response); + + assertEquals(i, _response.getStatus()); + assertEquals("reason-" + i, _response.getReason()); + super.reset(); + } + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java new file mode 100644 index 00000000000..9c2e2142600 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java @@ -0,0 +1,141 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.handler.AbstractHandler; + + +public class RewriteHandlerTest extends AbstractRuleTestCase +{ + RewriteHandler _handler; + RewritePatternRule _rule1; + RewritePatternRule _rule2; + RewritePatternRule _rule3; + + + public void setUp() throws Exception + { + _handler=new RewriteHandler(); + _server.setHandler(_handler); + _handler.setHandler(new AbstractHandler(){ + + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setStatus(201); + request.setAttribute("target",target); + request.setAttribute("URI",request.getRequestURI()); + request.setAttribute("info",request.getPathInfo()); + } + + }); + + _rule1 = new RewritePatternRule(); + _rule1.setPattern("/aaa/*"); + _rule1.setReplacement("/bbb"); + _rule2 = new RewritePatternRule(); + _rule2.setPattern("/bbb/*"); + _rule2.setReplacement("/ccc"); + _rule3 = new RewritePatternRule(); + _rule3.setPattern("/ccc/*"); + _rule3.setReplacement("/ddd"); + + _handler.setRules(new Rule[]{_rule1,_rule2,_rule3}); + + super.setUp(); + } + + + public void test() throws Exception + { + _response.setStatus(200); + _request.setHandled(false); + _handler.setOriginalPathAttribute("/before"); + _handler.setRewriteRequestURI(false); + _handler.setRewritePathInfo(false); + _request.setRequestURI("/foo/bar"); + _request.setPathInfo("/foo/bar"); + _handler.handle("/foo/bar",_request,_response); + assertEquals(201,_response.getStatus()); + assertEquals("/foo/bar",_request.getAttribute("target")); + assertEquals("/foo/bar",_request.getAttribute("URI")); + assertEquals("/foo/bar",_request.getAttribute("info")); + assertEquals(null,_request.getAttribute("before")); + + + _response.setStatus(200); + _request.setHandled(false); + _handler.setOriginalPathAttribute(null); + _request.setRequestURI("/aaa/bar"); + _request.setPathInfo("/aaa/bar"); + _handler.handle("/aaa/bar",_request,_response); + assertEquals(201,_response.getStatus()); + assertEquals("/ddd/bar",_request.getAttribute("target")); + assertEquals("/aaa/bar",_request.getAttribute("URI")); + assertEquals("/aaa/bar",_request.getAttribute("info")); + assertEquals(null,_request.getAttribute("before")); + + + _response.setStatus(200); + _request.setHandled(false); + _handler.setOriginalPathAttribute("before"); + _handler.setRewriteRequestURI(true); + _handler.setRewritePathInfo(true); + _request.setRequestURI("/aaa/bar"); + _request.setPathInfo("/aaa/bar"); + _handler.handle("/aaa/bar",_request,_response); + assertEquals(201,_response.getStatus()); + assertEquals("/ddd/bar",_request.getAttribute("target")); + assertEquals("/ddd/bar",_request.getAttribute("URI")); + assertEquals("/ddd/bar",_request.getAttribute("info")); + assertEquals("/aaa/bar",_request.getAttribute("before")); + + + _response.setStatus(200); + _request.setHandled(false); + _rule2.setTerminating(true); + _request.setRequestURI("/aaa/bar"); + _request.setPathInfo("/aaa/bar"); + _handler.handle("/aaa/bar",_request,_response); + assertEquals(201,_response.getStatus()); + assertEquals("/ccc/bar",_request.getAttribute("target")); + assertEquals("/ccc/bar",_request.getAttribute("URI")); + assertEquals("/ccc/bar",_request.getAttribute("info")); + assertEquals("/aaa/bar",_request.getAttribute("before")); + + _response.setStatus(200); + _request.setHandled(false); + _rule2.setHandling(true); + _request.setAttribute("before",null); + _request.setAttribute("target",null); + _request.setAttribute("URI",null); + _request.setAttribute("info",null); + _request.setRequestURI("/aaa/bar"); + _request.setPathInfo("/aaa/bar"); + _handler.handle("/aaa/bar",_request,_response); + assertEquals(200,_response.getStatus()); + assertEquals(null,_request.getAttribute("target")); + assertEquals(null,_request.getAttribute("URI")); + assertEquals(null,_request.getAttribute("info")); + assertEquals("/aaa/bar",_request.getAttribute("before")); + assertTrue(_request.isHandled()); + + + + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java new file mode 100644 index 00000000000..dfecfc02791 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java @@ -0,0 +1,51 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + + +public class RewritePatternRuleTest extends AbstractRuleTestCase +{ + private RewritePatternRule _rule; + + String[][] _tests= + { + {"/foo/bar","/","/replace"}, + {"/foo/bar","/*","/replace/foo/bar"}, + {"/foo/bar","/foo/*","/replace/bar"}, + {"/foo/bar","/foo/bar","/replace"}, + {"/foo/bar.txt","*.txt","/replace"}, + }; + + public void setUp() throws Exception + { + super.setUp(); + _rule = new RewritePatternRule(); + _rule.setReplacement("/replace"); + } + + + public void testRequestUriEnabled() throws IOException + { + for (int i=0;i<_tests.length;i++) + { + _rule.setPattern(_tests[i][1]); + + String result = _rule.matchAndApply(_tests[i][0], _request, _response); + + assertEquals(_tests[i][1],_tests[i][2], result); + } + } + +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java new file mode 100644 index 00000000000..7372b6bbb4b --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java @@ -0,0 +1,47 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +public class RewriteRegexRuleTest extends AbstractRuleTestCase +{ + private RewriteRegexRule _rule; + + String[][] _tests= + { + {"/foo/bar",".*","/replace","/replace"}, + {"/foo/bar","/xxx.*","/replace",null}, + {"/foo/bar","/(.*)/(.*)","/$2/$1/xxx","/bar/foo/xxx"}, + }; + + public void setUp() throws Exception + { + super.setUp(); + _rule=new RewriteRegexRule(); + } + + public void testRequestUriEnabled() throws IOException + { + for (int i=0;i<_tests.length;i++) + { + _rule.setRegex(_tests[i][1]); + _rule.setReplacement(_tests[i][2]); + + String result = _rule.matchAndApply(_tests[i][0], _request, _response); + + assertEquals(_tests[i][1],_tests[i][3], result); + } + } + +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java new file mode 100644 index 00000000000..ee9906adbe5 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java @@ -0,0 +1,184 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.rewrite.handler; + + + +public class VirtualHostRuleContainerTest extends AbstractRuleTestCase +{ + RewriteHandler _handler; + + RewritePatternRule _rule; + RewritePatternRule _fooRule; + VirtualHostRuleContainer _fooContainerRule; + + public void setUp() throws Exception + { + _handler = new RewriteHandler(); + _handler.setRewriteRequestURI(true); + + _rule = new RewritePatternRule(); + _rule.setPattern("/cheese/*"); + _rule.setReplacement("/rule"); + + _fooRule = new RewritePatternRule(); + _fooRule.setPattern("/cheese/bar/*"); + _fooRule.setReplacement("/cheese/fooRule"); + + _fooContainerRule = new VirtualHostRuleContainer(); + _fooContainerRule.setVirtualHosts(new String[] {"foo.com"}); + _fooContainerRule.setRules(new Rule[] { _fooRule }); + + _server.setHandler(_handler); + + super.setUp(); + _request.setRequestURI("/cheese/bar"); + } + + public void testArbitraryHost() throws Exception + { + _request.setServerName("cheese.com"); + _handler.setRules(new Rule[] { _rule, _fooContainerRule }); + handleRequest(); + assertEquals("{_rule, _fooContainerRule, Host: cheese.com}: applied _rule", "/rule/bar", _request.getRequestURI()); + } + + public void testVirtualHost() throws Exception + { + _request.setServerName("foo.com"); + _handler.setRules(new Rule[] { _fooContainerRule }); + handleRequest(); + assertEquals("{_fooContainerRule, Host: foo.com}: applied _fooRule", "/cheese/fooRule", _request.getRequestURI()); + } + + public void testCascadingRules() throws Exception + { + _request.setServerName("foo.com"); + _request.setRequestURI("/cheese/bar"); + + _rule.setTerminating(false); + _fooRule.setTerminating(false); + _fooContainerRule.setTerminating(false); + + _handler.setRules(new Rule[]{_rule, _fooContainerRule}); + handleRequest(); + assertEquals("{_rule, _fooContainerRule}: applied _rule, didn't match _fooRule", "/rule/bar", _request.getRequestURI()); + + _request.setRequestURI("/cheese/bar"); + _handler.setRules(new Rule[] { _fooContainerRule, _rule }); + handleRequest(); + assertEquals("{_fooContainerRule, _rule}: applied _fooRule, _rule","/rule/fooRule", _request.getRequestURI()); + + _request.setRequestURI("/cheese/bar"); + _fooRule.setTerminating(true); + handleRequest(); + assertEquals("{_fooContainerRule, _rule}: (_fooRule is terminating); applied _fooRule, _rule", "/rule/fooRule", _request.getRequestURI()); + + _request.setRequestURI("/cheese/bar"); + _fooRule.setTerminating(false); + _fooContainerRule.setTerminating(true); + handleRequest(); + assertEquals("{_fooContainerRule, _rule}: (_fooContainerRule is terminating); applied _fooRule, terminated before _rule", "/cheese/fooRule", _request.getRequestURI()); + } + + public void testCaseInsensitiveHostname() throws Exception + { + _request.setServerName("Foo.com"); + _fooContainerRule.setVirtualHosts(new String[] {"foo.com"} ); + + _handler.setRules(new Rule[]{ _fooContainerRule }); + handleRequest(); + assertEquals("Foo.com and foo.com are equivalent", "/cheese/fooRule", _request.getRequestURI()); + } + + public void testEmptyVirtualHost() throws Exception + { + _request.setServerName("cheese.com"); + + _handler.setRules(new Rule[] { _fooContainerRule }); + _fooContainerRule.setVirtualHosts(null); + handleRequest(); + assertEquals("{_fooContainerRule: virtual hosts array is null, Host: cheese.com}: apply _fooRule", "/cheese/fooRule", _request.getRequestURI()); + + _request.setRequestURI("/cheese/bar"); + _request.setRequestURI("/cheese/bar"); + _fooContainerRule.setVirtualHosts(new String[] {}); + handleRequest(); + assertEquals("{_fooContainerRule: virtual hosts array is empty, Host: cheese.com}: apply _fooRule", "/cheese/fooRule", _request.getRequestURI()); + + _request.setRequestURI("/cheese/bar"); + _request.setRequestURI("/cheese/bar"); + _fooContainerRule.setVirtualHosts(new String[] {null}); + handleRequest(); + assertEquals("{_fooContainerRule: virtual host is null, Host: cheese.com}: apply _fooRule", "/cheese/fooRule", _request.getRequestURI()); + + } + + public void testMultipleVirtualHosts() throws Exception + { + _request.setServerName("foo.com"); + _handler.setRules(new Rule[] {_fooContainerRule }); + + _fooContainerRule.setVirtualHosts(new String[]{ "cheese.com" }); + handleRequest(); + assertEquals("{_fooContainerRule: vhosts[cheese.com], Host: foo.com}: no effect", "/cheese/bar", _request.getRequestURI()); + + _request.setRequestURI("/cheese/bar"); + _fooContainerRule.addVirtualHost( "foo.com" ); + handleRequest(); + assertEquals("{_fooContainerRule: vhosts[cheese.com, foo.com], Host: foo.com}: apply _fooRule", "/cheese/fooRule", _request.getRequestURI()); + } + + public void testWildcardVirtualHosts() throws Exception + { + checkWildcardHost(true,null,new String[] {"foo.com", ".foo.com", "vhost.foo.com"}); + checkWildcardHost(true,new String[] {null},new String[] {"foo.com", ".foo.com", "vhost.foo.com"}); + + checkWildcardHost(true,new String[] {"foo.com", "*.foo.com"}, new String[] {"foo.com", ".foo.com", "vhost.foo.com"}); + checkWildcardHost(false,new String[] {"foo.com", "*.foo.com"}, new String[] {"badfoo.com", ".badfoo.com", "vhost.badfoo.com"}); + + checkWildcardHost(false,new String[] {"*."}, new String[] {"anything.anything"}); + + checkWildcardHost(true,new String[] {"*.foo.com"}, new String[] {"vhost.foo.com", ".foo.com"}); + checkWildcardHost(false,new String[] {"*.foo.com"}, new String[] {"vhost.www.foo.com", "foo.com", "www.vhost.foo.com"}); + + checkWildcardHost(true,new String[] {"*.sub.foo.com"}, new String[] {"vhost.sub.foo.com", ".sub.foo.com"}); + checkWildcardHost(false,new String[] {"*.sub.foo.com"}, new String[] {".foo.com", "sub.foo.com", "vhost.foo.com"}); + + checkWildcardHost(false,new String[] {"foo.*.com","foo.com.*"}, new String[] {"foo.vhost.com", "foo.com.vhost", "foo.com"}); + } + + private void checkWildcardHost(boolean succeed, String[] ruleHosts, String[] requestHosts) throws Exception + { + _fooContainerRule.setVirtualHosts(ruleHosts); + _handler.setRules(new Rule[] { _fooContainerRule }); + + for(String host: requestHosts) + { + _request.setServerName(host); + _request.setRequestURI("/cheese/bar"); + handleRequest(); + if(succeed) + assertEquals("{_fooContainerRule, Host: "+host+"}: should apply _fooRule", "/cheese/fooRule", _request.getRequestURI()); + else + assertEquals("{_fooContainerRule, Host: "+host+"}: should not apply _fooRule", "/cheese/bar", _request.getRequestURI()); + } + } + + private void handleRequest() throws Exception + { + _server.handle("/cheese/bar", _request, _response); + } +} + diff --git a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml new file mode 100644 index 00000000000..224d48e2342 --- /dev/null +++ b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml @@ -0,0 +1,283 @@ + + + + + + + + + + + + + + + + + + + + + 10 + 50 + 5 + 2 + + + + + + + + + + + + + + + + + + 30000 + 2 + false + 8443 + 10000 + 5000 + + + + + + + + + + + + + + + + + + + + + + + + + + requestedPath + true + + + + + + + /* + /test + + + + + + /session/ + 401 + Setting error code 401 + + + + + + *.jsp + Server + dexter webserver + + + + + + *.jsp + title + driven header purpose + + + + + + /test/dispatch + http://jetty.eclipse.org + + + + + + /test-jaas/$ + /demo + + + + + + X-Forwarded-Scheme + https + https + + + + + + + + + eclipse.com + www.eclipse.com + eclipse.org + www.eclipse.org + + + + + + + /* + CookiePatternRule + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /contexts + 1 + + + + + + + + + + + + + + + + + + + + + + /webapps + false + true + false + /etc/webdefault.xml + + + + + + + + + + + + + + + + Test Realm + /etc/realm.properties + 0 + + + + + + + + + + + + + + + + /yyyy_mm_dd.request.log + yyyy_MM_dd + 90 + true + true + false + GMT + + + + + + + + true + true + true + 1000 + + diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml new file mode 100644 index 00000000000..ebeae8be075 --- /dev/null +++ b/jetty-security/pom.xml @@ -0,0 +1,73 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-security + Jetty :: Security + Jetty security infrastructure + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.security + J2SE-1.5 + org.eclipse.jetty.security;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + org.eclipse.jetty + jetty-server + ${project.version} + + + junit + junit + test + + + diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/Authentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/Authentication.java new file mode 100644 index 00000000000..51dd8ce8154 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/Authentication.java @@ -0,0 +1,66 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import org.eclipse.jetty.server.UserIdentity; + +/** + * Authentication state of a user. + * + * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $ + */ +public interface Authentication +{ + public enum Status + { + SEND_FAILURE(false), SEND_SUCCESS(true), SEND_CONTINUE(false), SUCCESS(true); + boolean _success; + Status(boolean success) {_success=success; } + public boolean isSuccess(){ return _success;} + } + + Status getAuthStatus(); + + String getAuthMethod(); + + UserIdentity getUserIdentity(); + + boolean isSuccess(); + + + public static final Authentication SUCCESS_UNAUTH_RESULTS = new Authentication() + { + public String getAuthMethod() {return null;} + public Status getAuthStatus() {return Authentication.Status.SUCCESS;} + public UserIdentity getUserIdentity() {return UserIdentity.UNAUTHENTICATED_IDENTITY;} + public boolean isSuccess() {return true;} + }; + + public static final Authentication SEND_CONTINUE_RESULTS = new Authentication() + { + public String getAuthMethod() {return null;} + public Status getAuthStatus() {return Authentication.Status.SEND_CONTINUE;} + public UserIdentity getUserIdentity() {return UserIdentity.UNAUTHENTICATED_IDENTITY;} + public boolean isSuccess() {return false;} + }; + + public static final Authentication SEND_FAILURE_RESULTS = new Authentication() + { + public String getAuthMethod() {return null;} + public Status getAuthStatus() {return Authentication.Status.SEND_FAILURE;} + public UserIdentity getUserIdentity() {return UserIdentity.UNAUTHENTICATED_IDENTITY;} + public boolean isSuccess() {return false;} + }; + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java new file mode 100644 index 00000000000..f3e37aa6970 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java @@ -0,0 +1,53 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.eclipse.jetty.server.Server; + +/** + * This is like the JASPI ServerAuthContext but is intended to be easier to use + * and allow lazy auth. + * + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public interface Authenticator +{ + void setConfiguration(Configuration configuration); + String getAuthMethod(); + + Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException; + Authentication.Status secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, Authentication validatedUser) throws ServerAuthException; + + interface Configuration + { + String getAuthMethod(); + String getRealmName(); + boolean isLazy(); + String getInitParameter(String key); + Set getInitParameterNames(); + LoginService getLoginService(); + IdentityService getIdentityService(); + } + + interface Factory + { + Authenticator getAuthenticator(Server server, ServletContext context, Configuration configuration); + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java new file mode 100644 index 00000000000..1135aa75917 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java @@ -0,0 +1,28 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.util.Set; + +/** + * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $ + */ +public interface ConstraintAware +{ + ConstraintMapping[] getConstraintMappings(); + + Set getRoles(); + + void setConstraintMappings(ConstraintMapping[] constraintMappings, Set roles); +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintMapping.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintMapping.java new file mode 100644 index 00000000000..378ff14d575 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintMapping.java @@ -0,0 +1,79 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import org.eclipse.jetty.http.security.Constraint; + +public class ConstraintMapping +{ + String _method; + + String _pathSpec; + + Constraint _constraint; + + /* ------------------------------------------------------------ */ + /** + * @return Returns the constraint. + */ + public Constraint getConstraint() + { + return _constraint; + } + + /* ------------------------------------------------------------ */ + /** + * @param constraint The constraint to set. + */ + public void setConstraint(Constraint constraint) + { + this._constraint = constraint; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the method. + */ + public String getMethod() + { + return _method; + } + + /* ------------------------------------------------------------ */ + /** + * @param method The method to set. + */ + public void setMethod(String method) + { + this._method = method; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the pathSpec. + */ + public String getPathSpec() + { + return _pathSpec; + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec The pathSpec to set. + */ + public void setPathSpec(String pathSpec) + { + this._pathSpec = pathSpec; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java new file mode 100644 index 00000000000..361e45e4415 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java @@ -0,0 +1,316 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.StringMap; + +/* ------------------------------------------------------------ */ +/** + * Handler to enforce SecurityConstraints. This implementation is servlet spec + * 2.4 compliant and precomputes the constraint combinations for runtime + * efficiency. + * + */ +public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware +{ + private ConstraintMapping[] _constraintMappings; + private Set _roles; + private PathMap _constraintMap = new PathMap(); + private boolean _strict = true; + + + /* ------------------------------------------------------------ */ + /** Get the strict mode. + * @return true if the security handler is running in strict mode. + */ + public boolean isStrict() + { + return _strict; + } + + /* ------------------------------------------------------------ */ + /** Set the strict mode of the security handler. + *

    + * When in strict mode (the default), the full servlet specification + * will be implemented. + * If not in strict mode, some additional flexibility in configuration + * is allowed:

      + *
    • All users do not need to have a role defined in the deployment descriptor + *
    • The * role in a constraint applies to ANY role rather than all roles defined in + * the deployment descriptor. + *
    + * + * @param strict the strict to set + */ + public void setStrict(boolean strict) + { + _strict = strict; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the contraintMappings. + */ + public ConstraintMapping[] getConstraintMappings() + { + return _constraintMappings; + } + + /* ------------------------------------------------------------ */ + public Set getRoles() + { + return _roles; + } + + /* ------------------------------------------------------------ */ + /** + * Process the constraints following the combining rules in Servlet 3.0 EA + * spec section 13.7.1 Note that much of the logic is in the RoleInfo class. + * + * @param constraintMappings + * The contraintMappings to set. + * @param roles + */ + public void setConstraintMappings(ConstraintMapping[] constraintMappings, Set roles) + { + if (isStarted()) + throw new IllegalStateException("Started"); + _constraintMappings = constraintMappings; + this._roles = roles; + + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.SecurityHandler#doStart() + */ + @Override + protected void doStart() throws Exception + { + _constraintMap.clear(); + if (_constraintMappings != null) + { + for (ConstraintMapping mapping : _constraintMappings) + { + Map mappings = (Map)_constraintMap.get(mapping.getPathSpec()); + if (mappings == null) + { + mappings = new StringMap(); + _constraintMap.put(mapping.getPathSpec(),mappings); + } + RoleInfo allMethodsRoleInfo = mappings.get(null); + if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden()) + { + continue; + } + String httpMethod = mapping.getMethod(); + RoleInfo roleInfo = mappings.get(httpMethod); + if (roleInfo == null) + { + roleInfo = new RoleInfo(); + mappings.put(httpMethod,roleInfo); + if (allMethodsRoleInfo != null) + { + roleInfo.combine(allMethodsRoleInfo); + } + } + if (roleInfo.isForbidden()) + { + continue; + } + Constraint constraint = mapping.getConstraint(); + boolean forbidden = constraint.isForbidden(); + roleInfo.setForbidden(forbidden); + if (forbidden) + { + if (httpMethod == null) + { + mappings.clear(); + mappings.put(null,roleInfo); + } + } + else + { + UserDataConstraint userDataConstraint = UserDataConstraint.get(constraint.getDataConstraint()); + roleInfo.setUserDataConstraint(userDataConstraint); + + boolean unchecked = !constraint.getAuthenticate(); + roleInfo.setUnchecked(unchecked); + if (!roleInfo.isUnchecked()) + { + if (constraint.isAnyRole()) + { + if (_strict) + { + // * means "all defined roles" + for (String role : _roles) + roleInfo.addRole(role); + } + else + // * means any role + roleInfo.setAnyRole(true); + } + else + { + String[] newRoles = constraint.getRoles(); + for (String role : newRoles) + { + if (_strict &&!_roles.contains(role)) + throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles); + roleInfo.addRole(role); + } + } + } + if (httpMethod == null) + { + for (Map.Entry entry : mappings.entrySet()) + { + if (entry.getKey() != null) + { + RoleInfo specific = entry.getValue(); + specific.combine(roleInfo); + } + } + } + } + } + } + super.doStart(); + } + + protected Object prepareConstraintInfo(String pathInContext, Request request) + { + Map mappings = (Map)_constraintMap.match(pathInContext); + + if (mappings != null) + { + String httpMethod = request.getMethod(); + RoleInfo roleInfo = mappings.get(httpMethod); + if (roleInfo == null) + { + roleInfo = mappings.get(null); + if (roleInfo != null) + { + return roleInfo; + } + } + } + return null; + } + + protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException + { + if (constraintInfo == null) + { + return true; + } + RoleInfo roleInfo = (RoleInfo)constraintInfo; + if (roleInfo.isForbidden()) + { + return false; + } + UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint(); + if (dataConstraint == null || dataConstraint == UserDataConstraint.None) + { + return true; + } + HttpConnection connection = HttpConnection.getCurrentConnection(); + Connector connector = connection.getConnector(); + + if (dataConstraint == UserDataConstraint.Integral) + { + if (connector.isIntegral(request)) + return true; + if (connector.getConfidentialPort() > 0) + { + String url = connector.getIntegralScheme() + "://" + request.getServerName() + ":" + connector.getIntegralPort() + request.getRequestURI(); + if (request.getQueryString() != null) + url += "?" + request.getQueryString(); + response.setContentLength(0); + response.sendRedirect(url); + request.setHandled(true); + } + return false; + } + else if (dataConstraint == UserDataConstraint.Confidential) + { + if (connector.isConfidential(request)) + return true; + + if (connector.getConfidentialPort() > 0) + { + String url = connector.getConfidentialScheme() + "://" + request.getServerName() + ":" + connector.getConfidentialPort() + + request.getRequestURI(); + if (request.getQueryString() != null) + url += "?" + request.getQueryString(); + + response.setContentLength(0); + response.sendRedirect(url); + request.setHandled(true); + } + return false; + } + else + { + throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint); + } + + } + + protected boolean isAuthMandatory(Request base_request, Response base_response, Object constraintInfo) + { + if (constraintInfo == null) + { + return false; + } + return !((RoleInfo)constraintInfo).isUnchecked(); + } + + protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity) + throws IOException + { + if (constraintInfo == null) + { + return true; + } + RoleInfo roleInfo = (RoleInfo)constraintInfo; + + if (roleInfo.isUnchecked()) + { + return true; + } + + if (roleInfo.isAnyRole() && request.getAuthType()!=null) + return true; + + String[] roles = roleInfo.getRoles(); + for (String role : roles) + { + if (userIdentity.isUserInRole(role)) + return true; + } + return false; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java b/jetty-security/src/main/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java new file mode 100644 index 00000000000..9e202beb9af --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java @@ -0,0 +1,31 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $ + */ +public interface CrossContextPsuedoSession +{ + + T fetch(HttpServletRequest request); + + void store(T data, HttpServletResponse response); + + void clear(HttpServletRequest request); + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthentication.java new file mode 100644 index 00000000000..58b41007d50 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthentication.java @@ -0,0 +1,59 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import org.eclipse.jetty.server.UserIdentity; + + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class DefaultAuthentication implements Authentication +{ + private final Authentication.Status _authStatus; + private final String _authMethod; + private final UserIdentity _userIdentity; + + public DefaultAuthentication(Authentication.Status authStatus, String authMethod, UserIdentity userIdentity) + { + _authStatus = authStatus; + _authMethod = authMethod; + _userIdentity=userIdentity; + } + + public String getAuthMethod() + { + return _authMethod; + } + + public Authentication.Status getAuthStatus() + { + return _authStatus; + } + + public UserIdentity getUserIdentity() + { + return _userIdentity; + } + + public boolean isSuccess() + { + return _authStatus.isSuccess(); + } + + public String toString() + { + return "{Auth,"+_authMethod+","+_authStatus+","+","+_userIdentity+"}"; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java new file mode 100644 index 00000000000..75fa6ac8c81 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java @@ -0,0 +1,88 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import javax.servlet.ServletContext; + +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.security.Authenticator.Configuration; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.security.authentication.ClientCertAuthenticator; +import org.eclipse.jetty.security.authentication.DigestAuthenticator; +import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.security.authentication.LazyAuthenticator; +import org.eclipse.jetty.security.authentication.SessionCachingAuthenticator; +import org.eclipse.jetty.server.Server; + +/* ------------------------------------------------------------ */ +/** + * The Default Authenticator Factory. + * Uses the {@link Configuration#getAuthMethod()} to select an {@link Authenticator} from:
      + *
    • {@link BasicAuthenticator}
    • + *
    • {@link DigestAuthenticator}
    • + *
    • {@link FormAuthenticator}
    • + *
    • {@link ClientCertAuthenticator}
    • + *
    + * If {@link Configuration#isLazy()} is true, the Authenticator is wrapped with a {@link LazyAuthenticator} + * instance. The FormAuthenticator is always wrapped in a {@link SessionCachingAuthenticator}. + *

    + * If a {@link LoginService} has not been set on this factory, then + * the service is selected by searching the {@link Server#getBeans(Class)} results for + * a service that matches the realm name, else the first LoginService found is used. + * + */ +public class DefaultAuthenticatorFactory implements Authenticator.Factory +{ + LoginService _loginService; + + public Authenticator getAuthenticator(Server server, ServletContext context, Configuration configuration) + { + String auth=configuration.getAuthMethod(); + Authenticator authenticator=null; + + if (auth==null || Constraint.__BASIC_AUTH.equalsIgnoreCase(auth)) + authenticator=new BasicAuthenticator(); + else if (Constraint.__DIGEST_AUTH.equalsIgnoreCase(auth)) + authenticator=new DigestAuthenticator(); + else if (Constraint.__FORM_AUTH.equalsIgnoreCase(auth)) + authenticator=new SessionCachingAuthenticator(new FormAuthenticator()); + if (Constraint.__CERT_AUTH.equalsIgnoreCase(auth)||Constraint.__CERT_AUTH2.equalsIgnoreCase(auth)) + authenticator=new ClientCertAuthenticator(); + + if (configuration.isLazy() && authenticator!=null) + authenticator=new LazyAuthenticator(authenticator); + + return authenticator; + } + + + /* ------------------------------------------------------------ */ + /** + * @return the loginService + */ + public LoginService getLoginService() + { + return _loginService; + } + + /* ------------------------------------------------------------ */ + /** + * @param loginService the loginService to set + */ + public void setLoginService(LoginService loginService) + { + _loginService = loginService; + } + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java new file mode 100644 index 00000000000..ada4b36d4d5 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java @@ -0,0 +1,119 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.security.Principal; +import java.util.Map; + +import javax.security.auth.Subject; + +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.UserIdentity.Scope; + + +/* ------------------------------------------------------------ */ +/** + * Default Identity Service implementation. + * This service handles only role reference maps passed in an + * associated {@link UserIdentity.Scope}. If there are roles + * refs present, then associate will wrap the UserIdentity with one + * that uses the role references in the {@link UserIdentity#isUserInRole(String)} + * implementation. All other operations are effectively noops. + * + */ +public class DefaultIdentityService implements IdentityService +{ + public DefaultIdentityService() + { + } + + /* ------------------------------------------------------------ */ + /** + * If there are roles refs present in the scope, then wrap the UserIdentity + * with one that uses the role references in the {@link UserIdentity#isUserInRole(String)} + */ + public UserIdentity associate(UserIdentity user, Scope scope) + { + Map roleRefMap=scope.getRoleRefMap(); + if (roleRefMap!=null && roleRefMap.size()>0) + return new RoleRefUserIdentity(user,roleRefMap); + return user; + } + + public void disassociate(UserIdentity scoped) + { + } + + public RoleRunAsToken associateRunAs(RunAsToken token) + { + return null; + } + + public void disassociateRunAs(RoleRunAsToken lastToken) + { + } + + public RunAsToken newRunAsToken(String runAsName) + { + return new RoleRunAsToken(runAsName); + } + + public UserIdentity newSystemUserIdentity() + { + return null; + } + + public UserIdentity newUserIdentity(final Subject subject, final Principal userPrincipal, final String[] roles) + { + return new DefaultUserIdentity(subject,userPrincipal,roles); + } + + /* ------------------------------------------------------------ */ + /** + * Wrapper UserIdentity used to apply RoleRef map. + * + */ + public static class RoleRefUserIdentity implements UserIdentity + { + final private UserIdentity _delegate; + final private Map _roleRefMap; + + public RoleRefUserIdentity(final UserIdentity user, final Map roleRefMap) + { + _delegate=user; + _roleRefMap=roleRefMap; + } + + public String[] getRoles() + { + return _delegate.getRoles(); + } + + public Subject getSubject() + { + return _delegate.getSubject(); + } + + public Principal getUserPrincipal() + { + return _delegate.getUserPrincipal(); + } + + public boolean isUserInRole(String role) + { + String link=_roleRefMap.get(role); + return _delegate.isUserInRole(link==null?role:link); + } + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java new file mode 100644 index 00000000000..07580f00e33 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java @@ -0,0 +1,71 @@ +// ======================================================================== +// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.security.Principal; + +import javax.security.auth.Subject; + +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.security.Authentication.Status; +import org.eclipse.jetty.server.UserIdentity; + + +/* ------------------------------------------------------------ */ +/** + * The default implementation of UserIdentity. + * + */ +public class DefaultUserIdentity implements UserIdentity +{ + /* Cache successful authentications for BASIC and DIGEST to avoid creation on every request */ + public final Authentication SUCCESSFUL_BASIC = new DefaultAuthentication(Status.SUCCESS,Constraint.__BASIC_AUTH,this); + public final Authentication SUCCESSFUL_DIGEST = new DefaultAuthentication(Status.SUCCESS,Constraint.__BASIC_AUTH,this); + + private final Subject _subject; + private final Principal _userPrincipal; + private final String[] _roles; + + public DefaultUserIdentity(Subject subject, Principal userPrincipal, String[] roles) + { + _subject=subject; + _userPrincipal=userPrincipal; + _roles=roles; + } + + public String[] getRoles() + { + return _roles; + } + + public Subject getSubject() + { + return _subject; + } + + public Principal getUserPrincipal() + { + return _userPrincipal; + } + + public boolean isUserInRole(String role) + { + for (String r :_roles) + if (r.equals(role)) + return true; + return false; + } + + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java new file mode 100644 index 00000000000..06c589b03ad --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java @@ -0,0 +1,90 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $ + */ +public class HashCrossContextPsuedoSession implements CrossContextPsuedoSession +{ + private final String _cookieName; + + private final String _cookiePath; + + private final Random _random = new SecureRandom(); + + private final Map _data = new HashMap(); + + public HashCrossContextPsuedoSession(String cookieName, String cookiePath) + { + this._cookieName = cookieName; + this._cookiePath = cookiePath == null ? "/" : cookiePath; + } + + public T fetch(HttpServletRequest request) + { + for (Cookie cookie : request.getCookies()) + { + if (_cookieName.equals(cookie.getName())) + { + String key = cookie.getValue(); + return _data.get(key); + } + } + return null; + } + + public void store(T datum, HttpServletResponse response) + { + String key; + + synchronized (_data) + { + // Create new ID + while (true) + { + key = Long.toString(Math.abs(_random.nextLong()), 30 + (int) (System.currentTimeMillis() % 7)); + if (!_data.containsKey(key)) break; + } + + _data.put(key, datum); + } + + Cookie cookie = new Cookie(_cookieName, key); + cookie.setPath(_cookiePath); + response.addCookie(cookie); + } + + public void clear(HttpServletRequest request) + { + for (Cookie cookie : request.getCookies()) + { + if (_cookieName.equals(cookie.getName())) + { + String key = cookie.getValue(); + _data.remove(key); + break; + } + } + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java new file mode 100644 index 00000000000..f82590390b5 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java @@ -0,0 +1,247 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.Scanner; +import org.eclipse.jetty.util.Scanner.BulkListener; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** + * Properties User Realm. + * + * An implementation of UserRealm that stores users and roles in-memory in + * HashMaps. + *

    + * Typically these maps are populated by calling the load() method or passing a + * properties resource to the constructor. The format of the properties file is: + * + *

    + *  username: password [,rolename ...]
    + * 
    + * + * Passwords may be clear text, obfuscated or checksummed. The class + * com.eclipse.Util.Password should be used to generate obfuscated passwords or + * password checksums. + * + * If DIGEST Authentication is used, the password must be in a recoverable + * format, either plain text or OBF:. + * + * @see org.eclipse.jetty.security.Password + * + */ +public class HashLoginService extends MappedLoginService +{ + private String _config; + private Resource _configResource; + private Scanner _scanner; + private int _refreshInterval = 0;// default is not to reload + + /* ------------------------------------------------------------ */ + public HashLoginService() + { + } + + /* ------------------------------------------------------------ */ + public HashLoginService(String name) + { + setName(name); + } + + /* ------------------------------------------------------------ */ + public HashLoginService(String name, String config) + { + setName(name); + setConfig(config); + } + + /* ------------------------------------------------------------ */ + public String getConfig() + { + return _config; + } + + /* ------------------------------------------------------------ */ + public void getConfig(String config) + { + _config=config; + } + + /* ------------------------------------------------------------ */ + public Resource getConfigResource() + { + return _configResource; + } + + /* ------------------------------------------------------------ */ + /** + * Load realm users from properties file. The property file maps usernames + * to password specs followed by an optional comma separated list of role + * names. + * + * @param config Filename or url of user properties file. + * @exception java.io.IOException if user properties file could not be + * loaded + */ + public void setConfig(String config) + { + _config = config; + } + + /* ------------------------------------------------------------ */ + public void setRefreshInterval(int msec) + { + _refreshInterval = msec; + } + + /* ------------------------------------------------------------ */ + public int getRefreshInterval() + { + return _refreshInterval; + } + + /* ------------------------------------------------------------ */ + @Override + protected UserIdentity loadUser(String username) + { + // TODO Auto-generated method stub + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public void loadUsers() throws IOException + { + if (_config==null) + return; + _configResource = Resource.newResource(_config); + + if (Log.isDebugEnabled()) Log.debug("Load " + this + " from " + _config); + Properties properties = new Properties(); + properties.load(_configResource.getInputStream()); + Set known = new HashSet(); + + for (Map.Entry entry : properties.entrySet()) + { + String username = ((String) entry.getKey()).trim(); + String credentials = ((String) entry.getValue()).trim(); + String roles = null; + int c = credentials.indexOf(','); + if (c > 0) + { + roles = credentials.substring(c + 1).trim(); + credentials = credentials.substring(0, c).trim(); + } + + if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0) + { + String[] roleArray = UserIdentity.NO_ROLES; + if (roles != null && roles.length() > 0) + roleArray = roles.split(","); + known.add(username); + putUser(username,Credential.getCredential(credentials),roleArray); + } + } + + Iterator users = _users.keySet().iterator(); + while(users.hasNext()) + { + String user=users.next(); + if (!known.contains(user)) + users.remove(); + } + } + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + super.doStart(); + + if (getRefreshInterval() > 0) + { + _scanner = new Scanner(); + _scanner.setScanInterval(getRefreshInterval()); + List dirList = new ArrayList(1); + dirList.add(_configResource.getFile()); + _scanner.setScanDirs(dirList); + _scanner.setFilenameFilter(new FilenameFilter() + { + public boolean accept(File dir, String name) + { + File f = new File(dir, name); + try + { + if (f.compareTo(_configResource.getFile()) == 0) return true; + } + catch (IOException e) + { + return false; + } + + return false; + } + + }); + _scanner.addListener(new BulkListener() + { + public void filesChanged(List filenames) throws Exception + { + if (filenames == null) return; + if (filenames.isEmpty()) return; + if (filenames.size() == 1 && filenames.get(0).equals(_config)) loadUsers(); + } + + public String toString() + { + return "HashLoginService$Scanner"; + } + + }); + _scanner.setReportExistingFilesOnStartup(false); + _scanner.setRecursive(false); + _scanner.start(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() + */ + protected void doStop() throws Exception + { + super.doStop(); + if (_scanner != null) _scanner.stop(); + _scanner = null; + } + + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java new file mode 100644 index 00000000000..84e7071b8da --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java @@ -0,0 +1,99 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.security.Principal; + +import javax.security.auth.Subject; + +import org.eclipse.jetty.server.UserIdentity; + +/* ------------------------------------------------------------ */ +/** + * Associates UserIdentities from with threads and UserIdentity.Contexts. + * + */ +public interface IdentityService +{ + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** A scoped UserIdentity. + * + * An interface used to ob + * + */ + interface Scoped + { + UserIdentity getScopedUserIdentity(); + } + + /* ------------------------------------------------------------ */ + /** + * Associate the {@link UserIdentity} and {@link UserIdentity.Scope} + * with the current thread. + * @param user The current user. + * @param context The new scope. + * @return A scoped {@link UserIdentity}. + */ + SCOPED associate(UserIdentity user, UserIdentity.Scope context); + + /* ------------------------------------------------------------ */ + /** + * Disassociate the current UserIdentity and reinstate the + * previousUser identity. + * TODO this might not be necessary. Both existing implementations are no-ops + * @param scoped SCOPED returned from previous associate call + */ + void disassociate(SCOPED scoped); + + /* ------------------------------------------------------------ */ + /** + * Associate a runas Token with the current thread. + * @param token The runAsToken to associate. + * @return The previous runAsToken or null. + */ + RUNAS associateRunAs(RunAsToken token); + + /* ------------------------------------------------------------ */ + /** + * Disassociate the current runAsToken from the thread + * and reassociate the previous token. + * @param token RUNAS returned from previous associateRunAs call + */ + void disassociateRunAs(RUNAS token); + + /* ------------------------------------------------------------ */ + /** + * Create a new UserIdentity for use with this identity service. + * The UserIdentity should be immutable and able to be cached. + * + * @param subject Subject to include in UserIdentity + * @param userPrincipal Principal to include in UserIdentity. This will be returned from getUserPrincipal calls + * @param roles set of roles to include in UserIdentity. + * @return A new immutable UserIdententity + */ + UserIdentity newUserIdentity(Subject subject, Principal userPrincipal, String[] roles); + + /* ------------------------------------------------------------ */ + /** + * Create a new RunAsToken from a runAsName (normally a role). + * @param runAsName Normally a role name + * @return A new immutable RunAsToken + */ + RunAsToken newRunAsToken(String runAsName); + + UserIdentity newSystemUserIdentity(); +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java new file mode 100644 index 00000000000..a994c45b5cb --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java @@ -0,0 +1,263 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** + * HashMapped User Realm with JDBC as data source. JDBCLoginService extends + * HashULoginService and adds a method to fetch user information from database. + * The login() method checks the inherited Map for the user. If the user is not + * found, it will fetch details from the database and populate the inherited + * Map. It then calls the superclass login() method to perform the actual + * authentication. Periodically (controlled by configuration parameter), + * internal hashes are cleared. Caching can be disabled by setting cache refresh + * interval to zero. Uses one database connection that is initialized at + * startup. Reconnect on failures. authenticate() is 'synchronized'. + * + * An example properties file for configuration is in + * $JETTY_HOME/etc/jdbcRealm.properties + * + * @version $Id: JDBCLoginService.java 4792 2009-03-18 21:55:52Z gregw $ + * + * + * + * + */ + +public class JDBCLoginService extends MappedLoginService +{ + private String _config; + private String _jdbcDriver; + private String _url; + private String _userName; + private String _password; + private String _userTableKey; + private String _userTablePasswordField; + private String _roleTableRoleField; + private int _cacheTime; + private long _lastHashPurge; + private Connection _con; + private String _userSql; + private String _roleSql; + + + /* ------------------------------------------------------------ */ + public JDBCLoginService() + throws IOException + { + } + + /* ------------------------------------------------------------ */ + public JDBCLoginService(String name) + throws IOException + { + setName(name); + } + + /* ------------------------------------------------------------ */ + public JDBCLoginService(String name, String config) + throws IOException + { + setName(name); + setConfig(config); + } + + /* ------------------------------------------------------------ */ + public JDBCLoginService(String name, IdentityService identityService, String config) + throws IOException + { + setName(name); + setIdentityService(identityService); + setConfig(config); + } + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.MappedLoginService#doStart() + */ + @Override + protected void doStart() throws Exception + { + Properties properties = new Properties(); + Resource resource = Resource.newResource(_config); + properties.load(resource.getInputStream()); + + _jdbcDriver = properties.getProperty("jdbcdriver"); + _url = properties.getProperty("url"); + _userName = properties.getProperty("username"); + _password = properties.getProperty("password"); + String _userTable = properties.getProperty("usertable"); + _userTableKey = properties.getProperty("usertablekey"); + String _userTableUserField = properties.getProperty("usertableuserfield"); + _userTablePasswordField = properties.getProperty("usertablepasswordfield"); + String _roleTable = properties.getProperty("roletable"); + String _roleTableKey = properties.getProperty("roletablekey"); + _roleTableRoleField = properties.getProperty("roletablerolefield"); + String _userRoleTable = properties.getProperty("userroletable"); + String _userRoleTableUserKey = properties.getProperty("userroletableuserkey"); + String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey"); + _cacheTime = new Integer(properties.getProperty("cachetime")); + + if (_jdbcDriver == null || _jdbcDriver.equals("") + || _url == null + || _url.equals("") + || _userName == null + || _userName.equals("") + || _password == null + || _cacheTime < 0) + { + if (Log.isDebugEnabled()) Log.debug("UserRealm " + getName() + " has not been properly configured"); + } + _cacheTime *= 1000; + _lastHashPurge = 0; + _userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?"; + _roleSql = "select r." + _roleTableRoleField + + " from " + + _roleTable + + " r, " + + _userRoleTable + + " u where u." + + _userRoleTableUserKey + + " = ?" + + " and r." + + _roleTableKey + + " = u." + + _userRoleTableRoleKey; + + Loader.loadClass(this.getClass(), _jdbcDriver).newInstance(); + connectDatabase(); + super.doStart(); + } + + + /* ------------------------------------------------------------ */ + public String getConfig() + { + return _config; + } + + /* ------------------------------------------------------------ */ + /** + * Load JDBC connection configuration from properties file. + * + * @param config Filename or url of user properties file. + * @exception java.io.IOException + */ + public void setConfig(String config) + { + if (isRunning()) + throw new IllegalStateException("Running"); + _config=config; + } + + /* ------------------------------------------------------------ */ + /** + * (re)Connect to database with parameters setup by loadConfig() + */ + public void connectDatabase() + { + try + { + Class.forName(_jdbcDriver); + _con = DriverManager.getConnection(_url, _userName, _password); + } + catch (SQLException e) + { + Log.warn("UserRealm " + getName() + " could not connect to database; will try later", e); + } + catch (ClassNotFoundException e) + { + Log.warn("UserRealm " + getName() + " could not connect to database; will try later", e); + } + } + + /* ------------------------------------------------------------ */ + @Override + public UserIdentity login(String username, Object credentials) + { + long now = System.currentTimeMillis(); + if (now - _lastHashPurge > _cacheTime || _cacheTime == 0) + { + _users.clear(); + _lastHashPurge = now; + } + + return super.login(username,credentials); + } + + + /* ------------------------------------------------------------ */ + @Override + protected void loadUsers() + { + } + + /* ------------------------------------------------------------ */ + @Override + protected UserIdentity loadUser(String username) + { + try + { + if (null == _con) + connectDatabase(); + + if (null == _con) + throw new SQLException("Can't connect to database"); + + PreparedStatement stat = _con.prepareStatement(_userSql); + stat.setObject(1, username); + ResultSet rs = stat.executeQuery(); + + if (rs.next()) + { + int key = rs.getInt(_userTableKey); + String credentials = rs.getString(_userTablePasswordField); + stat.close(); + + stat = _con.prepareStatement(_roleSql); + stat.setInt(1, key); + rs = stat.executeQuery(); + List roles = new ArrayList(); + while (rs.next()) + roles.add(rs.getString(_roleTableRoleField)); + + stat.close(); + return putUser(username, new Password(credentials),roles.toArray(new String[roles.size()])); + } + } + catch (SQLException e) + { + Log.warn("UserRealm " + getName() + " could not load user information from database", e); + connectDatabase(); + } + return null; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/LazyAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/LazyAuthentication.java new file mode 100644 index 00000000000..99a8df7a3f4 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/LazyAuthentication.java @@ -0,0 +1,85 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import javax.security.auth.Subject; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.eclipse.jetty.server.UserIdentity; + + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class LazyAuthentication implements Authentication +{ + private static final Subject unauthenticatedSubject = new Subject(); + + private final Authenticator _serverAuthentication; + private final ServletRequest _request; + private final ServletResponse _response; + + private Authentication _delegate; + + public LazyAuthentication(Authenticator serverAuthentication, ServletRequest request, ServletResponse response) + { + if (serverAuthentication == null) throw new NullPointerException("No ServerAuthentication"); + this._serverAuthentication = serverAuthentication; + this._request=request; + this._response=response; + } + + private Authentication getDelegate() + { + if (_delegate == null) + { + try + { + _delegate = _serverAuthentication.validateRequest(_request, _response, false); + } + catch (ServerAuthException e) + { + _delegate = DefaultAuthentication.SEND_FAILURE_RESULTS; + } + } + return _delegate; + } + + public Authentication.Status getAuthStatus() + { + return getDelegate().getAuthStatus(); + } + + public boolean isSuccess() + { + return getDelegate().isSuccess(); + } + + // for cleaning in secureResponse + public UserIdentity getUserIdentity() + { + return _delegate == null ? UserIdentity.UNAUTHENTICATED_IDENTITY: _delegate.getUserIdentity(); + } + + public String getAuthMethod() + { + return getDelegate().getAuthMethod(); + } + + public String toString() + { + return "{Lazy,"+_delegate+"}"; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/LoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/LoginService.java new file mode 100644 index 00000000000..2b2cc6d44a2 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/LoginService.java @@ -0,0 +1,28 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import org.eclipse.jetty.server.UserIdentity; + +/** + * @version $Rev: 4734 $ $Date: 2009-03-07 18:46:18 +0100 (Sat, 07 Mar 2009) $ + */ +public interface LoginService +{ + String getName(); + UserIdentity login(String username,Object credentials); + + IdentityService getIdentityService(); + void setIdentityService(IdentityService service); +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java new file mode 100644 index 00000000000..d4e88b3814d --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java @@ -0,0 +1,292 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.security; + +import java.io.IOException; +import java.security.Principal; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.security.auth.Subject; + +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.component.AbstractLifeCycle; + + + +/* ------------------------------------------------------------ */ +/** + * A login service that keeps UserIdentities in a concurrent map + * either as the source or a cache of the users. + * + */ +public abstract class MappedLoginService extends AbstractLifeCycle implements LoginService +{ + protected IdentityService _identityService=new DefaultIdentityService(); + protected String _name; + protected final ConcurrentMap _users=new ConcurrentHashMap(); + + /* ------------------------------------------------------------ */ + protected MappedLoginService() + { + } + + /* ------------------------------------------------------------ */ + /** Get the name. + * @return the name + */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** Get the identityService. + * @return the identityService + */ + public IdentityService getIdentityService() + { + return _identityService; + } + + /* ------------------------------------------------------------ */ + /** Get the users. + * @return the users + */ + public ConcurrentMap getUsers() + { + return _users; + } + + /* ------------------------------------------------------------ */ + /** Set the identityService. + * @param identityService the identityService to set + */ + public void setIdentityService(IdentityService identityService) + { + if (isRunning()) + throw new IllegalStateException("Running"); + _identityService = identityService; + } + + /* ------------------------------------------------------------ */ + /** Set the name. + * @param name the name to set + */ + public void setName(String name) + { + if (isRunning()) + throw new IllegalStateException("Running"); + _name = name; + } + + /* ------------------------------------------------------------ */ + /** Set the users. + * @param users the users to set + */ + public void setUsers(Map users) + { + if (isRunning()) + throw new IllegalStateException("Running"); + _users.clear(); + _users.putAll(users); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + loadUsers(); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + protected void doStop() throws Exception + { + super.doStop(); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return this.getClass().getSimpleName()+"["+_name+"]"; + } + + /* ------------------------------------------------------------ */ + /** Put user into realm. + * Called by implementations to put the user data loaded from + * file/db etc into the user structure. + * @param userName User name + * @param info a UserIdentity instance, or a String password or Credential instance + * @return User instance + */ + protected synchronized UserIdentity putUser(String userName, Object info) + { + final UserIdentity identity; + if (info instanceof UserIdentity) + identity=(UserIdentity)info; + else + { + Credential credential = (info instanceof Credential)?(Credential)info:Credential.getCredential(info.toString()); + + Principal userPrincipal = new KnownUser(userName,credential); + Subject subject = new Subject(); + subject.getPrincipals().add(userPrincipal); + subject.getPrivateCredentials().add(credential); + subject.setReadOnly(); + identity=_identityService.newUserIdentity(subject,userPrincipal,UserIdentity.NO_ROLES); + } + + _users.put(userName,identity); + return identity; + } + + /* ------------------------------------------------------------ */ + /** Put user into realm. + * @param userName + * @param credential + * @param roles + * @return UserIdentity + */ + protected synchronized UserIdentity putUser(String userName, Credential credential, String[] roles) + { + Principal userPrincipal = new KnownUser(userName,credential); + Subject subject = new Subject(); + subject.getPrincipals().add(userPrincipal); + subject.getPrivateCredentials().add(credential); + + if (roles!=null) + for (String role : roles) + subject.getPrincipals().add(new RolePrincipal(role)); + + subject.setReadOnly(); + UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles); + _users.put(userName,identity); + return identity; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.LoginService#login(java.lang.String, java.lang.Object) + */ + public UserIdentity login(String username, Object credentials) + { + UserIdentity user = _users.get(username); + + if (user==null) + user = loadUser(username); + + if (user!=null) + { + UserPrincipal principal = (UserPrincipal)user.getUserPrincipal(); + if (principal.authenticate(credentials)) + return user; + } + return null; + } + + /* ------------------------------------------------------------ */ + protected abstract UserIdentity loadUser(String username); + + /* ------------------------------------------------------------ */ + protected abstract void loadUsers() throws IOException; + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public interface UserPrincipal extends Principal + { + boolean authenticate(Object credentials); + public boolean isAuthenticated(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class RolePrincipal implements Principal + { + private final String _name; + public RolePrincipal(String name) + { + _name=name; + } + public String getName() + { + return _name; + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static class Anonymous implements UserPrincipal + { + public boolean isAuthenticated() + { + return false; + } + + public String getName() + { + return "Anonymous"; + } + + public boolean authenticate(Object credentials) + { + return false; + } + + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static class KnownUser implements UserPrincipal + { + private final String _name; + private final Credential _credential; + + /* -------------------------------------------------------- */ + public KnownUser(String name,Credential credential) + { + _name=name; + _credential=credential; + } + + /* -------------------------------------------------------- */ + public boolean authenticate(Object credentials) + { + return _credential!=null && _credential.check(credentials); + } + + /* ------------------------------------------------------------ */ + public String getName() + { + return _name; + } + + /* -------------------------------------------------------- */ + public boolean isAuthenticated() + { + return true; + } + } +} + diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java new file mode 100644 index 00000000000..d4d687f51c6 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java @@ -0,0 +1,135 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.util.Arrays; + +import org.eclipse.jetty.util.LazyList; + +/** + * + * Badly named class that holds the role and user data constraint info for a + * path/http method combination, extracted and combined from security + * constraints. + * + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class RoleInfo +{ + private final static String[] NO_ROLES={}; + private boolean _isAnyRole; + private boolean _unchecked; + private boolean _forbidden; + private UserDataConstraint _userDataConstraint; + + private String[] _roles = NO_ROLES; + + public boolean isUnchecked() + { + return _unchecked; + } + + public void setUnchecked(boolean unchecked) + { + this._unchecked = unchecked; + if (unchecked) + { + _forbidden=false; + _roles=NO_ROLES; + _isAnyRole=false; + } + } + + public boolean isForbidden() + { + return _forbidden; + } + + public void setForbidden(boolean forbidden) + { + this._forbidden = forbidden; + if (forbidden) + { + _unchecked = false; + _userDataConstraint = null; + _isAnyRole=false; + _roles=NO_ROLES; + } + } + + public boolean isAnyRole() + { + return _isAnyRole; + } + + public void setAnyRole(boolean anyRole) + { + this._isAnyRole=anyRole; + if (anyRole) + { + _unchecked = false; + _roles=NO_ROLES; + } + } + + public UserDataConstraint getUserDataConstraint() + { + return _userDataConstraint; + } + + public void setUserDataConstraint(UserDataConstraint userDataConstraint) + { + if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint"); + if (this._userDataConstraint == null) + { + this._userDataConstraint = userDataConstraint; + } + else + { + this._userDataConstraint = this._userDataConstraint.combine(userDataConstraint); + } + } + + public String[] getRoles() + { + return _roles; + } + + public void addRole(String role) + { + _roles=(String[])LazyList.addToArray(_roles,role,String.class); + } + + public void combine(RoleInfo other) + { + if (other._forbidden) + setForbidden(true); + else if (other._unchecked) + setUnchecked(true); + else if (other._isAnyRole) + setAnyRole(true); + else if (!_isAnyRole) + { + for (String r : other._roles) + _roles=(String[])LazyList.addToArray(_roles,r,String.class); + } + + setUserDataConstraint(other._userDataConstraint); + } + + public String toString() + { + return "{RoleInfo"+(_forbidden?",F":"")+(_unchecked?",U":"")+(_isAnyRole?",*":Arrays.asList(_roles).toString())+"}"; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/RoleRunAsToken.java b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleRunAsToken.java new file mode 100644 index 00000000000..02ee2d20e05 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleRunAsToken.java @@ -0,0 +1,39 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + + + +/** + * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $ + */ +public class RoleRunAsToken implements RunAsToken +{ + private final String _runAsRole; + + public RoleRunAsToken(String runAsRole) + { + this._runAsRole = runAsRole; + } + + public String getRunAsRole() + { + return _runAsRole; + } + + public String toString() + { + return "RoleRunAsToken("+_runAsRole+")"; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/RunAsToken.java b/jetty-security/src/main/java/org/eclipse/jetty/security/RunAsToken.java new file mode 100644 index 00000000000..7bf84ccd871 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/RunAsToken.java @@ -0,0 +1,22 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +/** + * marker interface for run-as-role tokens + * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $ + */ +public interface RunAsToken +{ +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java new file mode 100644 index 00000000000..f550d235631 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -0,0 +1,524 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.io.IOException; +import java.security.Principal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; + +/** + * Abstract SecurityHandler. + * Select and apply an {@link Authenticator} to a request. + *

    + * The Authenticator may either be directly set on the handler + * or will be create during {@link #start()} with a call to + * either the default or set AuthenticatorFactory. + */ +public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.Configuration +{ + /* ------------------------------------------------------------ */ + private boolean _checkWelcomeFiles = false; + private Authenticator _authenticator; + private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory(); + private boolean _isLazy=true; + private String _realmName; + private String _authMethod; + private final Map _initParameters=new HashMap(); + private LoginService _loginService; + private boolean _loginServiceShared; + private IdentityService _identityService; + + /* ------------------------------------------------------------ */ + protected SecurityHandler() + { + } + + /* ------------------------------------------------------------ */ + /** Get the identityService. + * @return the identityService + */ + public IdentityService getIdentityService() + { + return _identityService; + } + + /* ------------------------------------------------------------ */ + /** Set the identityService. + * @param identityService the identityService to set + */ + public void setIdentityService(IdentityService identityService) + { + if (isStarted()) + throw new IllegalStateException("Started"); + _identityService = identityService; + } + + /* ------------------------------------------------------------ */ + /** Get the loginService. + * @return the loginService + */ + public LoginService getLoginService() + { + return _loginService; + } + + /* ------------------------------------------------------------ */ + /** Set the loginService. + * @param loginService the loginService to set + */ + public void setLoginService(LoginService loginService) + { + if (isStarted()) + throw new IllegalStateException("Started"); + _loginService = loginService; + _loginServiceShared=false; + } + + + /* ------------------------------------------------------------ */ + public Authenticator getAuthenticator() + { + return _authenticator; + } + + /* ------------------------------------------------------------ */ + /** Set the authenticator. + * @param authenticator + * @throws IllegalStateException if the SecurityHandler is running + */ + public void setAuthenticator(Authenticator authenticator) + { + if (isStarted()) + throw new IllegalStateException("Started"); + _authenticator = authenticator; + } + + /* ------------------------------------------------------------ */ + /** + * @return the authenticatorFactory + */ + public Authenticator.Factory getAuthenticatorFactory() + { + return _authenticatorFactory; + } + + /* ------------------------------------------------------------ */ + /** + * @param authenticatorFactory the authenticatorFactory to set + * @throws IllegalStateException if the SecurityHandler is running + */ + public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory) + { + if (isRunning()) + throw new IllegalStateException("running"); + _authenticatorFactory = authenticatorFactory; + } + + /* ------------------------------------------------------------ */ + /** + * @return the isLazy + */ + public boolean isLazy() + { + return _isLazy; + } + + /* ------------------------------------------------------------ */ + /** + * @param isLazy the isLazy to set + * @throws IllegalStateException if the SecurityHandler is running + */ + public void setLazy(boolean isLazy) + { + if (isRunning()) + throw new IllegalStateException("running"); + _isLazy = isLazy; + } + + /* ------------------------------------------------------------ */ + /** + * @return the realmName + */ + public String getRealmName() + { + return _realmName; + } + + /* ------------------------------------------------------------ */ + /** + * @param realmName the realmName to set + * @throws IllegalStateException if the SecurityHandler is running + */ + public void setRealmName(String realmName) + { + if (isRunning()) + throw new IllegalStateException("running"); + _realmName = realmName; + } + + /* ------------------------------------------------------------ */ + /** + * @return the authMethod + */ + public String getAuthMethod() + { + return _authMethod; + } + + /* ------------------------------------------------------------ */ + /** + * @param authMethod the authMethod to set + * @throws IllegalStateException if the SecurityHandler is running + */ + public void setAuthMethod(String authMethod) + { + if (isRunning()) + throw new IllegalStateException("running"); + _authMethod = authMethod; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if forwards to welcome files are authenticated + */ + public boolean isCheckWelcomeFiles() + { + return _checkWelcomeFiles; + } + + /* ------------------------------------------------------------ */ + /** + * @param authenticateWelcomeFiles True if forwards to welcome files are + * authenticated + * @throws IllegalStateException if the SecurityHandler is running + */ + public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles) + { + if (isRunning()) + throw new IllegalStateException("running"); + _checkWelcomeFiles = authenticateWelcomeFiles; + } + + /* ------------------------------------------------------------ */ + public String getInitParameter(String key) + { + return _initParameters.get(key); + } + + /* ------------------------------------------------------------ */ + public Set getInitParameterNames() + { + return _initParameters.keySet(); + } + + /* ------------------------------------------------------------ */ + /** Set an initialization parameter. + * @param key + * @param value + * @return previous value + * @throws IllegalStateException if the SecurityHandler is running + */ + public String setInitParameter(String key, String value) + { + if (isRunning()) + throw new IllegalStateException("running"); + return _initParameters.put(key,value); + } + + + /* ------------------------------------------------------------ */ + protected LoginService findLoginService() + { + List list = getServer().getBeans(LoginService.class); + + for (LoginService service : list) + if (service.getName().equals(getRealmName())) + return service; + if (list.size()>0) + return list.get(0); + return null; + } + + /* ------------------------------------------------------------ */ + protected IdentityService findIdentityService() + { + List services = getServer().getBeans(IdentityService.class); + if (services!=null && services.size()>0) + return services.get(0); + return null; + } + + /* ------------------------------------------------------------ */ + /** + */ + protected void doStart() + throws Exception + { + // complicated resolution of login and identity service to handle + // many different ways these can be constructed and injected. + + if (_loginService==null) + { + _loginService=findLoginService(); + if (_loginService!=null) + _loginServiceShared=true; + } + + if (_identityService==null) + { + if (_loginService!=null) + _identityService=_loginService.getIdentityService(); + + if (_identityService==null) + _identityService=findIdentityService(); + + if (_identityService==null) + _identityService=new DefaultIdentityService(); + } + + if (_loginService!=null) + { + if (_loginService.getIdentityService()==null) + _loginService.setIdentityService(_identityService); + else if (_loginService.getIdentityService()!=_identityService) + throw new IllegalStateException("LoginService has different IdentityService to "+this); + } + + if (!_loginServiceShared && _loginService instanceof LifeCycle) + ((LifeCycle)_loginService).start(); + + if (_authenticator==null && _authenticatorFactory!=null) + { + _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this); + if (_authenticator!=null) + _authMethod=_authenticator.getAuthMethod(); + } + + if (_authenticator==null) + { + Log.warn("No ServerAuthentication for "+this); + throw new IllegalStateException("No ServerAuthentication"); + } + + _authenticator.setConfiguration(this); + if (_authenticator instanceof LifeCycle) + ((LifeCycle)_authenticator).start(); + + + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop() + */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + + if (!_loginServiceShared && _loginService instanceof LifeCycle) + ((LifeCycle)_loginService).stop(); + + } + + protected boolean checkSecurity(Request request) + { + switch(request.getDispatcherType()) + { + case REQUEST: + case ASYNC: + return true; + case FORWARD: + if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null) + { + request.removeAttribute("org.eclipse.jetty.server.welcome"); + return true; + } + return false; + default: + return false; + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(java.lang.String, + * javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String pathInContext, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + final Request base_request = (request instanceof Request) ? (Request) request : HttpConnection.getCurrentConnection().getRequest(); + final Response base_response = (response instanceof Response) ? (Response) response : HttpConnection.getCurrentConnection().getResponse(); + final Handler handler=getHandler(); + + if (handler==null) + return; + + if (checkSecurity(base_request)) + { + Object constraintInfo = prepareConstraintInfo(pathInContext, base_request); + + // Check data constraints + if (!checkUserDataPermissions(pathInContext, base_request, base_response, constraintInfo)) + { + if (!base_request.isHandled()) + { + response.sendError(Response.SC_FORBIDDEN); + base_request.setHandled(true); + } + return; + } + + // is Auth mandatory? + boolean isAuthMandatory = isAuthMandatory(base_request, base_response, constraintInfo); + + // check authentication + UserIdentity old_user_identity=base_request.getUserIdentity(); + try + { + final Authenticator authenticator = _authenticator; + Authentication authentication = authenticator.validateRequest(request, response, isAuthMandatory); + + if (authentication.getAuthStatus() == Authentication.Status.SUCCESS) + { + final UserIdentity user_identity=authentication.getUserIdentity(); + base_request.setAuthType(authentication.getAuthMethod()); + base_request.setUserIdentity(user_identity); + + if (isAuthMandatory && !checkWebResourcePermissions(pathInContext, base_request, base_response, constraintInfo, user_identity)) + { + response.sendError(Response.SC_FORBIDDEN, "User not in required role"); + base_request.setHandled(true); + return; + } + + handler.handle(pathInContext, request, response); + + authenticator.secureResponse(request, response, isAuthMandatory, authentication); + } + else + { + base_request.setHandled(true); + } + } + catch (ServerAuthException e) + { + // jaspi 3.8.3 send HTTP 500 internal server error, with message + // from AuthException + response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } + finally + { + base_request.setUserIdentity(old_user_identity); + } + } + else + handler.handle(pathInContext, request, response); + } + + + /* ------------------------------------------------------------ */ + protected abstract Object prepareConstraintInfo(String pathInContext, Request request); + + /* ------------------------------------------------------------ */ + protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException; + + /* ------------------------------------------------------------ */ + protected abstract boolean isAuthMandatory(Request base_request, Response base_response, Object constraintInfo); + + /* ------------------------------------------------------------ */ + protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, + UserIdentity userIdentity) throws IOException; + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class NotChecked implements Principal + { + public String getName() + { + return null; + } + + public String toString() + { + return "NOT CHECKED"; + } + + public SecurityHandler getSecurityHandler() + { + return SecurityHandler.this; + } + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static Principal __NO_USER = new Principal() + { + public String getName() + { + return null; + } + + public String toString() + { + return "No User"; + } + }; + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * Nobody user. The Nobody UserPrincipal is used to indicate a partial state + * of authentication. A request with a Nobody UserPrincipal will be allowed + * past all authentication constraints - but will not be considered an + * authenticated request. It can be used by Authenticators such as + * FormAuthenticator to allow access to logon and error pages within an + * authenticated URI tree. + */ + public static Principal __NOBODY = new Principal() + { + public String getName() + { + return "Nobody"; + } + + public String toString() + { + return getName(); + } + }; + + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ServerAuthException.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ServerAuthException.java new file mode 100644 index 00000000000..d0f26943bff --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ServerAuthException.java @@ -0,0 +1,42 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.security.GeneralSecurityException; + +/** + * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $ + */ +public class ServerAuthException extends GeneralSecurityException +{ + + public ServerAuthException() + { + } + + public ServerAuthException(String s) + { + super(s); + } + + public ServerAuthException(String s, Throwable throwable) + { + super(s, throwable); + } + + public ServerAuthException(Throwable throwable) + { + super(throwable); + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/UserDataConstraint.java b/jetty-security/src/main/java/org/eclipse/jetty/security/UserDataConstraint.java new file mode 100644 index 00000000000..e1d4369242d --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/UserDataConstraint.java @@ -0,0 +1,35 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +/** + * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $ + */ +public enum UserDataConstraint +{ + None, Integral, Confidential; + + public static UserDataConstraint get(int dataConstraint) + { + if (dataConstraint < -1 || dataConstraint > 2) throw new IllegalArgumentException("Expected -1, 0, 1, or 2, not: " + dataConstraint); + if (dataConstraint == -1) return None; + return values()[dataConstraint]; + } + + public UserDataConstraint combine(UserDataConstraint other) + { + if (this.compareTo(other) < 0) return this; + return other; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java new file mode 100644 index 00000000000..d91251e6518 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java @@ -0,0 +1,106 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import java.io.IOException; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.DefaultAuthentication; +import org.eclipse.jetty.security.DefaultUserIdentity; +import org.eclipse.jetty.security.ServerAuthException; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.StringUtil; + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class BasicAuthenticator extends LoginAuthenticator +{ + /* ------------------------------------------------------------ */ + /** + * @param loginService + */ + public BasicAuthenticator() + { + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.Authenticator#getAuthMethod() + */ + public String getAuthMethod() + { + return Constraint.__BASIC_AUTH; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.Authenticator#validateRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, boolean) + */ + public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException + { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + String credentials = request.getHeader(HttpHeaders.AUTHORIZATION); + + try + { + if (credentials != null) + { + credentials = credentials.substring(credentials.indexOf(' ')+1); + credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1); + int i = credentials.indexOf(':'); + String username = credentials.substring(0,i); + String password = credentials.substring(i+1); + + UserIdentity user = _loginService.login(username,password); + if (user!=null) + { + if (user instanceof DefaultUserIdentity) + return ((DefaultUserIdentity)user).SUCCESSFUL_BASIC; + return new DefaultAuthentication(Authentication.Status.SUCCESS,Constraint.__BASIC_AUTH,user); + } + } + + if (!mandatory) + { + return DefaultAuthentication.SUCCESS_UNAUTH_RESULTS; + } + response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "basic realm=\"" + _loginService.getName() + '"'); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return DefaultAuthentication.SEND_CONTINUE_RESULTS; + } + catch (IOException e) + { + throw new ServerAuthException(e); + } + } + + // most likely validatedUser is not needed here. + + // corrct? + public Authentication.Status secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication validatedUser) throws ServerAuthException + { + return Authentication.Status.SUCCESS; + } + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java new file mode 100644 index 00000000000..4b2cd51997e --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java @@ -0,0 +1,98 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import java.io.IOException; +import java.security.Principal; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.DefaultAuthentication; +import org.eclipse.jetty.security.ServerAuthException; +import org.eclipse.jetty.server.UserIdentity; + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class ClientCertAuthenticator extends LoginAuthenticator +{ + public ClientCertAuthenticator() + { + super(); + } + + public String getAuthMethod() + { + return Constraint.__CERT_AUTH; + } + + /** + * TODO what should happen if an insecure page is accessed without a client + * cert? Current code requires a client cert always but allows access to + * insecure pages if it is not recognized. + * + * @return + * @throws ServerAuthException + */ + public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException + { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + java.security.cert.X509Certificate[] certs = (java.security.cert.X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); + + try + { + // Need certificates. + if (certs == null || certs.length == 0 || certs[0] == null) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, + "A client certificate is required for accessing this web application but the server's listener is not configured for mutual authentication (or the client did not provide a certificate)."); + return DefaultAuthentication.SEND_FAILURE_RESULTS; + } + + Principal principal = certs[0].getSubjectDN(); + if (principal == null) principal = certs[0].getIssuerDN(); + final String username = principal == null ? "clientcert" : principal.getName(); + + // TODO no idea if this is correct + final char[] credential = B64Code.encode(certs[0].getSignature()); + + UserIdentity user = _loginService.login(username,credential); + if (user!=null) + return new DefaultAuthentication(Authentication.Status.SUCCESS,Constraint.__CERT_AUTH2,user); + + if (!mandatory) + { + return DefaultAuthentication.SUCCESS_UNAUTH_RESULTS; + } + response.sendError(HttpServletResponse.SC_FORBIDDEN, "The provided client certificate does not correspond to a trusted user."); + return DefaultAuthentication.SEND_FAILURE_RESULTS; + } + catch (IOException e) + { + throw new ServerAuthException(e.getMessage()); + } + } + + public Authentication.Status secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication validatedUser) throws ServerAuthException + { + return Authentication.Status.SUCCESS; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DelegateAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DelegateAuthenticator.java new file mode 100644 index 00000000000..e7343cc7d2c --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DelegateAuthenticator.java @@ -0,0 +1,57 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.ServerAuthException; + +public class DelegateAuthenticator implements Authenticator +{ + protected final Authenticator _delegate; + + public void setConfiguration(Configuration configuration) + { + _delegate.setConfiguration(configuration); + } + + public String getAuthMethod() + { + return _delegate.getAuthMethod(); + } + + public DelegateAuthenticator(Authenticator delegate) + { + _delegate=delegate; + } + + public Authenticator getDelegate() + { + return _delegate; + } + + public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean manditory) throws ServerAuthException + { + return _delegate.validateRequest(request, response, manditory); + } + + public Authentication.Status secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication validatedUser) throws ServerAuthException + { + return _delegate.secureResponse(req,res, mandatory, validatedUser); + } + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java new file mode 100644 index 00000000000..dad736a197a --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java @@ -0,0 +1,333 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import java.io.IOException; +import java.security.MessageDigest; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.DefaultAuthentication; +import org.eclipse.jetty.security.DefaultUserIdentity; +import org.eclipse.jetty.security.ServerAuthException; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class DigestAuthenticator extends LoginAuthenticator +{ + protected long _maxNonceAge = 0; + protected long _nonceSecret = this.hashCode() ^ System.currentTimeMillis(); + protected boolean _useStale = false; + + public DigestAuthenticator() + { + super(); + } + + public String getAuthMethod() + { + return Constraint.__DIGEST_AUTH; + } + + public Authentication.Status secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication validatedUser) throws ServerAuthException + { + return Authentication.Status.SUCCESS; + } + + public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException + { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + String credentials = request.getHeader(HttpHeaders.AUTHORIZATION); + + try + { + boolean stale = false; + if (credentials != null) + { + if (Log.isDebugEnabled()) Log.debug("Credentials: " + credentials); + QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false); + final Digest digest = new Digest(request.getMethod()); + String last = null; + String name = null; + + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken(); + char c = (tok.length() == 1) ? tok.charAt(0) : '\0'; + + switch (c) + { + case '=': + name = last; + last = tok; + break; + case ',': + name = null; + case ' ': + break; + + default: + last = tok; + if (name != null) + { + if ("username".equalsIgnoreCase(name)) + digest.username = tok; + else if ("realm".equalsIgnoreCase(name)) + digest.realm = tok; + else if ("nonce".equalsIgnoreCase(name)) + digest.nonce = tok; + else if ("nc".equalsIgnoreCase(name)) + digest.nc = tok; + else if ("cnonce".equalsIgnoreCase(name)) + digest.cnonce = tok; + else if ("qop".equalsIgnoreCase(name)) + digest.qop = tok; + else if ("uri".equalsIgnoreCase(name)) + digest.uri = tok; + else if ("response".equalsIgnoreCase(name)) digest.response = tok; + break; + } + } + } + + int n = checkNonce(digest.nonce, (Request)request); + + if (n > 0) + { + UserIdentity user = _loginService.login(digest.username,digest); + if (user!=null) + { + if (user instanceof DefaultUserIdentity) + return ((DefaultUserIdentity)user).SUCCESSFUL_BASIC; + return new DefaultAuthentication(Authentication.Status.SUCCESS,Constraint.__DIGEST_AUTH,user); + } + } + else if (n == 0) stale = true; + + } + + if (!mandatory) { return DefaultAuthentication.SUCCESS_UNAUTH_RESULTS; } + String domain = request.getContextPath(); + if (domain == null) domain = "/"; + response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"" + _loginService.getName() + + "\", domain=\"" + + domain + + "\", nonce=\"" + + newNonce((Request)request) + + "\", algorithm=MD5, qop=\"auth\"" + + (_useStale ? (" stale=" + stale) : "")); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return DefaultAuthentication.SEND_CONTINUE_RESULTS; + } + catch (IOException e) + { + throw new ServerAuthException(e); + } + + } + + public String newNonce(Request request) + { + long ts=request.getTimeStamp(); + long sk = _nonceSecret; + + byte[] nounce = new byte[24]; + for (int i = 0; i < 8; i++) + { + nounce[i] = (byte) (ts & 0xff); + ts = ts >> 8; + nounce[8 + i] = (byte) (sk & 0xff); + sk = sk >> 8; + } + + byte[] hash = null; + try + { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.reset(); + md.update(nounce, 0, 16); + hash = md.digest(); + } + catch (Exception e) + { + Log.warn(e); + } + + for (int i = 0; i < hash.length; i++) + { + nounce[8 + i] = hash[i]; + if (i == 23) break; + } + + return new String(B64Code.encode(nounce)); + } + + /** + * @param nonce nonce to check + * @param request + * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce + */ + /* ------------------------------------------------------------ */ + private int checkNonce(String nonce, Request request) + { + try + { + byte[] n = B64Code.decode(nonce.toCharArray()); + if (n.length != 24) return -1; + + long ts = 0; + long sk = _nonceSecret; + byte[] n2 = new byte[16]; + System.arraycopy(n, 0, n2, 0, 8); + for (int i = 0; i < 8; i++) + { + n2[8 + i] = (byte) (sk & 0xff); + sk = sk >> 8; + ts = (ts << 8) + (0xff & (long) n[7 - i]); + } + + long age = request.getTimeStamp() - ts; + if (Log.isDebugEnabled()) Log.debug("age=" + age); + + byte[] hash = null; + try + { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.reset(); + md.update(n2, 0, 16); + hash = md.digest(); + } + catch (Exception e) + { + Log.warn(e); + } + + for (int i = 0; i < 16; i++) + if (n[i + 8] != hash[i]) return -1; + + if (_maxNonceAge > 0 && (age < 0 || age > _maxNonceAge)) return 0; // stale + + return 1; + } + catch (Exception e) + { + Log.ignore(e); + } + return -1; + } + + private static class Digest extends Credential + { + String method = null; + String username = null; + String realm = null; + String nonce = null; + String nc = null; + String cnonce = null; + String qop = null; + String uri = null; + String response = null; + + /* ------------------------------------------------------------ */ + Digest(String m) + { + method = m; + } + + /* ------------------------------------------------------------ */ + public boolean check(Object credentials) + { + String password = (credentials instanceof String) ? (String) credentials : credentials.toString(); + + try + { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] ha1; + if (credentials instanceof Credential.MD5) + { + // Credentials are already a MD5 digest - assume it's in + // form user:realm:password (we have no way to know since + // it's a digest, alright?) + ha1 = ((Credential.MD5) credentials).getDigest(); + } + else + { + // calc A1 digest + md.update(username.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(realm.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(password.getBytes(StringUtil.__ISO_8859_1)); + ha1 = md.digest(); + } + // calc A2 digest + md.reset(); + md.update(method.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(uri.getBytes(StringUtil.__ISO_8859_1)); + byte[] ha2 = md.digest(); + + // calc digest + // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" + // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) ) + // <"> + // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) + // ) > <"> + + md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(nonce.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(nc.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(cnonce.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(qop.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1)); + byte[] digest = md.digest(); + + // check digest + return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response)); + } + catch (Exception e) + { + Log.warn(e); + } + + return false; + } + + public String toString() + { + return username + "," + response; + } + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java new file mode 100644 index 00000000000..c01fe7258fd --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java @@ -0,0 +1,219 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import java.io.IOException; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.DefaultAuthentication; +import org.eclipse.jetty.security.ServerAuthException; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class FormAuthenticator extends LoginAuthenticator +{ + public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page"; + public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page"; + public final static String __J_URI = "org.eclipse.jetty.util.URI"; + public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth"; + public final static String __J_SECURITY_CHECK = "/j_security_check"; + public final static String __J_USERNAME = "j_username"; + public final static String __J_PASSWORD = "j_password"; + private String _formErrorPage; + private String _formErrorPath; + private String _formLoginPage; + private String _formLoginPath; + + public FormAuthenticator() + { + } + + /* ------------------------------------------------------------ */ + public FormAuthenticator(String login,String error) + { + if (login!=null) + setLoginPage(login); + if (error!=null) + setErrorPage(error); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.Configuration) + */ + @Override + public void setConfiguration(Configuration configuration) + { + super.setConfiguration(configuration); + String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE); + if (login!=null) + setLoginPage(login); + String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE); + if (error!=null) + setErrorPage(error); + } + + + + public String getAuthMethod() + { + return Constraint.__FORM_AUTH; + } + + private void setLoginPage(String path) + { + if (!path.startsWith("/")) + { + Log.warn("form-login-page must start with /"); + path = "/" + path; + } + _formLoginPage = path; + _formLoginPath = path; + if (_formLoginPath.indexOf('?') > 0) + _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?')); + } + + /* ------------------------------------------------------------ */ + private void setErrorPage(String path) + { + if (path == null || path.trim().length() == 0) + { + _formErrorPath = null; + _formErrorPage = null; + } + else + { + if (!path.startsWith("/")) + { + Log.warn("form-error-page must start with /"); + path = "/" + path; + } + _formErrorPage = path; + _formErrorPath = path; + + if (_formErrorPath.indexOf('?') > 0) + _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?')); + } + } + + public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException + { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + HttpSession session = request.getSession(mandatory); + String uri = request.getPathInfo(); + // not mandatory and not authenticated + if (session == null || isLoginOrErrorPage(uri)) + { + return DefaultAuthentication.SUCCESS_UNAUTH_RESULTS; + } + + + try + { + // Handle a request for authentication. + // TODO perhaps j_securitycheck can be uri suffix? + if (uri.endsWith(__J_SECURITY_CHECK)) + { + final String username = request.getParameter(__J_USERNAME); + final char[] password = request.getParameter(__J_PASSWORD).toCharArray(); + + UserIdentity user = _loginService.login(username,password); + if (user!=null) + { + // Redirect to original request + String nuri = (String) session.getAttribute(__J_URI); + if (nuri == null || nuri.length() == 0) + { + nuri = request.getContextPath(); + if (nuri.length() == 0) nuri = URIUtil.SLASH; + } + // TODO shouldn't we forward to original URI instead? + session.removeAttribute(__J_URI); // Remove popped return URI. + response.setContentLength(0); + response.sendRedirect(response.encodeRedirectURL(nuri)); + return new DefaultAuthentication(Authentication.Status.SEND_SUCCESS,Constraint.__FORM_AUTH,user); + } + + // not authenticated + if (Log.isDebugEnabled()) Log.debug("Form authentication FAILED for " + StringUtil.printable(username)); + if (_formErrorPage == null) + { + if (response != null) + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + else + { +// response.setContentLength(0); //???? + RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage); + dispatcher.forward(request, response); + } + // TODO is this correct response if isMandatory false??? Can + // that occur? + return DefaultAuthentication.SEND_FAILURE_RESULTS; + } + // Check if the session is already authenticated. + + // Don't authenticate authform or errorpage + if (!mandatory) + // TODO verify this is correct action + return DefaultAuthentication.SUCCESS_UNAUTH_RESULTS; + + // redirect to login page + if (request.getQueryString() != null) + uri += "?" + request.getQueryString(); + //TODO is this safe if the client is sending several requests concurrently in the same session to secured resources? + session.setAttribute(__J_URI, request.getScheme() + "://" + + request.getServerName() + + ":" + + request.getServerPort() + + URIUtil.addPaths(request.getContextPath(), uri)); + RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage); + dispatcher.forward(request, response); + return DefaultAuthentication.SEND_CONTINUE_RESULTS; + } + catch (IOException e) + { + throw new ServerAuthException(e); + } + catch (ServletException e) + { + throw new ServerAuthException(e); + } + } + + public boolean isLoginOrErrorPage(String pathInContext) + { + return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath)); + } + + public Authentication.Status secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication validatedUser) throws ServerAuthException + { + return Authentication.Status.SUCCESS; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LazyAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LazyAuthenticator.java new file mode 100644 index 00000000000..65c237bb352 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LazyAuthenticator.java @@ -0,0 +1,45 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.LazyAuthentication; +import org.eclipse.jetty.security.ServerAuthException; + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class LazyAuthenticator extends DelegateAuthenticator +{ + public LazyAuthenticator(Authenticator delegate) + { + super(delegate); + } + + /** + * @see org.eclipse.jetty.security.Authenticator#validateRequest(ServletRequest, ServletResponse, boolean) + */ + public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException + { + if (!mandatory) + { + return new LazyAuthentication(_delegate,request,response); + } + return _delegate.validateRequest(request, response, mandatory); + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java new file mode 100644 index 00000000000..444d2498536 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java @@ -0,0 +1,44 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.LoginService; + +public abstract class LoginAuthenticator implements Authenticator +{ + protected LoginService _loginService; + protected IdentityService _identityService; + + protected LoginAuthenticator() + { + } + + public void setConfiguration(Configuration configuration) + { + _loginService=configuration.getLoginService(); + if (_loginService==null) + throw new IllegalStateException("No LoginService for "+this); + _identityService=configuration.getIdentityService(); + if (_identityService==null) + throw new IllegalStateException("No IdentityService for "+this); + } + + public LoginService getLoginService() + { + return _loginService; + } + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallback.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallback.java new file mode 100644 index 00000000000..06a285c2986 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallback.java @@ -0,0 +1,50 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import java.security.Principal; + +import javax.security.auth.Subject; + + +/** + * This is similar to the jaspi PasswordValidationCallback but includes user + * principal and group info as well. + * + * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $ + */ +public interface LoginCallback +{ + public Subject getSubject(); + + public String getUserName(); + + public Object getCredential(); + + public boolean isSuccess(); + + public void setSuccess(boolean success); + + public Principal getUserPrincipal(); + + public void setUserPrincipal(Principal userPrincipal); + + public String[] getRoles(); + + public void setRoles(String[] roles); + + public void clearPassword(); + + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java new file mode 100644 index 00000000000..bb1ee368982 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java @@ -0,0 +1,104 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import java.security.Principal; + +import javax.security.auth.Subject; + +import org.eclipse.jetty.server.UserIdentity; + +/** + * This is similar to the jaspi PasswordValidationCallback but includes user + * principal and group info as well. + * + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class LoginCallbackImpl implements LoginCallback +{ + // initial data + private final Subject subject; + + private final String userName; + + private Object credential; + + private boolean success; + + private Principal userPrincipal; + + private String[] roles = UserIdentity.NO_ROLES; + + //TODO could use Credential instance instead of Object if Basic/Form create a Password object + public LoginCallbackImpl (Subject subject, String userName, Object credential) + { + this.subject = subject; + this.userName = userName; + this.credential = credential; + } + + public Subject getSubject() + { + return subject; + } + + public String getUserName() + { + return userName; + } + + public Object getCredential() + { + return credential; + } + + public boolean isSuccess() + { + return success; + } + + public void setSuccess(boolean success) + { + this.success = success; + } + + public Principal getUserPrincipal() + { + return userPrincipal; + } + + public void setUserPrincipal(Principal userPrincipal) + { + this.userPrincipal = userPrincipal; + } + + public String[] getRoles() + { + return roles; + } + + public void setRoles(String[] groups) + { + this.roles = groups; + } + + public void clearPassword() + { + if (credential != null) + { + credential = null; + } + } + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionCachingAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionCachingAuthenticator.java new file mode 100644 index 00000000000..b862fd8efef --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionCachingAuthenticator.java @@ -0,0 +1,59 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.DefaultAuthentication; +import org.eclipse.jetty.security.ServerAuthException; + +/** + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class SessionCachingAuthenticator extends DelegateAuthenticator +{ + public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth"; + + + public SessionCachingAuthenticator(Authenticator delegate) + { + super(delegate); + } + + public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException + { + HttpSession session = ((HttpServletRequest)request).getSession(mandatory); + // not mandatory and not authenticated + if (session == null) + return DefaultAuthentication.SUCCESS_UNAUTH_RESULTS; + + Authentication authentication = (Authentication) session.getAttribute(__J_AUTHENTICATED); + if (authentication != null) + return authentication; + + authentication = _delegate.validateRequest(request, response, mandatory); + if (authentication != null && authentication.getUserIdentity().getSubject() != null) + { + Authentication next=new DefaultAuthentication(Authentication.Status.SUCCESS,authentication.getAuthMethod(),authentication.getUserIdentity()); + session.setAttribute(__J_AUTHENTICATED, next); + } + return authentication; + } + +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/XCPSCachingAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/XCPSCachingAuthenticator.java new file mode 100644 index 00000000000..c139bb45f17 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/XCPSCachingAuthenticator.java @@ -0,0 +1,55 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security.authentication; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.security.Authentication; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.CrossContextPsuedoSession; +import org.eclipse.jetty.security.ServerAuthException; + +/** + * Cross-context psuedo-session caching ServerAuthentication + * + * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + */ +public class XCPSCachingAuthenticator extends DelegateAuthenticator +{ + public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth"; + + private final CrossContextPsuedoSession _xcps; + + public XCPSCachingAuthenticator(Authenticator delegate, CrossContextPsuedoSession xcps) + { + super(delegate); + this._xcps = xcps; + } + + public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean manditory) throws ServerAuthException + { + + Authentication serverAuthResult = _xcps.fetch((HttpServletRequest)request); + if (serverAuthResult != null) return serverAuthResult; + + serverAuthResult = _delegate.validateRequest(request, response, manditory); + if (serverAuthResult != null) _xcps.store(serverAuthResult, (HttpServletResponse)response); + + return serverAuthResult; + } + +} diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java new file mode 100644 index 00000000000..d389c6adaed --- /dev/null +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java @@ -0,0 +1,607 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.security; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.security.authentication.SessionCachingAuthenticator; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.server.session.SessionHandler; + +/** + * + */ +public class ConstraintTest extends TestCase +{ + private static final String TEST_REALM = "TestRealm"; + + Server _server = new Server(); + LocalConnector _connector = new LocalConnector(); + ContextHandler _context = new ContextHandler(); + SessionHandler _session = new SessionHandler(); + ConstraintSecurityHandler _security = new ConstraintSecurityHandler(); + HashLoginService _loginService = new HashLoginService(TEST_REALM); + + RequestHandler _handler = new RequestHandler(); + private static final String APP_CONTEXT = "localhost /ctx"; + + { + _server.setConnectors(new Connector[]{_connector}); + _context.setContextPath("/ctx"); + _server.setHandler(_context); + _context.setHandler(_session); + _session.setHandler(_security); + _security.setHandler(_handler); + + _loginService.putUser("user",new Password("password")); + _loginService.putUser("user2",new Password("password"), new String[] {"user"}); + _loginService.putUser("admin",new Password("password"), new String[] {"user","administrator"}); + _server.addBean(_loginService); + } + + public ConstraintTest(String arg0) + { + super(arg0); + Constraint constraint0 = new Constraint(); + constraint0.setAuthenticate(true); + constraint0.setName("forbid"); + ConstraintMapping mapping0 = new ConstraintMapping(); + mapping0.setPathSpec("/forbid/*"); + mapping0.setConstraint(constraint0); + + Constraint constraint1 = new Constraint(); + constraint1.setAuthenticate(true); + constraint1.setName("auth"); + constraint1.setRoles(new String[]{Constraint.ANY_ROLE}); + ConstraintMapping mapping1 = new ConstraintMapping(); + mapping1.setPathSpec("/auth/*"); + mapping1.setConstraint(constraint1); + + Constraint constraint2 = new Constraint(); + constraint2.setAuthenticate(true); + constraint2.setName("admin"); + constraint2.setRoles(new String[]{"administrator"}); + ConstraintMapping mapping2 = new ConstraintMapping(); + mapping2.setPathSpec("/admin/*"); + mapping2.setConstraint(constraint2); + + Constraint constraint3 = new Constraint(); + constraint3.setAuthenticate(false); + constraint3.setName("relax"); + ConstraintMapping mapping3 = new ConstraintMapping(); + mapping3.setPathSpec("/admin/relax/*"); + mapping3.setConstraint(constraint3); + + Set knownRoles=new HashSet(); + knownRoles.add("user"); + knownRoles.add("administrator"); + + _security.setConstraintMappings(new ConstraintMapping[] + { + mapping0, mapping1, mapping2, mapping3 + },knownRoles); + } + + /* + * @see TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + + } + + /* + * @see TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + _server.stop(); + } + + + + public void testConstraints() + throws Exception + { + ConstraintMapping[] mappings =_security.getConstraintMappings(); + + assertTrue (mappings[0].getConstraint().isForbidden()); + assertFalse(mappings[1].getConstraint().isForbidden()); + assertFalse(mappings[2].getConstraint().isForbidden()); + assertFalse(mappings[3].getConstraint().isForbidden()); + + assertFalse(mappings[0].getConstraint().isAnyRole()); + assertTrue (mappings[1].getConstraint().isAnyRole()); + assertFalse(mappings[2].getConstraint().isAnyRole()); + assertFalse(mappings[3].getConstraint().isAnyRole()); + + assertFalse(mappings[0].getConstraint().hasRole("administrator")); + assertTrue (mappings[1].getConstraint().hasRole("administrator")); + assertTrue (mappings[2].getConstraint().hasRole("administrator")); + assertFalse(mappings[3].getConstraint().hasRole("administrator")); + + assertTrue (mappings[0].getConstraint().getAuthenticate()); + assertTrue (mappings[1].getConstraint().getAuthenticate()); + assertTrue (mappings[2].getConstraint().getAuthenticate()); + assertFalse(mappings[3].getConstraint().getAuthenticate()); + } + + + public void testBasic() + throws Exception + { + _security.setAuthenticator(new BasicAuthenticator()); + _security.setStrict(false); + _server.start(); + + String response; + response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403 Forbidden")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user:wrong") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + + // test admin + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("admin:wrong") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "\r\n"); + + assertTrue(response.startsWith("HTTP/1.1 403 ")); + assertTrue(response.indexOf("User not in required role") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("admin:password") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/relax/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + } + + public void testForm() + throws Exception + { + _security.setAuthenticator(new SessionCachingAuthenticator( + new FormAuthenticator("/testLoginPage","/testErrorPage"))); + _security.setStrict(false); + _server.start(); + + String response; + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403 Forbidden")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); +// assertTrue(response.startsWith("HTTP/1.1 302 ")); +// assertTrue(response.indexOf("testLoginPage") > 0); + String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + + _connector.reopen(); + response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Content-Length: 31\r\n" + + "\r\n" + + "j_username=user&j_password=wrong\r\n"); + //TODO we are forwarded to the error page now. Is there any way to verify the contents? + assertTrue(response.startsWith("HTTP/1.1 200 ")); +// assertTrue(response.indexOf("Location") > 0); +// assertTrue(response.indexOf("testErrorPage") > 0); + + + _connector.reopen(); + response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Content-Length: 35\r\n" + + "\r\n" + + "j_username=user&j_password=password\r\n"); + assertTrue(response.startsWith("HTTP/1.1 302 ")); + assertTrue(response.indexOf("Location") > 0); + assertTrue(response.indexOf("/ctx/auth/info") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403")); + assertTrue(response.indexOf("User not in required role") > 0); + + } + + public void testStrictBasic() + throws Exception + { + _security.setAuthenticator(new BasicAuthenticator()); + _server.start(); + + String response; + response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403 Forbidden")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user:wrong") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + + // test admin + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("admin:wrong") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "\r\n"); + + assertTrue(response.startsWith("HTTP/1.1 403 ")); + assertTrue(response.indexOf("User not in required role") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("admin:password") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/relax/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + } + + public void testStrictForm() + throws Exception + { + _security.setAuthenticator(new SessionCachingAuthenticator( + new FormAuthenticator("/testLoginPage","/testErrorPage"))); + + _server.start(); + + String response; + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403 Forbidden")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); +// assertTrue(response.startsWith("HTTP/1.1 302 ")); +// assertTrue(response.indexOf("testLoginPage") > 0); + String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + + _connector.reopen(); + response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Content-Length: 31\r\n" + + "\r\n" + + "j_username=user&j_password=wrong\r\n"); + //TODO we are forwarded to the error page now. Is there any way to verify the contents? + assertTrue(response.startsWith("HTTP/1.1 200 ")); +// assertTrue(response.indexOf("Location") > 0); +// assertTrue(response.indexOf("testErrorPage") > 0); + + + _connector.reopen(); + response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Content-Length: 35\r\n" + + "\r\n" + + "j_username=user&j_password=password\r\n"); + assertTrue(response.startsWith("HTTP/1.1 302 ")); + assertTrue(response.indexOf("Location") > 0); + assertTrue(response.indexOf("/ctx/auth/info") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403")); + assertTrue(response.indexOf("User not in required role") > 0); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403")); + assertTrue(response.indexOf("User not in required role") > 0); + + + + // log in again as user2 + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); +// assertTrue(response.startsWith("HTTP/1.1 302 ")); +// assertTrue(response.indexOf("testLoginPage") > 0); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + + _connector.reopen(); + response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Content-Length: 36\r\n" + + "\r\n" + + "j_username=user2&j_password=password\r\n"); + assertTrue(response.startsWith("HTTP/1.1 302 ")); + assertTrue(response.indexOf("Location") > 0); + assertTrue(response.indexOf("/ctx/auth/info") > 0); + + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403")); + assertTrue(response.indexOf("User not in required role") > 0); + + + + // log in again as admin + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); +// assertTrue(response.startsWith("HTTP/1.1 302 ")); +// assertTrue(response.indexOf("testLoginPage") > 0); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + + _connector.reopen(); + response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Content-Length: 36\r\n" + + "\r\n" + + "j_username=admin&j_password=password\r\n"); + assertTrue(response.startsWith("HTTP/1.1 302 ")); + assertTrue(response.indexOf("Location") > 0); + assertTrue(response.indexOf("/ctx/auth/info") > 0); + + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + + } + + public void testRoleRef() + throws Exception + { + RoleCheckHandler check=new RoleCheckHandler(); + _security.setHandler(check); + _security.setAuthenticator(new BasicAuthenticator()); + _security.setStrict(false); + _server.start(); + + String response; + response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 500 ")); + + _server.stop(); + + RoleRefHandler roleref = new RoleRefHandler(); + _security.setHandler(roleref); + roleref.setHandler(check); + + _server.start(); + + _connector.reopen(); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + } + + class RequestHandler extends AbstractHandler + { + public void handle(String target, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException + { + ((Request) request).setHandled(true); + if (request.getAuthType()==null || "user".equals(request.getRemoteUser()) || request.isUserInRole("user")) + response.setStatus(200); + else + response.sendError(500); + } + } + + class RoleRefHandler extends HandlerWrapper + { + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + UserIdentity old = ((Request) request).getUserIdentity(); + UserIdentity scoped = _security.getIdentityService().associate(old, + new UserIdentity.Scope() + { + + public String getContextPath() + { + return "/"; + } + + public String getName() + { + return "someServlet"; + } + + public Map getRoleRefMap() + { + Map map = new HashMap(); + map.put("untranslated", "user"); + return map; + } + + public String getRunAsRole() + { + return null; + } + + }); + ((Request)request).setUserIdentity(scoped); + + try + { + super.handle(target,request,response); + } + finally + { + _security.getIdentityService().disassociate(scoped); + ((Request)request).setUserIdentity(old); + } + } + } + + class RoleCheckHandler extends AbstractHandler + { + public void handle(String target, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException + { + ((Request) request).setHandled(true); + if (request.getAuthType()==null || "user".equals(request.getRemoteUser()) || request.isUserInRole("untranslated")) + response.setStatus(200); + else + response.sendError(500); + } + } +} diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml new file mode 100644 index 00000000000..b688ccca7a5 --- /dev/null +++ b/jetty-server/pom.xml @@ -0,0 +1,106 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-server + Jetty :: Server Core + The core jetty server artifact. + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.server + J2SE-1.5 + http://jetty.eclipse.org + !org.eclipse.jetty.*,!org.eclipse.xml.*,!org.eclipse.resource.*,!org.eclipse.io.*,!org.eclipse.servlet.jetty.*,javax.servlet.resources;resolution:=optional,javax.servlet.jsp;resolution:=optional,org.apache.jasper.servlet;resolution:=optional,org.eclipse.jetty.handler.management;resolution:=optional,* + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + artifact-jar + + jar + + + + test-jar + + test-jar + + + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + site-component.xml + config.xml + + + + + + + + + + junit + junit + test + + + org.mortbay.jetty + servlet-api + + + org.eclipse.jetty + jetty-http + ${project.version} + + + diff --git a/jetty-server/src/main/assembly/site-component.xml b/jetty-server/src/main/assembly/site-component.xml new file mode 100644 index 00000000000..575269c1a1e --- /dev/null +++ b/jetty-server/src/main/assembly/site-component.xml @@ -0,0 +1,15 @@ + + site-component + + jar + + + + ${basedir} + jetty + + src/main/resources/org/eclipse/** + + + + diff --git a/jetty-server/src/main/config/etc/jdbcRealm.properties b/jetty-server/src/main/config/etc/jdbcRealm.properties new file mode 100644 index 00000000000..48104d88ad9 --- /dev/null +++ b/jetty-server/src/main/config/etc/jdbcRealm.properties @@ -0,0 +1,72 @@ +# +# This is a sample properties file for the org.eclipse.jetty.security.JDBCLoginService +# implemtation of the UserRealm interface. This allows Jetty users authentication +# to work from a database. +# +# +-------+ +------------+ +-------+ +# | users | | user_roles | | roles | +# +-------+ +------------+ +-------+ +# | id | /| user_id |\ | id | +# | user -------| role_id |------- role | +# | pwd | \| |/ | | +# +-------+ +------------+ +-------+ +# +# +# 'cachetime' is a time in seconds to cache positive database +# lookups in internal hash table. Set to 0 to disable caching. +# +# +# For MySQL: +# create a MYSQL user called "jetty" with password "jetty" +# +# Create the tables: +# create table users +# ( +# id integer primary key, +# username varchar(100) not null unique key, +# pwd varchar(20) not null +# ); +# +# create table roles +# ( +# id integer primary key, +# role varchar(100) not null unique key +# ); +# +# create table user_roles +# ( +# user_id integer not null, +# role_id integer not null, +# unique key (user_id, role_id), +# index(user_id) +# ); +# +# I'm not sure unique key with a first component of user_id will be +# user by MySQL in query, so additional index wouldn't hurt. +# +# To test JDBC implementation: +# +# mysql> insert into users values (1, 'admin', 'password'); +# mysql> insert into roles values (1, 'server-administrator'); +# mysql> insert into roles values (2, 'content-administrator'); +# mysql> insert into user_roles values (1, 1); +# mysql> insert into user_roles values (1, 2); +# +# Replace HashUserRealm in etc/admin.xml with JDBCUserRealm and +# set path to properties file. +# +jdbcdriver = org.gjt.mm.mysql.Driver +url = jdbc:mysql://localhost/jetty +username = jetty +password = jetty +usertable = users +usertablekey = id +usertableuserfield = username +usertablepasswordfield = pwd +roletable = roles +roletablekey = id +roletablerolefield = role +userroletable = user_roles +userroletableuserkey = user_id +userroletablerolekey = role_id +cachetime = 300 diff --git a/jetty-server/src/main/config/etc/jetty-bio-ssl.xml b/jetty-server/src/main/config/etc/jetty-bio-ssl.xml new file mode 100644 index 00000000000..c90cfca7fa8 --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-bio-ssl.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + 9443 + 30000 + /etc/keystore + OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + OBF:1u2u1wml1z7s1z7a1wnl1u2g + /etc/keystore + OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + + + + diff --git a/jetty-server/src/main/config/etc/jetty-bio.xml b/jetty-server/src/main/config/etc/jetty-bio.xml new file mode 100644 index 00000000000..f08dbf4726e --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-bio.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + 50000 + 1500 + + + + + diff --git a/jetty-server/src/main/config/etc/jetty-proxy.xml b/jetty-server/src/main/config/etc/jetty-proxy.xml new file mode 100644 index 00000000000..c402bf2b196 --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-proxy.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + 10 + 50 + + + + + + + + + + + + + + 300000 + 2 + false + 20000 + 5000 + + + + + + + + + org.eclipse.jetty.servlets.AsyncProxyServlet + / + + + + + + + + true + true + true + 1000 + + diff --git a/jetty-server/src/main/config/etc/jetty-ssl.xml b/jetty-server/src/main/config/etc/jetty-ssl.xml new file mode 100644 index 00000000000..ff5e20a4199 --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-ssl.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + 8443 + 30000 + 2 + 100 + /etc/keystore + OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + OBF:1u2u1wml1z7s1z7a1wnl1u2g + /etc/keystore + OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + + + + diff --git a/jetty-server/src/main/config/etc/jetty-stats.xml b/jetty-server/src/main/config/etc/jetty-stats.xml new file mode 100644 index 00000000000..0414f05b541 --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-stats.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/jetty-server/src/main/config/etc/jetty-xinetd.xml b/jetty-server/src/main/config/etc/jetty-xinetd.xml new file mode 100644 index 00000000000..675896b4086 --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-xinetd.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + 300000 + 2 + false + 20000 + 5000 + + + + + diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml new file mode 100644 index 00000000000..ca0eafdc26e --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + 10 + 200 + + + + + + + + + + + + + + + + + 300000 + 2 + false + 8443 + 20000 + 5000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /contexts + 5 + + + + + + + + + + + + + + + + + + + + + + /webapps + false + true + false + /etc/webdefault.xml + + + + + + + + + + + + + + + Test Realm + /etc/realm.properties + 0 + + + + + + + + + + + + + + + /logs/yyyy_mm_dd.request.log + yyyy_MM_dd + 90 + true + false + false + GMT + + + + + + + + true + true + true + 1000 + + diff --git a/jetty-server/src/main/config/etc/keystore b/jetty-server/src/main/config/etc/keystore new file mode 100644 index 0000000000000000000000000000000000000000..08f6cda8a7b0104236c37f3c06b4cda1d8fc58e6 GIT binary patch literal 1416 zcmezO_TO6u1_mY|W&~r_tkjZ{N+3_R)UekNZbhDKH`EXUR-=hO7hY&y{H-e(+TowuJ*{`? zwVfT`ns@KmCLd9_H?QZ%#(VNFq-*~l&ib%)(Y!;gI{W`RJIFow^~EB{E~hHzXm8Qd z$+I3OFWL4l%D`0pEH}^DdFqAf_3vNy-07jRmW@8^t|?HpH{w+9zJZ0HTQ{GwgE`KsL0)*BinM>ExK{8V|s z_c83?=Ue&W%>UG?wT~>g^C-D5b6Kid$utMyOUY~8&RQ2O5Kv$6WA72RZCZOI`%XW7 zWGv*iET~D-A?Uf$)PkAXC#^&coOKW1)_jbSp1ybc*-Pg z*1AKX?hJ-g?<}Z|$~fj~%&Q)8GVXHmuY_w7iL*9Li<#ba=>(4shvkf&zalFllPfRmB({Gr>G+(AG{|j5^yi-$J^G%gy&!3%F`Y=?Z zqHag3(zm<4?6(-6S8caBa7ykAzsvLfb>|ds{`&FW?ULoW&INs`nHskiv_4^yUS(;u zwJG}=M=euvt`d*!{pAW3tsAR0lWEH+#d4(r%9_{jEh_BIde_H z2F(udYbrB!9Sz@xPFu0!#>2SI`Ssat!p3^(;?40Uy|?vOtS!3O8Zd1|F}L60tKCOm znylYEA@-w}ri%BqU%N_+XGl&zI@4*vv5nXG(`N27=6zy$WG5s+N9dUvSOODr2QVSG z7&I}yWn%FZKK#XimyJ`a&7GmI50%m z5h5xEN+4Za!qUF^MI{POiIob@`FX{qIVG8S=?VcQl?py3DTaIoJRnuv!mM7P3}z^0 zAOYet3k!lXoL(+aZ&G5VUVc%!ft)z6frX)=fw_T+k%h5YlsK=knSrs9DU>^yoZ2`a zIUIo{19M|9Ff2Qn8XFm!A3o&OeJrUOYjfQ^)giy{^|QK1CEP2QH%vD1Nc3MB^xmW4 z2fxzex2hhF*O)r=%WpirQ+H{vy7qk+o=56U7bdiMykRRo{_oVH)zDhW2yL+!vyn`wiA33|lR42Pgou}g~dvauF+U45K+=Wccj0}v( z&I3j>GtgZkrPhJg=H9<%l_$GC*-}{lrfI>Hv$DOvyPj6+9TS$eJZ37ao71_#A!EVD zCY$?z=d9ULtT&}=o8QIxpMTDAp0ptI@ccyM2W9WgVozM<*}KLmGg5O$WNJ zyVS~6#D7};!ib^4U#WeU%`4Hj5}6$f=N+nWnZl~&!Rl~+N3e: [, ...] +# +# Passwords may be clear text, obfuscated or checksummed. The class +# org.eclipse.util.Password should be used to generate obfuscated +# passwords or password checksums +# +# If DIGEST Authentication is used, the password must be in a recoverable +# format, either plain text or OBF:. +# +jetty: MD5:164c88b302622e17050af52c89945d44,user +admin: CRYPT:ad1ks..kc.1Ug,server-administrator,content-administrator,admin +other: OBF:1xmk1w261u9r1w1c1xmq,user +plain: plain,user +user: password,user + +# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password +digest: MD5:6e120743ad67abfbc385bc2bb754e297,user diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java new file mode 100644 index 00000000000..1d3430ae2b6 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java @@ -0,0 +1,990 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import javax.servlet.ServletRequest; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.io.AbstractBuffers; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.thread.ThreadPool; + + +/** Abstract Connector implementation. + * This abstract implementation of the Connector interface provides:

      + *
    • AbstractLifeCycle implementation
    • + *
    • Implementations for connector getters and setters
    • + *
    • Buffer management
    • + *
    • Socket configuration
    • + *
    • Base acceptor thread
    • + *
    • Optional reverse proxy headers checking
    • + *
    + * + * + */ +public abstract class AbstractConnector extends AbstractBuffers implements Connector +{ + private String _name; + + private Server _server; + private ThreadPool _threadPool; + private String _host; + private int _port=0; + private String _integralScheme=HttpSchemes.HTTPS; + private int _integralPort=0; + private String _confidentialScheme=HttpSchemes.HTTPS; + private int _confidentialPort=0; + private int _acceptQueueSize=0; + private int _acceptors=1; + private int _acceptorPriorityOffset=0; + private boolean _useDNS; + private boolean _forwarded; + private String _hostHeader; + private String _forwardedHostHeader = "X-Forwarded-Host"; // default to mod_proxy_http header + private String _forwardedServerHeader = "X-Forwarded-Server"; // default to mod_proxy_http header + private String _forwardedForHeader = "X-Forwarded-For"; // default to mod_proxy_http header + private boolean _reuseAddress=true; + + protected int _maxIdleTime=200000; + protected int _lowResourceMaxIdleTime=-1; + protected int _soLingerTime=-1; + + private transient Thread[] _acceptorThread; + + Object _statsLock = new Object(); + transient long _statsStartedAt=-1; + transient int _requests; + transient int _connections; // total number of connections made to server + + transient int _connectionsOpen; // number of connections currently open + transient int _connectionsOpenMin; // min number of connections open simultaneously + transient int _connectionsOpenMax; // max number of connections open simultaneously + + transient long _connectionsDurationMin; // min duration of a connection + transient long _connectionsDurationMax; // max duration of a connection + transient long _connectionsDurationTotal; // total duration of all coneection + + transient int _connectionsRequestsMin; // min requests per connection + transient int _connectionsRequestsMax; // max requests per connection + + + /* ------------------------------------------------------------------------------- */ + /** + */ + public AbstractConnector() + { + } + + /* ------------------------------------------------------------------------------- */ + /* + */ + public Server getServer() + { + return _server; + } + + /* ------------------------------------------------------------------------------- */ + public void setServer(Server server) + { + _server=server; + } + + /* ------------------------------------------------------------------------------- */ + /* + * @see org.eclipse.jetty.http.HttpListener#getHttpServer() + */ + public ThreadPool getThreadPool() + { + return _threadPool; + } + + /* ------------------------------------------------------------------------------- */ + public void setThreadPool(ThreadPool pool) + { + _threadPool=pool; + } + + /* ------------------------------------------------------------------------------- */ + /** + */ + public void setHost(String host) + { + _host=host; + } + + /* ------------------------------------------------------------------------------- */ + /* + */ + public String getHost() + { + return _host; + } + + /* ------------------------------------------------------------------------------- */ + /* + * @see org.eclipse.jetty.server.server.HttpListener#setPort(int) + */ + public void setPort(int port) + { + _port=port; + } + + /* ------------------------------------------------------------------------------- */ + /* + * @see org.eclipse.jetty.server.server.HttpListener#getPort() + */ + public int getPort() + { + return _port; + } + + + /* ------------------------------------------------------------ */ + /** + * @return Returns the maxIdleTime. + */ + public int getMaxIdleTime() + { + return _maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * Set the maximum Idle time for a connection, which roughly translates + * to the {@link Socket#setSoTimeout(int)} call, although with NIO + * implementations other mechanisms may be used to implement the timeout. + * The max idle time is applied:
      + *
    • When waiting for a new request to be received on a connection
    • + *
    • When reading the headers and content of a request
    • + *
    • When writing the headers and content of a response
    • + *
    + * Jetty interprets this value as the maximum time between some progress being + * made on the connection. So if a single byte is read or written, then the + * timeout (if implemented by jetty) is reset. However, in many instances, + * the reading/writing is delegated to the JVM, and the semantic is more + * strictly enforced as the maximum time a single read/write operation can + * take. Note, that as Jetty supports writes of memory mapped file buffers, + * then a write may take many 10s of seconds for large content written to a + * slow device. + *

    + * Previously, Jetty supported separate idle timeouts and IO operation timeouts, + * however the expense of changing the value of soTimeout was significant, so + * these timeouts were merged. With the advent of NIO, it may be possible to + * again differentiate these values (if there is demand). + * + * @param maxIdleTime The maxIdleTime to set. + */ + public void setMaxIdleTime(int maxIdleTime) + { + _maxIdleTime = maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the maxIdleTime. + */ + public int getLowResourceMaxIdleTime() + { + return _lowResourceMaxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @param maxIdleTime The maxIdleTime to set. + */ + public void setLowResourceMaxIdleTime(int maxIdleTime) + { + _lowResourceMaxIdleTime = maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the soLingerTime. + */ + public int getSoLingerTime() + { + return _soLingerTime; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the acceptQueueSize. + */ + public int getAcceptQueueSize() + { + return _acceptQueueSize; + } + + /* ------------------------------------------------------------ */ + /** + * @param acceptQueueSize The acceptQueueSize to set. + */ + public void setAcceptQueueSize(int acceptQueueSize) + { + _acceptQueueSize = acceptQueueSize; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the number of acceptor threads. + */ + public int getAcceptors() + { + return _acceptors; + } + + /* ------------------------------------------------------------ */ + /** + * @param acceptors The number of acceptor threads to set. + */ + public void setAcceptors(int acceptors) + { + _acceptors = acceptors; + } + + /* ------------------------------------------------------------ */ + /** + * @param soLingerTime The soLingerTime to set or -1 to disable. + */ + public void setSoLingerTime(int soLingerTime) + { + _soLingerTime = soLingerTime; + } + + /* ------------------------------------------------------------ */ + protected void doStart() throws Exception + { + if (_server==null) + throw new IllegalStateException("No server"); + + // open listener port + open(); + + super.doStart(); + + if (_threadPool==null) + _threadPool=_server.getThreadPool(); + if (_threadPool!=_server.getThreadPool() && (_threadPool instanceof LifeCycle)) + ((LifeCycle)_threadPool).start(); + + // Start selector thread + synchronized(this) + { + _acceptorThread=new Thread[getAcceptors()]; + + for (int i=0;i<_acceptorThread.length;i++) + { + if (!_threadPool.dispatch(new Acceptor(i))) + { + Log.warn("insufficient maxThreads configured for {}",this); + break; + } + } + } + + Log.info("Started {}",this); + } + + /* ------------------------------------------------------------ */ + protected void doStop() throws Exception + { + try{close();} catch(IOException e) {Log.warn(e);} + + if (_threadPool==_server.getThreadPool()) + _threadPool=null; + else if (_threadPool instanceof LifeCycle) + ((LifeCycle)_threadPool).stop(); + + super.doStop(); + + Thread[] acceptors=null; + synchronized(this) + { + acceptors=_acceptorThread; + _acceptorThread=null; + } + if (acceptors != null) + { + for (int i=0;i= 0) + socket.setSoTimeout(_maxIdleTime); + if (_soLingerTime >= 0) + socket.setSoLinger(true, _soLingerTime/1000); + else + socket.setSoLinger(false, 0); + } + catch (Exception e) + { + Log.ignore(e); + } + } + + + /* ------------------------------------------------------------ */ + public void customize(EndPoint endpoint, Request request) + throws IOException + { + if (isForwarded()) + checkForwardedHeaders(endpoint, request); + } + + /* ------------------------------------------------------------ */ + protected void checkForwardedHeaders(EndPoint endpoint, Request request) + throws IOException + { + HttpFields httpFields = request.getConnection().getRequestFields(); + + // Retrieving headers from the request + String forwardedHost = getLeftMostValue(httpFields.getStringField(getForwardedHostHeader())); + String forwardedServer = getLeftMostValue(httpFields.getStringField(getForwardedServerHeader())); + String forwardedFor = getLeftMostValue(httpFields.getStringField(getForwardedForHeader())); + + if (_hostHeader!=null) + { + // Update host header + httpFields.put(HttpHeaders.HOST_BUFFER, _hostHeader); + request.setServerName(null); + request.setServerPort(-1); + request.getServerName(); + } + else if (forwardedHost != null) + { + // Update host header + httpFields.put(HttpHeaders.HOST_BUFFER, forwardedHost); + request.setServerName(null); + request.setServerPort(-1); + request.getServerName(); + } + + if (forwardedServer != null) + { + // Use provided server name + request.setServerName(forwardedServer); + } + + if (forwardedFor != null) + { + request.setRemoteAddr(forwardedFor); + InetAddress inetAddress = null; + + if (_useDNS) + { + try + { + inetAddress = InetAddress.getByName(forwardedFor); + } + catch (UnknownHostException e) + { + Log.ignore(e); + } + } + + request.setRemoteHost(inetAddress==null?forwardedFor:inetAddress.getHostName()); + } + } + + /* ------------------------------------------------------------ */ + protected String getLeftMostValue(String headerValue) { + if (headerValue == null) + return null; + + int commaIndex = headerValue.indexOf(','); + + if (commaIndex == -1) + { + // Single value + return headerValue; + } + + // The left-most value is the farthest downstream client + return headerValue.substring(0, commaIndex); + } + + /* ------------------------------------------------------------ */ + public void persist(EndPoint endpoint) + throws IOException + { + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#getConfidentialPort() + */ + public int getConfidentialPort() + { + return _confidentialPort; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#getConfidentialScheme() + */ + public String getConfidentialScheme() + { + return _confidentialScheme; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server.Request) + */ + public boolean isIntegral(Request request) + { + return false; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#getConfidentialPort() + */ + public int getIntegralPort() + { + return _integralPort; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#getIntegralScheme() + */ + public String getIntegralScheme() + { + return _integralScheme; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server.Request) + */ + public boolean isConfidential(Request request) + { + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @param confidentialPort The confidentialPort to set. + */ + public void setConfidentialPort(int confidentialPort) + { + _confidentialPort = confidentialPort; + } + + /* ------------------------------------------------------------ */ + /** + * @param confidentialScheme The confidentialScheme to set. + */ + public void setConfidentialScheme(String confidentialScheme) + { + _confidentialScheme = confidentialScheme; + } + + /* ------------------------------------------------------------ */ + /** + * @param integralPort The integralPort to set. + */ + public void setIntegralPort(int integralPort) + { + _integralPort = integralPort; + } + + /* ------------------------------------------------------------ */ + /** + * @param integralScheme The integralScheme to set. + */ + public void setIntegralScheme(String integralScheme) + { + _integralScheme = integralScheme; + } + + /* ------------------------------------------------------------ */ + protected abstract void accept(int acceptorID) throws IOException, InterruptedException; + + /* ------------------------------------------------------------ */ + public void stopAccept(int acceptorID) throws Exception + { + } + + /* ------------------------------------------------------------ */ + public boolean getResolveNames() + { + return _useDNS; + } + + /* ------------------------------------------------------------ */ + public void setResolveNames(boolean resolve) + { + _useDNS=resolve; + } + + /* ------------------------------------------------------------ */ + /** + * Is reverse proxy handling on? + * @return true if this connector is checking the x-forwarded-for/host/server headers + */ + public boolean isForwarded() + { + return _forwarded; + } + + /* ------------------------------------------------------------ */ + /** + * Set reverse proxy handling + * @param check true if this connector is checking the x-forwarded-for/host/server headers + */ + public void setForwarded(boolean check) + { + if (check) + Log.debug(this+" is forwarded"); + _forwarded=check; + } + + /* ------------------------------------------------------------ */ + public String getHostHeader() + { + return _hostHeader; + } + + /* ------------------------------------------------------------ */ + /** + * Set a forced valued for the host header to control what is returned + * by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}. + * This value is only used if {@link #isForwarded()} is true. + * @param hostHeader The value of the host header to force. + */ + public void setHostHeader(String hostHeader) + { + _hostHeader=hostHeader; + } + + /* ------------------------------------------------------------ */ + public String getForwardedHostHeader() + { + return _forwardedHostHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedHostHeader The header name for forwarded hosts (default x-forwarded-host) + */ + public void setForwardedHostHeader(String forwardedHostHeader) + { + _forwardedHostHeader=forwardedHostHeader; + } + + /* ------------------------------------------------------------ */ + public String getForwardedServerHeader() + { + return _forwardedServerHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedHostHeader The header name for forwarded server (default x-forwarded-server) + */ + public void setForwardedServerHeader(String forwardedServerHeader) + { + _forwardedServerHeader=forwardedServerHeader; + } + + /* ------------------------------------------------------------ */ + public String getForwardedForHeader() + { + return _forwardedForHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedHostHeader The header name for forwarded for (default x-forwarded-for) + */ + public void setForwardedForHeader(String forwardedRemoteAddressHeade) + { + _forwardedForHeader=forwardedRemoteAddressHeade; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + String name = this.getClass().getName(); + int dot = name.lastIndexOf('.'); + if (dot>0) + name=name.substring(dot+1); + + return name+"@"+(getHost()==null?"0.0.0.0":getHost())+":"+(getLocalPort()<=0?getPort():getLocalPort()); + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class Acceptor implements Runnable + { + int _acceptor=0; + + Acceptor(int id) + { + _acceptor=id; + } + + /* ------------------------------------------------------------ */ + public void run() + { + Thread current = Thread.currentThread(); + synchronized(AbstractConnector.this) + { + if (_acceptorThread==null) + return; + + _acceptorThread[_acceptor]=current; + } + String name =_acceptorThread[_acceptor].getName(); + current.setName(name+" - Acceptor"+_acceptor+" "+AbstractConnector.this); + int old_priority=current.getPriority(); + + try + { + current.setPriority(old_priority-_acceptorPriorityOffset); + while (isRunning() && getConnection()!=null) + { + try + { + accept(_acceptor); + } + catch(EofException e) + { + Log.ignore(e); + } + catch(IOException e) + { + Log.ignore(e); + } + catch(ThreadDeath e) + { + Log.warn(e); + throw e; + } + catch(Throwable e) + { + Log.warn(e); + } + } + } + finally + { + current.setPriority(old_priority); + current.setName(name); + try + { + if (_acceptor==0) + close(); + } + catch (IOException e) + { + Log.warn(e); + } + + synchronized(AbstractConnector.this) + { + if (_acceptorThread!=null) + _acceptorThread[_acceptor]=null; + } + } + } + } + + /* ------------------------------------------------------------ */ + public String getName() + { + if (_name==null) + _name= (getHost()==null?"0.0.0.0":getHost())+":"+(getLocalPort()<=0?getPort():getLocalPort()); + return _name; + } + + /* ------------------------------------------------------------ */ + public void setName(String name) + { + _name = name; + } + + + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of requests handled by this context + * since last call of statsReset(). If setStatsOn(false) then this + * is undefined. + */ + public int getRequests() {return _requests;} + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsDurationMin. + */ + public long getConnectionsDurationMin() + { + return _connectionsDurationMin; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsDurationTotal. + */ + public long getConnectionsDurationTotal() + { + return _connectionsDurationTotal; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsOpenMin. + */ + public int getConnectionsOpenMin() + { + return _connectionsOpenMin; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsRequestsMin. + */ + public int getConnectionsRequestsMin() + { + return _connectionsRequestsMin; + } + + + /* ------------------------------------------------------------ */ + /** + * @return Number of connections accepted by the server since + * statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnections() {return _connections;} + + /* ------------------------------------------------------------ */ + /** + * @return Number of connections currently open that were opened + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsOpen() {return _connectionsOpen;} + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of connections opened simultaneously + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsOpenMax() {return _connectionsOpenMax;} + + /* ------------------------------------------------------------ */ + /** + * @return Average duration in milliseconds of open connections + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getConnectionsDurationAve() {return _connections==0?0:(_connectionsDurationTotal/_connections);} + + /* ------------------------------------------------------------ */ + /** + * @return Maximum duration in milliseconds of an open connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getConnectionsDurationMax() {return _connectionsDurationMax;} + + /* ------------------------------------------------------------ */ + /** + * @return Average number of requests per connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsRequestsAve() {return _connections==0?0:(_requests/_connections);} + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of requests per connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsRequestsMax() {return _connectionsRequestsMax;} + + + + /* ------------------------------------------------------------ */ + /** Reset statistics. + */ + public void statsReset() + { + _statsStartedAt=_statsStartedAt==-1?-1:System.currentTimeMillis(); + + _connections=0; + + _connectionsOpenMin=_connectionsOpen; + _connectionsOpenMax=_connectionsOpen; + _connectionsOpen=0; + + _connectionsDurationMin=0; + _connectionsDurationMax=0; + _connectionsDurationTotal=0; + + _requests=0; + + _connectionsRequestsMin=0; + _connectionsRequestsMax=0; + } + + /* ------------------------------------------------------------ */ + public void setStatsOn(boolean on) + { + if (on && _statsStartedAt!=-1) + return; + Log.debug("Statistics on = "+on+" for "+this); + statsReset(); + _statsStartedAt=on?System.currentTimeMillis():-1; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if statistics collection is turned on. + */ + public boolean getStatsOn() + { + return _statsStartedAt!=-1; + } + + /* ------------------------------------------------------------ */ + /** + * @return Timestamp stats were started at. + */ + public long getStatsOnMs() + { + return (_statsStartedAt!=-1)?(System.currentTimeMillis()-_statsStartedAt):0; + } + + /* ------------------------------------------------------------ */ + protected void connectionOpened(HttpConnection connection) + { + if (_statsStartedAt==-1) + return; + synchronized(_statsLock) + { + _connectionsOpen++; + if (_connectionsOpen > _connectionsOpenMax) + _connectionsOpenMax=_connectionsOpen; + } + } + + /* ------------------------------------------------------------ */ + protected void connectionClosed(HttpConnection connection) + { + if (_statsStartedAt>=0) + { + long duration=System.currentTimeMillis()-connection.getTimeStamp(); + int requests=connection.getRequests(); + synchronized(_statsLock) + { + _requests+=requests; + _connections++; + _connectionsOpen--; + _connectionsDurationTotal+=duration; + if (_connectionsOpen<0) + _connectionsOpen=0; + if (_connectionsOpen<_connectionsOpenMin) + _connectionsOpenMin=_connectionsOpen; + if (_connectionsDurationMin==0 || duration<_connectionsDurationMin) + _connectionsDurationMin=duration; + if (duration>_connectionsDurationMax) + _connectionsDurationMax=duration; + if (_connectionsRequestsMin==0 || requests<_connectionsRequestsMin) + _connectionsRequestsMin=requests; + if (requests>_connectionsRequestsMax) + _connectionsRequestsMax=requests; + } + } + + if (connection!=null) + connection.destroy(); + } + + /* ------------------------------------------------------------ */ + /** + * @return the acceptorPriority + */ + public int getAcceptorPriorityOffset() + { + return _acceptorPriorityOffset; + } + + /* ------------------------------------------------------------ */ + /** + * Set the priority offset of the acceptor threads. The priority is adjusted by + * this amount (default 0) to either favour the acceptance of new threads and newly active + * connections or to favour the handling of already dispatched connections. + * @param offset the amount to alter the priority of the acceptor threads. + */ + public void setAcceptorPriorityOffset(int offset) + { + _acceptorPriorityOffset=offset; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if the the server socket will be opened in SO_REUSEADDR mode. + */ + public boolean getReuseAddress() + { + return _reuseAddress; + } + + /* ------------------------------------------------------------ */ + /** + * @param reuseAddress True if the the server socket will be opened in SO_REUSEADDR mode. + */ + public void setReuseAddress(boolean reuseAddress) + { + _reuseAddress=reuseAddress; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequest.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequest.java new file mode 100644 index 00000000000..4bb375d48d3 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequest.java @@ -0,0 +1,708 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.thread.Timeout; + +/* ------------------------------------------------------------ */ +/** Asyncrhonous Request. + * + * + * + */ +public class AsyncRequest implements AsyncContext +{ + // STATES: + private static final int __IDLE=0; // Idle request + private static final int __DISPATCHED=1; // Request dispatched to filter/servlet + private static final int __SUSPENDING=2; // Suspend called, but not yet returned to container + private static final int __REDISPATCHING=3;// resumed while dispatched + private static final int __SUSPENDED=4; // Suspended and parked + private static final int __UNSUSPENDING=5; // Has been scheduled + private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet + private static final int __COMPLETING=7; // complete while dispatched + private static final int __UNCOMPLETED=8; // Request is completable + private static final int __COMPLETE=9; // Request is complete + + // State table + // __HANDLE __UNHANDLE __SUSPEND __REDISPATCH + // IDLE */ { __DISPATCHED, __Illegal, __Illegal, __Illegal }, + // DISPATCHED */ { __Illegal, __UNCOMPLETED, __SUSPENDING, __Ignore }, + // SUSPENDING */ { __Illegal, __SUSPENDED, __Illegal,__REDISPATCHING }, + // REDISPATCHING */ { __Illegal, _REDISPATCHED, __Ignored, __Ignore }, + // COMPLETING */ { __Illegal, __UNCOMPLETED, __Illegal, __Illegal }, + // SUSPENDED */ { __REDISPATCHED, __Illegal, __Illegal, __UNSUSPENDING }, + // UNSUSPENDING */ { __REDISPATCHED, __Illegal, __Illegal, __Ignore }, + // REDISPATCHED */ { __Illegal, __UNCOMPLETED, __SUSPENDING, __Ignore }, + + + /* ------------------------------------------------------------ */ + protected HttpConnection _connection; + protected Object _listeners; + + /* ------------------------------------------------------------ */ + private int _state; + private boolean _initial; + private long _timeoutMs; + private AsyncEventState _event; + + /* ------------------------------------------------------------ */ + protected AsyncRequest() + { + _state=__IDLE; + _initial=true; + } + + /* ------------------------------------------------------------ */ + protected AsyncRequest(final HttpConnection connection) + { + this(); + if (connection!=null) + setConnection(connection); + } + + /* ------------------------------------------------------------ */ + protected void setConnection(final HttpConnection connection) + { + _connection=connection; + } + + /* ------------------------------------------------------------ */ + public void setAsyncTimeout(long ms) + { + _timeoutMs=ms; + } + + /* ------------------------------------------------------------ */ + public long getAsyncTimeout() + { + return _timeoutMs; + } + + /* ------------------------------------------------------------ */ + public AsyncEventState getAsyncEventState() + { + return _event; + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#isInitial() + */ + public boolean isInitial() + { + synchronized(this) + { + return _initial; + } + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#isSuspended() + */ + public boolean isSuspended() + { + synchronized(this) + { + switch(_state) + { + case __SUSPENDING: + case __REDISPATCHING: + case __COMPLETING: + case __SUSPENDED: + return true; + + default: + return false; + } + } + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return getStatusString(); + } + + /* ------------------------------------------------------------ */ + public String getStatusString() + { + synchronized (this) + { + return + ((_state==__IDLE)?"IDLE": + (_state==__DISPATCHED)?"DISPATCHED": + (_state==__SUSPENDING)?"SUSPENDING": + (_state==__SUSPENDED)?"SUSPENDED": + (_state==__REDISPATCHING)?"REDISPATCHING": + (_state==__UNSUSPENDING)?"UNSUSPENDING": + (_state==__REDISPATCHED)?"REDISPATCHED": + (_state==__COMPLETING)?"COMPLETING": + (_state==__UNCOMPLETED)?"UNCOMPLETED": + (_state==__COMPLETE)?"COMPLETE": + ("UNKNOWN?"+_state))+ + (_initial?",initial":""); + } + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#resume() + */ + protected boolean handling() + { + synchronized (this) + { + switch(_state) + { + case __DISPATCHED: + case __REDISPATCHED: + case __COMPLETE: + throw new IllegalStateException(this.getStatusString()); + + case __IDLE: + _initial=true; + _state=__DISPATCHED; + return true; + + case __SUSPENDING: + case __REDISPATCHING: + throw new IllegalStateException(this.getStatusString()); + + case __COMPLETING: + _state=__UNCOMPLETED; + return false; + + case __SUSPENDED: + cancelTimeout(); + case __UNSUSPENDING: + _state=__REDISPATCHED; + return true; + + default: + throw new IllegalStateException(""+_state); + } + } + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#suspend(long) + */ + protected void suspend(final ServletContext context, + final ServletRequest request, + final ServletResponse response) + { + synchronized (this) + { + _event=new AsyncEventState(context,request,response); + + switch(_state) + { + case __DISPATCHED: + case __REDISPATCHED: + _state=__SUSPENDING; + return; + + case __IDLE: + throw new IllegalStateException(this.getStatusString()); + + case __SUSPENDING: + case __REDISPATCHING: + return; + + case __COMPLETING: + case __SUSPENDED: + case __UNSUSPENDING: + case __COMPLETE: + throw new IllegalStateException(this.getStatusString()); + + default: + throw new IllegalStateException(""+_state); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Signal that the HttpConnection has finished handling the request. + * For blocking connectors, this call may block if the request has + * been suspended (startAsync called). + * @return true if handling is complete, false if the request should + * be handled again (eg because of a resume that happened before unhandle was called) + */ + protected boolean unhandle() + { + synchronized (this) + { + switch(_state) + { + case __REDISPATCHED: + case __DISPATCHED: + _state=__UNCOMPLETED; + return true; + + case __IDLE: + throw new IllegalStateException(this.getStatusString()); + + case __SUSPENDING: + _initial=false; + _state=__SUSPENDED; + scheduleTimeout(); // could block and change state. + if (_state==__SUSPENDED) + return true; + else if (_state==__COMPLETING) + { + _state=__UNCOMPLETED; + return true; + } + _initial=false; + _state=__REDISPATCHED; + return false; + + case __REDISPATCHING: + _initial=false; + _state=__REDISPATCHED; + return false; + + case __COMPLETING: + _initial=false; + _state=__UNCOMPLETED; + return true; + + case __SUSPENDED: + case __UNSUSPENDING: + default: + throw new IllegalStateException(this.getStatusString()); + } + } + } + + /* ------------------------------------------------------------ */ + public void dispatch() + { + boolean dispatch=false; + synchronized (this) + { + switch(_state) + { + case __REDISPATCHED: + case __DISPATCHED: + case __IDLE: + case __REDISPATCHING: + case __COMPLETING: + case __COMPLETE: + case __UNCOMPLETED: + return; + + case __SUSPENDING: + _state=__REDISPATCHING; + return; + + case __SUSPENDED: + dispatch=true; + _state=__UNSUSPENDING; + break; + + case __UNSUSPENDING: + return; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + + if (dispatch) + { + cancelTimeout(); + scheduleDispatch(); + } + } + + /* ------------------------------------------------------------ */ + protected void expired() + { + synchronized (this) + { + switch(_state) + { + case __SUSPENDING: + case __SUSPENDED: + break; + default: + return; + } + } + + if (_listeners!=null) + { + for(int i=0;i0 && wait>0) + { + try + { + this.wait(wait); + } + catch (InterruptedException e) + { + Log.ignore(e); + } + wait=expire_at-System.currentTimeMillis(); + } + + if (_timeoutMs>0 && wait<=0) + expired(); + } + } + else + _connection.scheduleTimeout(_event._timeout,_timeoutMs); + } + + /* ------------------------------------------------------------ */ + protected void cancelTimeout() + { + EndPoint endp=_connection.getEndPoint(); + if (endp.isBlocking()) + { + synchronized(this) + { + _timeoutMs=0; + this.notifyAll(); + } + } + else if (_event!=null) + _connection.cancelTimeout(_event._timeout); + } + + /* ------------------------------------------------------------ */ + public boolean isCompleting() + { + return _state==__COMPLETING; + } + + /* ------------------------------------------------------------ */ + boolean isUncompleted() + { + return _state==__UNCOMPLETED; + } + + /* ------------------------------------------------------------ */ + public boolean isComplete() + { + return _state==__COMPLETE; + } + + + /* ------------------------------------------------------------ */ + public boolean isAsyncStarted() + { + switch(_state) + { + case __SUSPENDING: + case __REDISPATCHING: + case __UNSUSPENDING: + case __SUSPENDED: + return true; + + default: + return false; + } + } + + + /* ------------------------------------------------------------ */ + public boolean isAsync() + { + switch(_state) + { + case __IDLE: + case __DISPATCHED: + return false; + + default: + return true; + } + } + + /* ------------------------------------------------------------ */ + public void dispatch(ServletContext context, String path) + { + _event._dispatchContext=context; + _event._path=path; + dispatch(); + } + + /* ------------------------------------------------------------ */ + public void dispatch(String path) + { + _event._path=path; + dispatch(); + } + + /* ------------------------------------------------------------ */ + public ServletRequest getRequest() + { + if (_event!=null) + return _event.getRequest(); + return _connection.getRequest(); + } + + /* ------------------------------------------------------------ */ + public ServletResponse getResponse() + { + if (_event!=null) + return _event.getResponse(); + return _connection.getResponse(); + } + + /* ------------------------------------------------------------ */ + public void start(Runnable run) + { + ((Context)_event.getServletContext()).getContextHandler().handle(run); + } + + /* ------------------------------------------------------------ */ + public boolean hasOriginalRequestAndResponse() + { + return (_event!=null && _event.getRequest()==_connection._request && _event.getResponse()==_connection._response); + } + + /* ------------------------------------------------------------ */ + public ContextHandler getContextHandler() + { + if (_event!=null) + return ((Context)_event.getServletContext()).getContextHandler(); + return null; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class AsyncEventState extends AsyncEvent + { + final Timeout.Task _timeout; + final ServletContext _suspendedContext; + ServletContext _dispatchContext; + String _path; + + public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response) + { + super(request,response); + _suspendedContext=context; + _timeout= new Timeout.Task() + { + public void expired() + { + AsyncRequest.this.expired(); + } + }; + } + + public ServletContext getSuspendedContext() + { + return _suspendedContext; + } + + public ServletContext getDispatchContext() + { + return _dispatchContext; + } + + public ServletContext getServletContext() + { + return _dispatchContext==null?_suspendedContext:_dispatchContext; + } + + public String getPath() + { + return _path; + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java new file mode 100644 index 00000000000..e5976995e57 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java @@ -0,0 +1,322 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.component.LifeCycle; + +/** HTTP Connector. + * Implementations of this interface provide connectors for the HTTP protocol. + * A connector receives requests (normally from a socket) and calls the + * handle method of the Handler object. These operations are performed using + * threads from the ThreadPool set on the connector. + * + * When a connector is registered with an instance of Server, then the server + * will set itself as both the ThreadPool and the Handler. Note that a connector + * can be used without a Server if a thread pool and handler are directly provided. + * + * + * + */ +public interface Connector extends LifeCycle, Buffers +{ + /* ------------------------------------------------------------ */ + /** + * @return the name of the connector. Defaults to the HostName:port + */ + String getName(); + + /* ------------------------------------------------------------ */ + /** + * Opens the connector + * @throws IOException + */ + void open() throws IOException; + + /* ------------------------------------------------------------ */ + void close() throws IOException; + + /* ------------------------------------------------------------ */ + void setServer(Server server); + + /* ------------------------------------------------------------ */ + Server getServer(); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the headerBufferSize. + */ + int getHeaderBufferSize(); + + /* ------------------------------------------------------------ */ + /** + * Set the size of the buffer to be used for request and response headers. + * An idle connection will at most have one buffer of this size allocated. + * @param headerBufferSize The headerBufferSize to set. + */ + void setHeaderBufferSize(int headerBufferSize); + + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestBufferSize. + */ + int getRequestBufferSize(); + + /* ------------------------------------------------------------ */ + /** + * Set the size of the content buffer for receiving requests. + * These buffers are only used for active connections that have + * requests with bodies that will not fit within the header buffer. + * @param requestBufferSize The requestBufferSize to set. + */ + void setRequestBufferSize(int requestBufferSize); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the responseBufferSize. + */ + int getResponseBufferSize(); + + /* ------------------------------------------------------------ */ + /** + * Set the size of the content buffer for sending responses. + * These buffers are only used for active connections that are sending + * responses with bodies that will not fit within the header buffer. + * @param responseBufferSize The responseBufferSize to set. + */ + void setResponseBufferSize(int responseBufferSize); + + + /* ------------------------------------------------------------ */ + /** + * @return The port to use when redirecting a request if a data constraint of integral is + * required. See {@link org.eclipse.jetty.server.server.security.Constraint#getDataConstraint()} + */ + int getIntegralPort(); + + /* ------------------------------------------------------------ */ + /** + * @return The schema to use when redirecting a request if a data constraint of integral is + * required. See {@link org.eclipse.jetty.server.server.security.Constraint#getDataConstraint()} + */ + String getIntegralScheme(); + + /* ------------------------------------------------------------ */ + /** + * @param request A request + * @return true if the request is integral. This normally means the https schema has been used. + */ + boolean isIntegral(Request request); + + /* ------------------------------------------------------------ */ + /** + * @return The port to use when redirecting a request if a data constraint of confidential is + * required. See {@link org.eclipse.jetty.server.server.security.Constraint#getDataConstraint()} + */ + int getConfidentialPort(); + + + /* ------------------------------------------------------------ */ + /** + * @return The schema to use when redirecting a request if a data constraint of confidential is + * required. See {@link org.eclipse.jetty.server.server.security.Constraint#getDataConstraint()} + */ + String getConfidentialScheme(); + + /* ------------------------------------------------------------ */ + /** + * @param request A request + * @return true if the request is confidential. This normally means the https schema has been used. + */ + boolean isConfidential(Request request); + + /* ------------------------------------------------------------ */ + /** Customize a request for an endpoint. + * Called on every request to allow customization of the request for + * the particular endpoint (eg security properties from a SSL connection). + * @param endpoint + * @param request + * @throws IOException + */ + void customize(EndPoint endpoint, Request request) throws IOException; + + /* ------------------------------------------------------------ */ + /** Persist an endpoint. + * Called after every request if the connection is to remain open. + * @param endpoint + * @param request + * @throws IOException + */ + void persist(EndPoint endpoint) throws IOException; + + /* ------------------------------------------------------------ */ + String getHost(); + + /* ------------------------------------------------------------ */ + void setHost(String hostname); + + /* ------------------------------------------------------------ */ + /** + * @param port The port fto listen of for connections or 0 if any available + * port may be used. + */ + void setPort(int port); + + /* ------------------------------------------------------------ */ + /** + * @return The configured port for the connector or 0 if any available + * port may be used. + */ + int getPort(); + + /* ------------------------------------------------------------ */ + /** + * @return The actual port the connector is listening on or -1 if there + * is no port or the connector is not open. + */ + int getLocalPort(); + + /* ------------------------------------------------------------ */ + int getMaxIdleTime(); + void setMaxIdleTime(int ms); + + /* ------------------------------------------------------------ */ + int getLowResourceMaxIdleTime(); + void setLowResourceMaxIdleTime(int ms); + + /* ------------------------------------------------------------ */ + /** + * @return the underlying socket, channel, buffer etc. for the connector. + */ + Object getConnection(); + + + /* ------------------------------------------------------------ */ + /** + * @return true if names resolution should be done. + */ + boolean getResolveNames(); + + + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of requests handled by this connector + * since last call of statsReset(). If setStatsOn(false) then this + * is undefined. + */ + public int getRequests(); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsDurationMin. + */ + public long getConnectionsDurationMin(); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsDurationTotal. + */ + public long getConnectionsDurationTotal(); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsOpenMin. + */ + public int getConnectionsOpenMin(); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsRequestsMin. + */ + public int getConnectionsRequestsMin(); + + + /* ------------------------------------------------------------ */ + /** + * @return Number of connections accepted by the server since + * statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnections() ; + + /* ------------------------------------------------------------ */ + /** + * @return Number of connections currently open that were opened + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsOpen() ; + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of connections opened simultaneously + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsOpenMax() ; + + /* ------------------------------------------------------------ */ + /** + * @return Average duration in milliseconds of open connections + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getConnectionsDurationAve() ; + + /* ------------------------------------------------------------ */ + /** + * @return Maximum duration in milliseconds of an open connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getConnectionsDurationMax(); + + /* ------------------------------------------------------------ */ + /** + * @return Average number of requests per connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsRequestsAve() ; + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of requests per connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsRequestsMax(); + + + + /* ------------------------------------------------------------ */ + /** Reset statistics. + */ + public void statsReset(); + + /* ------------------------------------------------------------ */ + public void setStatsOn(boolean on); + + /* ------------------------------------------------------------ */ + /** + * @return True if statistics collection is turned on. + */ + public boolean getStatsOn(); + + /* ------------------------------------------------------------ */ + /** + * @return Timestamp stats were started at. + */ + public long getStatsOnMs(); + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java new file mode 100644 index 00000000000..d3e98271ad8 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java @@ -0,0 +1,296 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +/** Cookie parser + *

    Optimized stateful cookie parser. Cookies fields are added with the + * {@link #addCookieField(String)} method and parsed on the next subsequent + * call to {@link #getCookies()}. + * If the added fields are identical to those last added (as strings), then the + * cookies are not re parsed. + * + * + */ +public class CookieCutter +{ + private static final byte STATE_DELIMITER = 1; + private static final byte STATE_NAME = 2; + private static final byte STATE_VALUE = 4; + private static final byte STATE_QUOTED_VALUE = 8; + private static final byte STATE_UNQUOTED_VALUE = 16; + + private Cookie[] _cookies; + private String[] _fields; + int _added=0; + boolean _dirty; + HttpServletRequest _request; + + public CookieCutter() + { + + } + + public CookieCutter(HttpServletRequest request) + { + _request = request; + } + + public Cookie[] getCookies() + { + if (_added>0) + { + if (!_dirty && _added==_fields.length) + { + // same cookies as last time! + _added=0; + return _cookies; + } + + parseFields(); + } + return _cookies; + } + + public void setCookies(Cookie[] cookies) + { + _dirty=false; + _added=0; + _cookies=cookies; + } + + public void reset() + { + _fields=null; + _cookies=null; + } + + public void addCookieField(String f) + { + if (!_dirty && + _fields!=null && + _fields.length>_added && + _fields[_added].equals(f)) + { + _added++; + return; + } + + if (_dirty) + { + _added++; + _fields=(String[])LazyList.addToArray(_fields,f,String.class); + } + else + { + _dirty=true; + if (_added>0) + { + String[] fields=new String[_added+1]; + System.arraycopy(_fields,0,fields,0,_added); + fields[_added++]=f; + _fields=fields; + } + else + { + _fields = new String[]{f}; + _added=1; + } + + } + } + + protected void parseFields() + { + Object cookies = null; + + int version = 0; + + // For each cookie field + for (int f=0;f<_added;f++) + { + String hdr = _fields[f]; + + // Parse the header + String name = null; + String value = null; + + Cookie cookie = null; + + byte state = STATE_NAME; + for (int i = 0, tokenstart = 0, length = hdr.length(); i < length; i++) + { + char c = hdr.charAt(i); + switch (c) + { + case ',': + case ';': + switch (state) + { + case STATE_DELIMITER: + state = STATE_NAME; + tokenstart = i + 1; + break; + case STATE_UNQUOTED_VALUE: + state = STATE_NAME; + value = hdr.substring(tokenstart, i).trim(); + if(_request!=null && _request.isRequestedSessionIdFromURL()) + value = URIUtil.decodePath(value); + tokenstart = i + 1; + break; + case STATE_NAME: + name = hdr.substring(tokenstart, i); + value = ""; + tokenstart = i + 1; + break; + case STATE_VALUE: + state = STATE_NAME; + value = ""; + tokenstart = i + 1; + break; + } + break; + case '=': + switch (state) + { + case STATE_NAME: + state = STATE_VALUE; + name = hdr.substring(tokenstart, i); + tokenstart = i + 1; + break; + case STATE_VALUE: + state = STATE_UNQUOTED_VALUE; + tokenstart = i; + break; + } + break; + case '"': + switch (state) + { + case STATE_VALUE: + state = STATE_QUOTED_VALUE; + tokenstart = i + 1; + break; + case STATE_QUOTED_VALUE: + state = STATE_DELIMITER; + value = hdr.substring(tokenstart, i); + break; + } + break; + case ' ': + case '\t': + break; + default: + switch (state) + { + case STATE_VALUE: + state = STATE_UNQUOTED_VALUE; + tokenstart = i; + break; + case STATE_DELIMITER: + state = STATE_NAME; + tokenstart = i; + break; + } + } + + if (i + 1 == length) + { + switch (state) + { + case STATE_UNQUOTED_VALUE: + value = hdr.substring(tokenstart).trim(); + if(_request!=null && _request.isRequestedSessionIdFromURL()) + value = URIUtil.decodePath(value); + break; + case STATE_NAME: + name = hdr.substring(tokenstart); + value = ""; + break; + case STATE_VALUE: + value = ""; + break; + } + } + + if (name != null && value != null) + { + name = name.trim(); + + try + { + if (name.startsWith("$")) + { + String lowercaseName = name.toLowerCase(); + if ("$path".equals(lowercaseName)) + { + cookie.setPath(value); + } + else if ("$domain".equals(lowercaseName)) + { + cookie.setDomain(value); + } + else if ("$version".equals(lowercaseName)) + { + version = Integer.parseInt(value); + } + } + else + { + cookie = new Cookie(name, value); + + if (version > 0) + { + cookie.setVersion(version); + } + + cookies = LazyList.add(cookies, cookie); + } + } + catch (Exception e) + { + Log.ignore(e); + } + + name = null; + value = null; + } + } + } + + int l = LazyList.size(cookies); + if (l>0) + { + if (_cookies != null && _cookies.length == l) + { + for (int i = 0; i < l; i++) + _cookies[i] = (Cookie) LazyList.get(cookies, i); + } + else + _cookies = (Cookie[]) LazyList.toArray(cookies,Cookie.class); + } + + _added=0; + _dirty=false; + + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java new file mode 100644 index 00000000000..50e16efbc77 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java @@ -0,0 +1,571 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; + +/* ------------------------------------------------------------ */ +/** Servlet RequestDispatcher. + * + * + */ +public class Dispatcher implements RequestDispatcher +{ + /** Dispatch include attribute names */ + public final static String __INCLUDE_PREFIX="javax.servlet.include."; + public final static String __INCLUDE_REQUEST_URI= INCLUDE_REQUEST_URI; + public final static String __INCLUDE_CONTEXT_PATH= INCLUDE_CONTEXT_PATH; + public final static String __INCLUDE_SERVLET_PATH= INCLUDE_SERVLET_PATH; + public final static String __INCLUDE_PATH_INFO= INCLUDE_PATH_INFO; + public final static String __INCLUDE_QUERY_STRING= INCLUDE_QUERY_STRING; + + /** Dispatch include attribute names */ + public final static String __FORWARD_PREFIX="javax.servlet.forward."; + public final static String __FORWARD_REQUEST_URI= FORWARD_REQUEST_URI; + public final static String __FORWARD_CONTEXT_PATH= FORWARD_CONTEXT_PATH; + public final static String __FORWARD_SERVLET_PATH= FORWARD_SERVLET_PATH; + public final static String __FORWARD_PATH_INFO= FORWARD_PATH_INFO; + public final static String __FORWARD_QUERY_STRING= FORWARD_QUERY_STRING; + + /** JSP attributes */ + public final static String __JSP_FILE="org.apache.catalina.jsp_file"; + + /* ------------------------------------------------------------ */ + private ContextHandler _contextHandler; + private String _uri; + private String _path; + private String _dQuery; + private String _named; + + /* ------------------------------------------------------------ */ + /** + * @param contextHandler + * @param uriInContext + * @param pathInContext + * @param query + */ + public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query) + { + _contextHandler=contextHandler; + _uri=uri; + _path=pathInContext; + _dQuery=query; + } + + + /* ------------------------------------------------------------ */ + /** Constructor. + * @param servletHandler + * @param name + */ + public Dispatcher(ContextHandler contextHandler,String name) + throws IllegalStateException + { + _contextHandler=contextHandler; + _named=name; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException + { + forward(request, response, DispatcherType.FORWARD); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException + { + forward(request, response, DispatcherType.ERROR); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException + { + Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + request.removeAttribute(__JSP_FILE); // TODO remove when glassfish 1044 is fixed + + // TODO - allow stream or writer???? + + DispatcherType old_type = base_request.getDispatcherType(); + Attributes old_attr=base_request.getAttributes(); + MultiMap old_params=base_request.getParameters(); + try + { + base_request.setDispatcherType(DispatcherType.INCLUDE); + base_request.getConnection().include(); + if (_named!=null) + _contextHandler.doHandle(_named, base_request,(HttpServletRequest)request, (HttpServletResponse)response); + else + { + String query=_dQuery; + + if (query!=null) + { + MultiMap parameters=new MultiMap(); + UrlEncoded.decodeTo(query,parameters,request.getCharacterEncoding()); + + if (old_params!=null && old_params.size()>0) + { + // Merge parameters. + Iterator iter = old_params.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + String name=(String)entry.getKey(); + Object values=entry.getValue(); + for (int i=0;i0) + { + // Merge parameters; new parameters of the same name take precedence. + Iterator iter = old_params.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + String name=(String)entry.getKey(); + + if (parameters.containsKey(name)) + { + rewrite_old_query = true; + } + else + { + Object values=entry.getValue(); + for (int i=0;i0) + { + if ( rewrite_old_query ) + { + StringBuilder overridden_query_string = new StringBuilder(); + MultiMap overridden_old_query = new MultiMap(); + UrlEncoded.decodeTo(old_query,overridden_old_query,request.getCharacterEncoding()); + + MultiMap overridden_new_query = new MultiMap(); + UrlEncoded.decodeTo(query,overridden_new_query,request.getCharacterEncoding()); + + Iterator iter = overridden_old_query.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + String name=(String)entry.getKey(); + if(!overridden_new_query.containsKey(name)) + { + Object values=entry.getValue(); + for (int i=0;i + * The contained handlers may be one (see @{link {@link org.eclipse.jetty.server.server.handler.HandlerWrapper}) + * or many (see {@link org.eclipse.jetty.server.server.handler.HandlerList} or {@link org.eclipse.jetty.server.server.handler.HandlerCollection}. + * + */ +public interface HandlerContainer extends LifeCycle +{ + public void addHandler(Handler handler); + public void removeHandler(Handler handler); + + public Handler[] getChildHandlers(); + public Handler[] getChildHandlersByClass(Class byclass); + public Handler getChildHandlerByClass(Class byclass); +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java new file mode 100644 index 00000000000..3d3584d5268 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -0,0 +1,1114 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.AbstractGenerator; +import org.eclipse.jetty.http.EncodedHttpURI; +import org.eclipse.jetty.http.Generator; +import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.Parser; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.thread.Timeout; + +/** + *

    A HttpConnection represents the connection of a HTTP client to the server + * and is created by an instance of a {@link Connector}. It's prime function is + * to associate {@link Request} and {@link Response} instances with a {@link EndPoint}. + *

    + *

    + * A connection is also the prime mechanism used by jetty to recycle objects without + * pooling. The {@link Request}, {@link Response}, {@link HttpParser}, {@link HttpGenerator} + * and {@link HttpFields} instances are all recycled for the duraction of + * a connection. Where appropriate, allocated buffers are also kept associated + * with the connection via the parser and/or generator. + *

    + * + * + * + * + */ +public class HttpConnection implements Connection +{ + private static int UNKNOWN = -2; + private static ThreadLocal __currentConnection = new ThreadLocal(); + + private final long _timeStamp=System.currentTimeMillis(); + private int _requests; + private volatile boolean _handling; + + protected final Connector _connector; + protected final EndPoint _endp; + protected final Server _server; + protected final HttpURI _uri; + + protected final Parser _parser; + protected final HttpFields _requestFields; + protected final Request _request; + protected ServletInputStream _in; + + protected final Generator _generator; + protected final HttpFields _responseFields; + protected final Response _response; + protected Output _out; + protected OutputWriter _writer; + protected PrintWriter _printWriter; + + int _include; + + private Object _associatedObject; // associated object + + private transient int _expect = UNKNOWN; + private transient int _version = UNKNOWN; + private transient boolean _head = false; + private transient boolean _host = false; + private transient boolean _delayedHandling=false; + + /* ------------------------------------------------------------ */ + public static HttpConnection getCurrentConnection() + { + return (HttpConnection) __currentConnection.get(); + } + + /* ------------------------------------------------------------ */ + protected static void setCurrentConnection(HttpConnection connection) + { + __currentConnection.set(connection); + } + + /* ------------------------------------------------------------ */ + /** Constructor + * + */ + public HttpConnection(Connector connector, EndPoint endpoint, Server server) + { + _uri = URIUtil.__CHARSET==StringUtil.__UTF8?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); + _connector = connector; + _endp = endpoint; + _parser = new HttpParser(_connector, endpoint, new RequestHandler(), _connector.getHeaderBufferSize(), _connector.getRequestBufferSize()); + _requestFields = new HttpFields(); + _responseFields = new HttpFields(); + _request = new Request(this); + _response = new Response(this); + _generator = new HttpGenerator(_connector, _endp, _connector.getHeaderBufferSize(), _connector.getResponseBufferSize()); + _generator.setSendServerVersion(server.getSendServerVersion()); + _server = server; + } + + protected HttpConnection(Connector connector, EndPoint endpoint, Server server, + Parser parser, Generator generator, Request request) + { + _uri = URIUtil.__CHARSET==StringUtil.__UTF8?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); + _connector = connector; + _endp = endpoint; + _parser = parser; + _requestFields = new HttpFields(); + _responseFields = new HttpFields(); + _request = request; + _response = new Response(this); + _generator = generator; + _generator.setSendServerVersion(server.getSendServerVersion()); + _server = server; + } + + /* ------------------------------------------------------------ */ + public void destroy() + { + synchronized(this) + { + while(_handling) + Thread.yield(); + + if (_parser!=null) + _parser.reset(true); + + if (_generator!=null) + _generator.reset(true); + + if (_requestFields!=null) + _requestFields.destroy(); + + if (_responseFields!=null) + _responseFields.destroy(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return the parser used by this connection + */ + public Parser getParser() + { + return _parser; + } + + /* ------------------------------------------------------------ */ + /** + * @return the number of requests handled by this connection + */ + public int getRequests() + { + return _requests; + } + + /* ------------------------------------------------------------ */ + /** + * @return The time this connection was established. + */ + public long getTimeStamp() + { + return _timeStamp; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the associatedObject. + */ + public Object getAssociatedObject() + { + return _associatedObject; + } + + /* ------------------------------------------------------------ */ + /** + * @param associatedObject The associatedObject to set. + */ + public void setAssociatedObject(Object associatedObject) + { + _associatedObject = associatedObject; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connector. + */ + public Connector getConnector() + { + return _connector; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestFields. + */ + public HttpFields getRequestFields() + { + return _requestFields; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the responseFields. + */ + public HttpFields getResponseFields() + { + return _responseFields; + } + + /* ------------------------------------------------------------ */ + /** + * @return The result of calling {@link #getConnector}.{@link Connector#isConfidential(Request) isCondidential}(request), or false + * if there is no connector. + */ + public boolean isConfidential(Request request) + { + if (_connector!=null) + return _connector.isConfidential(request); + return false; + } + + /* ------------------------------------------------------------ */ + /** + * Find out if the request is INTEGRAL security. + * @param request + * @return true if there is a {@link #getConnector() connector} and it considers request + * to be {@link Connector#isIntegral(Request) integral} + */ + public boolean isIntegral(Request request) + { + if (_connector!=null) + return _connector.isIntegral(request); + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @return The {@link EndPoint} for this connection. + */ + public EndPoint getEndPoint() + { + return _endp; + } + + /* ------------------------------------------------------------ */ + /** + * @return false (this method is not yet implemented) + */ + public boolean getResolveNames() + { + return _connector.getResolveNames(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the request. + */ + public Request getRequest() + { + return _request; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the response. + */ + public Response getResponse() + { + return _response; + } + + /* ------------------------------------------------------------ */ + /** + * @return The input stream for this connection. The stream will be created if it does not already exist. + */ + public ServletInputStream getInputStream() throws IOException + { + // If the client is expecting 100 CONTINUE, then send it now. + if (_expect == HttpHeaderValues.CONTINUE_ORDINAL) + { + if (((HttpParser)_parser).getHeaderBuffer()==null || ((HttpParser)_parser).getHeaderBuffer().length()<2) + { + _generator.setResponse(HttpStatus.CONTINUE_100, null); + _generator.completeHeader(null, true); + _generator.complete(); + _generator.reset(false); + } + _expect = UNKNOWN; + } + + if (_in == null) + _in = new HttpInput(((HttpParser)_parser),_connector.getMaxIdleTime()); + return _in; + } + + /* ------------------------------------------------------------ */ + /** + * @return The output stream for this connection. The stream will be created if it does not already exist. + */ + public ServletOutputStream getOutputStream() + { + if (_out == null) + _out = new Output(); + return _out; + } + + /* ------------------------------------------------------------ */ + /** + * @return A {@link PrintWriter} wrapping the {@link #getOutputStream output stream}. The writer is created if it + * does not already exist. + */ + public PrintWriter getPrintWriter(String encoding) + { + getOutputStream(); + if (_writer==null) + { + _writer=new OutputWriter(); + _printWriter=new PrintWriter(_writer) + { + /* ------------------------------------------------------------ */ + /* + * @see java.io.PrintWriter#close() + */ + public void close() + { + try + { + out.close(); + } + catch(IOException e) + { + Log.debug(e); + setError(); + } + } + + }; + } + _writer.setCharacterEncoding(encoding); + return _printWriter; + } + + /* ------------------------------------------------------------ */ + public boolean isResponseCommitted() + { + return _generator.isCommitted(); + } + + /* ------------------------------------------------------------ */ + public void handle() throws IOException + { + // Loop while more in buffer + boolean more_in_buffer =true; // assume true until proven otherwise + boolean progress=true; + + try + { + _handling=true; + setCurrentConnection(this); + + while (more_in_buffer) + { + try + { + if (_request._async.isAsync()) + { + Log.debug("resume request",_request); + if (!_request._async.isComplete()) + handleRequest(); + else if (!_parser.isComplete()) + progress|=_parser.parseAvailable()>0; + + if (_generator.isCommitted() && !_generator.isComplete()) + _generator.flushBuffer(); + if (_endp.isBufferingOutput()) + _endp.flush(); + } + else + { + // If we are not ended then parse available + if (!_parser.isComplete()) + progress|=_parser.parseAvailable()>0; + + // Do we have more generating to do? + // Loop here because some writes may take multiple steps and + // we need to flush them all before potentially blocking in the + // next loop. + while (_generator.isCommitted() && !_generator.isComplete()) + { + long written=_generator.flushBuffer(); + if (written<=0) + break; + progress=true; + if (_endp.isBufferingOutput()) + _endp.flush(); + } + + // Flush buffers + if (_endp.isBufferingOutput()) + { + _endp.flush(); + if (!_endp.isBufferingOutput()) + progress=true; + } + + if (!progress) + return; + progress=false; + } + } + catch (HttpException e) + { + if (Log.isDebugEnabled()) + { + Log.debug("uri="+_uri); + Log.debug("fields="+_requestFields); + Log.debug(e); + } + _generator.sendError(e.getStatus(), e.getReason(), null, true); + + _parser.reset(true); + _endp.close(); + throw e; + } + finally + { + more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); + + if (_parser.isComplete() && _generator.isComplete() && !_endp.isBufferingOutput()) + { + if (!_generator.isPersistent()) + { + _parser.reset(true); + more_in_buffer=false; + } + + reset(!more_in_buffer); + progress=true; + } + + if (_request.isAsyncStarted()) + { + Log.debug("return with suspended request"); + more_in_buffer=false; + } + else if (_generator.isCommitted() && !_generator.isComplete() && _endp instanceof SelectChannelEndPoint) // TODO remove SelectChannel dependency + ((SelectChannelEndPoint)_endp).setWritable(false); + } + } + } + finally + { + setCurrentConnection(null); + _handling=false; + } + } + + /* ------------------------------------------------------------ */ + public void scheduleTimeout(Timeout.Task task, long timeoutMs) + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + public void cancelTimeout(Timeout.Task task) + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + public void reset(boolean returnBuffers) + { + _parser.reset(returnBuffers); // TODO maybe only release when low on resources + _requestFields.clear(); + _request.recycle(); + + _generator.reset(returnBuffers); // TODO maybe only release when low on resources + _responseFields.clear(); + _response.recycle(); + + _uri.clear(); + } + + /* ------------------------------------------------------------ */ + protected void handleRequest() throws IOException + { + boolean handling=_server.isRunning() && _request._async.handling(); + boolean error = false; + + String threadName=null; + try + { + if (Log.isDebugEnabled()) + { + threadName=Thread.currentThread().getName(); + Thread.currentThread().setName(threadName+" - "+_uri); + } + + // Loop here to handle async request redispatches. + // The loop is controlled by the call to async.unhandle in the + // finally block below. If call is from a non-blocking connector, + // then the unhandle will return false only if an async dispatch has + // already happened when unhandle is called. For a blocking connector, + // the wait for the asynchronous dispatch or timeout actually happens + // within the call to unhandle(). + while (handling) + { + _request.setHandled(false); + try + { + String info=URIUtil.canonicalPath(_uri.getDecodedPath()); + if (info==null) + throw new HttpException(400); + _request.setPathInfo(info); + + if (_out!=null) + _out.reopen(); + + if (_request._async.isInitial()) + { + _request.setDispatcherType(DispatcherType.REQUEST); + _connector.customize(_endp, _request); + _server.handle(this); + } + else + { + _request.setDispatcherType(DispatcherType.ASYNC); + _server.handleAsync(this); + } + + } + catch (RetryRequest r) + { + Log.ignore(r); + } + catch (EofException e) + { + Log.ignore(e); + error=true; + } + catch (HttpException e) + { + Log.debug(e); + _request.setHandled(true); + _response.sendError(e.getStatus(), e.getReason()); + error=true; + } + catch (Exception e) + { + Log.warn(e); + _request.setHandled(true); + _generator.sendError(500, null, null, true); + error=true; + } + catch (Error e) + { + Log.warn(e); + _request.setHandled(true); + _generator.sendError(500, null, null, true); + error=true; + } + finally + { + handling = !_request._async.unhandle() && _server != null; + } + } + } + finally + { + if (threadName!=null) + Thread.currentThread().setName(threadName); + + if (_request._async.isUncompleted()) + { + _request._async.doComplete(); + + if (_expect == HttpHeaderValues.CONTINUE_ORDINAL) + { + // Continue not sent so don't parse any content + _expect = UNKNOWN; + if (_parser instanceof HttpParser) + ((HttpParser)_parser).setState(HttpParser.STATE_END); + } + + if(_endp.isOpen()) + { + if (_generator.isPersistent()) + _connector.persist(_endp); + + if (error) + _endp.close(); + else + { + if (!_response.isCommitted() && !_request.isHandled()) + _response.sendError(HttpServletResponse.SC_NOT_FOUND); + _response.complete(); + } + } + else + { + _response.complete(); + } + + _request.setHandled(true); + } + } + } + + /* ------------------------------------------------------------ */ + public void commitResponse(boolean last) throws IOException + { + if (!_generator.isCommitted()) + { + _generator.setResponse(_response.getStatus(), _response.getReason()); + _generator.completeHeader(_responseFields, last); + } + if (last) + _generator.complete(); + } + + /* ------------------------------------------------------------ */ + public void completeResponse() throws IOException + { + if (!_generator.isCommitted()) + { + _generator.setResponse(_response.getStatus(), _response.getReason()); + _generator.completeHeader(_responseFields, HttpGenerator.LAST); + } + + _generator.complete(); + } + + /* ------------------------------------------------------------ */ + public void flushResponse() throws IOException + { + try + { + commitResponse(HttpGenerator.MORE); + _generator.flushBuffer(); + } + catch(IOException e) + { + throw (e instanceof EofException) ? e:new EofException(e); + } + } + + /* ------------------------------------------------------------ */ + public Generator getGenerator() + { + return _generator; + } + + + /* ------------------------------------------------------------ */ + public boolean isIncluding() + { + return _include>0; + } + + /* ------------------------------------------------------------ */ + public void include() + { + _include++; + } + + /* ------------------------------------------------------------ */ + public void included() + { + _include--; + if (_out!=null) + _out.reopen(); + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return _generator.isIdle() && (_parser.isIdle() || _delayedHandling); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class RequestHandler extends HttpParser.EventHandler + { + private String _charset; + + /* + * + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startRequest(org.eclipse.io.Buffer, + * org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException + { + _host = false; + _expect = UNKNOWN; + _delayedHandling=false; + _charset=null; + + if(_request.getTimeStamp()==0) + _request.setTimeStamp(System.currentTimeMillis()); + _request.setMethod(method.toString()); + + try + { + _uri.parse(uri.array(), uri.getIndex(), uri.length()); + _request.setUri(_uri); + + if (version==null) + { + _request.setProtocol(HttpVersions.HTTP_0_9); + _version=HttpVersions.HTTP_0_9_ORDINAL; + } + else + { + version= HttpVersions.CACHE.get(version); + _version = HttpVersions.CACHE.getOrdinal(version); + if (_version <= 0) _version = HttpVersions.HTTP_1_0_ORDINAL; + _request.setProtocol(version.toString()); + } + + _head = method == HttpMethods.HEAD_BUFFER; // depends on method being decached. + } + catch (Exception e) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400,null,e); + } + } + + /* + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#parsedHeaderValue(org.eclipse.io.Buffer) + */ + public void parsedHeader(Buffer name, Buffer value) + { + int ho = HttpHeaders.CACHE.getOrdinal(name); + switch (ho) + { + case HttpHeaders.HOST_ORDINAL: + // TODO check if host matched a host in the URI. + _host = true; + break; + + case HttpHeaders.EXPECT_ORDINAL: + value = HttpHeaderValues.CACHE.lookup(value); + _expect = HttpHeaderValues.CACHE.getOrdinal(value); + break; + + case HttpHeaders.ACCEPT_ENCODING_ORDINAL: + case HttpHeaders.USER_AGENT_ORDINAL: + value = HttpHeaderValues.CACHE.lookup(value); + break; + + case HttpHeaders.CONTENT_TYPE_ORDINAL: + value = MimeTypes.CACHE.lookup(value); + _charset=MimeTypes.getCharsetFromContentType(value); + break; + + case HttpHeaders.CONNECTION_ORDINAL: + //looks rather clumsy, but the idea is to optimize for a single valued header + int ordinal = HttpHeaderValues.CACHE.getOrdinal(value); + switch(ordinal) + { + case -1: + { + String[] values = value.toString().split(","); + for (int i=0;values!=null && i 0) throw new IllegalStateException("!empty"); + + if (content instanceof HttpContent) + { + HttpContent c = (HttpContent) content; + Buffer contentType = c.getContentType(); + if (contentType != null && !_responseFields.containsKey(HttpHeaders.CONTENT_TYPE_BUFFER)) + { + String enc = _response.getSetCharacterEncoding(); + if(enc==null) + _responseFields.add(HttpHeaders.CONTENT_TYPE_BUFFER, contentType); + else + { + if(contentType instanceof CachedBuffer) + { + CachedBuffer content_type = ((CachedBuffer)contentType).getAssociate(enc); + if(content_type!=null) + _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, content_type); + else + { + _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, + contentType+";charset="+QuotedStringTokenizer.quote(enc,";= ")); + } + } + else + { + _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, + contentType+";charset="+QuotedStringTokenizer.quote(enc,";= ")); + } + } + } + if (c.getContentLength() > 0) + _responseFields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER, c.getContentLength()); + Buffer lm = c.getLastModified(); + long lml=c.getResource().lastModified(); + if (lm != null) + _responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm,lml); + else if (c.getResource()!=null) + { + if (lml!=-1) + _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, lml); + } + + content = c.getBuffer(); + if (content==null) + content=c.getInputStream(); + } + else if (content instanceof Resource) + { + resource=(Resource)content; + _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, resource.lastModified()); + content=resource.getInputStream(); + } + + + if (content instanceof Buffer) + { + _generator.addContent((Buffer) content, HttpGenerator.LAST); + commitResponse(HttpGenerator.LAST); + } + else if (content instanceof InputStream) + { + InputStream in = (InputStream)content; + + try + { + int max = _generator.prepareUncheckedAddContent(); + Buffer buffer = _generator.getUncheckedBuffer(); + + int len=buffer.readFrom(in,max); + + while (len>=0) + { + _generator.completeUncheckedAddContent(); + _out.flush(); + + max = _generator.prepareUncheckedAddContent(); + buffer = _generator.getUncheckedBuffer(); + len=buffer.readFrom(in,max); + } + _generator.completeUncheckedAddContent(); + _out.flush(); + } + finally + { + if (resource!=null) + resource.release(); + else + in.close(); + + } + } + else + throw new IllegalArgumentException("unknown content type?"); + + + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class OutputWriter extends HttpWriter + { + OutputWriter() + { + super(HttpConnection.this._out); + } + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java new file mode 100644 index 00000000000..d817e55bc8b --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -0,0 +1,62 @@ +// ======================================================================== +// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; + +import javax.servlet.ServletInputStream; + +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.io.Buffer; + +public class HttpInput extends ServletInputStream +{ + protected final HttpParser _parser; + protected final long _maxIdleTime; + + /* ------------------------------------------------------------ */ + public HttpInput(HttpParser parser, long maxIdleTime) + { + _parser=parser; + _maxIdleTime=maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.InputStream#read() + */ + public int read() throws IOException + { + int c=-1; + Buffer content=_parser.blockForContent(_maxIdleTime); + if (content!=null) + c= 0xff & content.get(); + return c; + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.InputStream#read(byte[], int, int) + */ + public int read(byte[] b, int off, int len) throws IOException + { + int l=-1; + Buffer content=_parser.blockForContent(_maxIdleTime); + if (content!=null) + l= content.get(b, off, len); + return l; + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOnlyCookie.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOnlyCookie.java new file mode 100644 index 00000000000..6d21c118071 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOnlyCookie.java @@ -0,0 +1,45 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import javax.servlet.http.Cookie; + +/* ------------------------------------------------------------ */ +/** HttpOnlyCookie. + * + *

    + * Implements {@link javax.servlet.Cookie} from the {@link javax.servlet} package. + *

    + * This derivation of javax.servlet.http.Cookie can be used to indicate + * that the microsoft httponly extension should be used. + * The addSetCookie method on HttpFields checks for this type. + * @deprecated use {@link javax.servlet.Cookie#setHttpOnly(boolean)} + * + * + */ +public class HttpOnlyCookie extends Cookie +{ + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + */ + public HttpOnlyCookie(String name, String value) + { + super(name, value); + setHttpOnly(true); + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java new file mode 100644 index 00000000000..75a0a53d188 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -0,0 +1,173 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.Writer; + +import javax.servlet.ServletOutputStream; + +import org.eclipse.jetty.http.AbstractGenerator; +import org.eclipse.jetty.http.Generator; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.ByteArrayOutputStream2; + +/** Output. + * + *

    + * Implements {@link javax.servlet.ServletOutputStream} from the {@link javax.servlet} package. + *

    + * A {@link ServletOutputStream} implementation that writes content + * to a {@link AbstractGenerator}. The class is designed to be reused + * and can be reopened after a close. + */ +public class HttpOutput extends ServletOutputStream +{ + protected final AbstractGenerator _generator; + protected final long _maxIdleTime; + protected final ByteArrayBuffer _buf = new ByteArrayBuffer(AbstractGenerator.NO_BYTES); + protected boolean _closed; + + // These are held here for reuse by Writer + String _characterEncoding; + Writer _converter; + char[] _chars; + ByteArrayOutputStream2 _bytes; + + + /* ------------------------------------------------------------ */ + public HttpOutput(AbstractGenerator generator, long maxIdleTime) + { + _generator=generator; + _maxIdleTime=maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.OutputStream#close() + */ + public void close() throws IOException + { + _closed=true; + } + + /* ------------------------------------------------------------ */ + public void reopen() + { + _closed=false; + } + + /* ------------------------------------------------------------ */ + public void flush() throws IOException + { + _generator.flush(_maxIdleTime); + } + + /* ------------------------------------------------------------ */ + public void write(byte[] b, int off, int len) throws IOException + { + _buf.wrap(b, off, len); + write(_buf); + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.OutputStream#write(byte[]) + */ + public void write(byte[] b) throws IOException + { + _buf.wrap(b); + write(_buf); + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.OutputStream#write(int) + */ + public void write(int b) throws IOException + { + if (_closed) + throw new IOException("Closed"); + if (!_generator.isOpen()) + throw new EofException(); + + // Block until we can add _content. + while (_generator.isBufferFull()) + { + _generator.blockForOutput(_maxIdleTime); + if (_closed) + throw new IOException("Closed"); + if (!_generator.isOpen()) + throw new EofException(); + } + + // Add the _content + if (_generator.addContent((byte)b)) + // Buffers are full so flush. + flush(); + + if (_generator.isContentWritten()) + { + flush(); + close(); + } + } + + /* ------------------------------------------------------------ */ + private void write(Buffer buffer) throws IOException + { + if (_closed) + throw new IOException("Closed"); + if (!_generator.isOpen()) + throw new EofException(); + + // Block until we can add _content. + while (_generator.isBufferFull()) + { + _generator.blockForOutput(_maxIdleTime); + if (_closed) + throw new IOException("Closed"); + if (!_generator.isOpen()) + throw new EofException(); + } + + // Add the _content + _generator.addContent(buffer, Generator.MORE); + + // Have to flush and complete headers? + if (_generator.isBufferFull()) + flush(); + + if (_generator.isContentWritten()) + { + flush(); + close(); + } + + // Block until our buffer is free + while (buffer.length() > 0 && _generator.isOpen()) + _generator.blockForOutput(_maxIdleTime); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletOutputStream#print(java.lang.String) + */ + public void print(String s) throws IOException + { + write(s.getBytes()); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java new file mode 100644 index 00000000000..723802d91e0 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java @@ -0,0 +1,266 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import org.eclipse.jetty.http.AbstractGenerator; +import org.eclipse.jetty.util.ByteArrayOutputStream2; +import org.eclipse.jetty.util.StringUtil; + +/** OutputWriter. + * A writer that can wrap a {@link HttpOutput} stream and provide + * character encodings. + * + * The UTF-8 encoding is done by this class and no additional + * buffers or Writers are used. + * The UTF-8 code was inspired by http://javolution.org + */ +public class HttpWriter extends Writer +{ + private static final int WRITE_CONV = 0; + private static final int WRITE_ISO1 = 1; + private static final int WRITE_UTF8 = 2; + + HttpOutput _out; + AbstractGenerator _generator; + int _writeMode; + int _surrogate; + + /* ------------------------------------------------------------ */ + public HttpWriter(HttpOutput out) + { + _out=out; + _generator=_out._generator; + + } + + /* ------------------------------------------------------------ */ + public void setCharacterEncoding(String encoding) + { + if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding)) + { + _writeMode = WRITE_ISO1; + } + else if (StringUtil.__UTF8.equalsIgnoreCase(encoding)) + { + _writeMode = WRITE_UTF8; + } + else + { + _writeMode = WRITE_CONV; + if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding)) + _out._converter = null; // Set lazily in getConverter() + } + + _out._characterEncoding = encoding; + if (_out._bytes==null) + _out._bytes = new ByteArrayOutputStream2(AbstractGenerator.MAX_OUTPUT_CHARS); + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + _out.close(); + } + + /* ------------------------------------------------------------ */ + public void flush() throws IOException + { + _out.flush(); + } + + /* ------------------------------------------------------------ */ + public void write (String s,int offset, int length) throws IOException + { + while (length > AbstractGenerator.MAX_OUTPUT_CHARS) + { + write(s, offset, AbstractGenerator.MAX_OUTPUT_CHARS); + offset += AbstractGenerator.MAX_OUTPUT_CHARS; + length -= AbstractGenerator.MAX_OUTPUT_CHARS; + } + + if (_out._chars==null) + { + _out._chars = new char[AbstractGenerator.MAX_OUTPUT_CHARS]; + } + char[] chars = _out._chars; + s.getChars(offset, offset + length, chars, 0); + write(chars, 0, length); + } + + /* ------------------------------------------------------------ */ + public void write (char[] s,int offset, int length) throws IOException + { + HttpOutput out = _out; + + while (length > 0) + { + out._bytes.reset(); + int chars = length>AbstractGenerator.MAX_OUTPUT_CHARS?AbstractGenerator.MAX_OUTPUT_CHARS:length; + + switch (_writeMode) + { + case WRITE_CONV: + { + Writer converter=getConverter(); + converter.write(s, offset, chars); + converter.flush(); + } + break; + + case WRITE_ISO1: + { + byte[] buffer=out._bytes.getBuf(); + int bytes=out._bytes.getCount(); + + if (chars>buffer.length-bytes) + chars=buffer.length-bytes; + + for (int i = 0; i < chars; i++) + { + int c = s[offset+i]; + buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255 + } + if (bytes>=0) + out._bytes.setCount(bytes); + + break; + } + + case WRITE_UTF8: + { + byte[] buffer=out._bytes.getBuf(); + int bytes=out._bytes.getCount(); + + if (bytes+chars>buffer.length) + chars=buffer.length-bytes; + + for (int i = 0; i < chars; i++) + { + int code = s[offset+i]; + + if ((code & 0xffffff80) == 0) + { + // 1b + buffer[bytes++]=(byte)(code); + } + else if((code&0xfffff800)==0) + { + // 2b + if (bytes+2>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xc0|(code>>6)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + + if (bytes+chars-i-1>buffer.length) + chars-=1; + } + else if((code&0xffff0000)==0) + { + // 3b + if (bytes+3>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xe0|(code>>12)); + buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + + if (bytes+chars-i-1>buffer.length) + chars-=2; + } + else if((code&0xff200000)==0) + { + // 4b + if (bytes+4>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xf0|(code>>18)); + buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + + if (bytes+chars-i-1>buffer.length) + chars-=3; + } + else if((code&0xf4000000)==0) + { + // 5b + if (bytes+5>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xf8|(code>>24)); + buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + + if (bytes+chars-i-1>buffer.length) + chars-=4; + } + else if((code&0x80000000)==0) + { + // 6b + if (bytes+6>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xfc|(code>>30)); + buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + + if (bytes+chars-i-1>buffer.length) + chars-=5; + } + else + { + buffer[bytes++]=(byte)('?'); + } + } + out._bytes.setCount(bytes); + break; + } + default: + throw new IllegalStateException(); + } + + out._bytes.writeTo(out); + length-=chars; + offset+=chars; + } + } + + /* ------------------------------------------------------------ */ + private Writer getConverter() throws IOException + { + if (_out._converter == null) + _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding); + return _out._converter; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java new file mode 100644 index 00000000000..e99850c1179 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java @@ -0,0 +1,211 @@ +// ======================================================================== +// Copyright (c) 2002-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.util.Enumeration; +import java.util.List; +import java.util.StringTokenizer; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Byte range inclusive of end points. + *
    + * 
    + *   parses the following types of byte ranges:
    + * 
    + *       bytes=100-499
    + *       bytes=-300
    + *       bytes=100-
    + *       bytes=1-2,2-3,6-,-2
    + *
    + *   given an entity length, converts range to string
    + * 
    + *       bytes 100-499/500
    + * 
    + * 
    + * + * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2 + * @version $version$ + * + */ +public class InclusiveByteRange +{ + long first = 0; + long last = 0; + + public InclusiveByteRange(long first, long last) + { + this.first = first; + this.last = last; + } + + public long getFirst() + { + return first; + } + + public long getLast() + { + return last; + } + + + + /* ------------------------------------------------------------ */ + /** + * @param headers Enumeration of Range header fields. + * @param size Size of the resource. + * @return LazyList of satisfiable ranges + */ + public static List satisfiableRanges(Enumeration headers,long size) + { + Object satRanges=null; + + // walk through all Range headers + headers: + while (headers.hasMoreElements()) + { + String header = (String) headers.nextElement(); + StringTokenizer tok = new StringTokenizer(header,"=,",false); + String t=null; + try + { + // read all byte ranges for this header + while (tok.hasMoreTokens()) + { + t=tok.nextToken().trim(); + + long first = -1; + long last = -1; + int d=t.indexOf('-'); + if (d<0 || t.indexOf("-",d+1)>=0) + { + if ("bytes".equals(t)) + continue; + Log.warn("Bad range format: {}",t); + continue headers; + } + else if (d==0) + { + if (d+1 last)) + continue headers; + + if (first=size) + return size-1; + return last; + } + + /* ------------------------------------------------------------ */ + public long getSize(long size) + { + return getLast(size)-getFirst(size)+1; + } + + + /* ------------------------------------------------------------ */ + public String toHeaderRangeString(long size) + { + StringBuilder sb = new StringBuilder(40); + sb.append("bytes "); + sb.append(getFirst(size)); + sb.append('-'); + sb.append(getLast(size)); + sb.append("/"); + sb.append(size); + return sb.toString(); + } + + /* ------------------------------------------------------------ */ + public static String to416HeaderRangeString(long size) + { + StringBuilder sb = new StringBuilder(40); + sb.append("bytes */"); + sb.append(size); + return sb.toString(); + } + + + /* ------------------------------------------------------------ */ + public String toString() + { + StringBuilder sb = new StringBuilder(60); + sb.append(Long.toString(first)); + sb.append(":"); + sb.append(Long.toString(last)); + return sb.toString(); + } + + +} + + + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java new file mode 100644 index 00000000000..1eed8de27d3 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java @@ -0,0 +1,221 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.ByteArrayEndPoint; +import org.eclipse.jetty.util.StringUtil; + +public class LocalConnector extends AbstractConnector +{ + ByteArrayEndPoint _endp; + ByteArrayBuffer _in; + ByteArrayBuffer _out; + + Server _server; + boolean _accepting; + boolean _keepOpen; + + public LocalConnector() + { + setPort(1); + } + + /* ------------------------------------------------------------ */ + public Object getConnection() + { + return _endp; + } + + + /* ------------------------------------------------------------ */ + public void setServer(Server server) + { + super.setServer(server); + this._server=server; + } + + /* ------------------------------------------------------------ */ + public void clear() + { + _in.clear(); + _out.clear(); + } + + /* ------------------------------------------------------------ */ + public void reopen() + { + _in.clear(); + _out.clear(); + _endp = new ByteArrayEndPoint(); + _endp.setIn(_in); + _endp.setOut(_out); + _endp.setGrowOutput(true); + _accepting=false; + } + + /* ------------------------------------------------------------ */ + public void doStart() + throws Exception + { + _in=new ByteArrayBuffer(8192); + _out=new ByteArrayBuffer(8192); + _endp = new ByteArrayEndPoint(); + _endp.setIn(_in); + _endp.setOut(_out); + _endp.setGrowOutput(true); + _accepting=false; + + super.doStart(); + } + + /* ------------------------------------------------------------ */ + public String getResponses(String requests) + throws Exception + { + return getResponses(requests,false); + } + + /* ------------------------------------------------------------ */ + public String getResponses(String requests, boolean keepOpen) + throws Exception + { + // System.out.println("\nREQUESTS :\n"+requests); + // System.out.flush(); + ByteArrayBuffer buf=new ByteArrayBuffer(requests,StringUtil.__ISO_8859_1); + if (_in.space()0) + connection.handle(); + } + finally + { + if (!_keepOpen) + { + connectionClosed(connection); + connection.destroy(); + connection=null; + } + synchronized (this) + { + _accepting=false; + this.notify(); + } + } + } + } + + + public void open() throws IOException + { + } + + public void close() throws IOException + { + } + + /* ------------------------------------------------------------------------------- */ + public int getLocalPort() + { + return -1; + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java new file mode 100644 index 00000000000..6a7587de84c --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java @@ -0,0 +1,513 @@ +// ======================================================================== +// Copyright (c) 1997-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Locale; +import java.util.TimeZone; + +import javax.servlet.http.Cookie; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.util.DateCache; +import org.eclipse.jetty.util.RolloverFileOutputStream; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.Utf8StringBuilder; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + +/** + * This {@link RequestLog} implementation outputs logs in the pseudo-standard + * NCSA common log format. Configuration options allow a choice between the + * standard Common Log Format (as used in the 3 log format) and the Combined Log + * Format (single log format). This log format can be output by most web + * servers, and almost all web log analysis software can understand these + * formats. + * + * + * + * + * @org.apache.xbean.XBean element="ncsaLog" + */ +public class NCSARequestLog extends AbstractLifeCycle implements RequestLog +{ + private String _filename; + private boolean _extended; + private boolean _append; + private int _retainDays; + private boolean _closeOut; + private boolean _preferProxiedForAddress; + private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z"; + private String _filenameDateFormat = null; + private Locale _logLocale = Locale.getDefault(); + private String _logTimeZone = "GMT"; + private String[] _ignorePaths; + private boolean _logLatency = false; + private boolean _logCookies = false; + private boolean _logServer = false; + + private transient OutputStream _out; + private transient OutputStream _fileOut; + private transient DateCache _logDateCache; + private transient PathMap _ignorePathMap; + private transient Writer _writer; + private transient ArrayList _buffers; + private transient char[] _copy; + + public NCSARequestLog() + { + _extended = true; + _append = true; + _retainDays = 31; + } + + /* ------------------------------------------------------------ */ + /** + * @param filename + * The filename for the request log. This may be in the + * format expected by {@link RolloverFileOutputStream} + */ + public NCSARequestLog(String filename) + { + _extended = true; + _append = true; + _retainDays = 31; + setFilename(filename); + } + + /* ------------------------------------------------------------ */ + /** + * @param filename + * The filename for the request log. This may be in the + * format expected by {@link RolloverFileOutputStream} + */ + public void setFilename(String filename) + { + if (filename != null) + { + filename = filename.trim(); + if (filename.length() == 0) + filename = null; + } + _filename = filename; + } + + public String getFilename() + { + return _filename; + } + + public String getDatedFilename() + { + if (_fileOut instanceof RolloverFileOutputStream) + return ((RolloverFileOutputStream)_fileOut).getDatedFilename(); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @param format + * Format for the timestamps in the log file. If not set, the + * pre-formated request timestamp is used. + */ + public void setLogDateFormat(String format) + { + _logDateFormat = format; + } + + public String getLogDateFormat() + { + return _logDateFormat; + } + + public void setLogLocale(Locale logLocale) + { + _logLocale = logLocale; + } + + public Locale getLogLocale() + { + return _logLocale; + } + + public void setLogTimeZone(String tz) + { + _logTimeZone = tz; + } + + public String getLogTimeZone() + { + return _logTimeZone; + } + + public void setRetainDays(int retainDays) + { + _retainDays = retainDays; + } + + public int getRetainDays() + { + return _retainDays; + } + + public void setExtended(boolean extended) + { + _extended = extended; + } + + public boolean isExtended() + { + return _extended; + } + + public void setAppend(boolean append) + { + _append = append; + } + + public boolean isAppend() + { + return _append; + } + + public void setIgnorePaths(String[] ignorePaths) + { + _ignorePaths = ignorePaths; + } + + public String[] getIgnorePaths() + { + return _ignorePaths; + } + + public void setLogCookies(boolean logCookies) + { + _logCookies = logCookies; + } + + public boolean getLogCookies() + { + return _logCookies; + } + + public boolean getLogServer() + { + return _logServer; + } + + public void setLogServer(boolean logServer) + { + _logServer = logServer; + } + + public void setLogLatency(boolean logLatency) + { + _logLatency = logLatency; + } + + public boolean getLogLatency() + { + return _logLatency; + } + + public void setPreferProxiedForAddress(boolean preferProxiedForAddress) + { + _preferProxiedForAddress = preferProxiedForAddress; + } + + /* ------------------------------------------------------------ */ + public void log(Request request, Response response) + { + if (!isStarted()) + return; + + try + { + if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null) + return; + + if (_fileOut == null) + return; + + Utf8StringBuilder u8buf; + StringBuilder buf; + synchronized(_writer) + { + int size=_buffers.size(); + u8buf = size==0?new Utf8StringBuilder(160):(Utf8StringBuilder)_buffers.remove(size-1); + buf = u8buf.getStringBuilder(); + } + + if (_logServer) + { + buf.append(request.getServerName()); + buf.append(' '); + } + + String addr = null; + if (_preferProxiedForAddress) + { + addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR); + } + + if (addr == null) + addr = request.getRemoteAddr(); + + buf.append(addr); + buf.append(" - "); + String user = request.getRemoteUser(); + buf.append((user == null)?" - ":user); + buf.append(" ["); + if (_logDateCache != null) + buf.append(_logDateCache.format(request.getTimeStamp())); + else + buf.append(request.getTimeStampBuffer().toString()); + + buf.append("] \""); + buf.append(request.getMethod()); + buf.append(' '); + + request.getUri().writeTo(u8buf); + + buf.append(' '); + buf.append(request.getProtocol()); + buf.append("\" "); + if (request.getAsyncRequest().isInitial()) + { + int status = response.getStatus(); + if (status <= 0) + status = 404; + buf.append((char)('0' + ((status / 100) % 10))); + buf.append((char)('0' + ((status / 10) % 10))); + buf.append((char)('0' + (status % 10))); + } + else + buf.append("Async"); + + long responseLength = response.getContentCount(); + if (responseLength >= 0) + { + buf.append(' '); + if (responseLength > 99999) + buf.append(responseLength); + else + { + if (responseLength > 9999) + buf.append((char)('0' + ((responseLength / 10000) % 10))); + if (responseLength > 999) + buf.append((char)('0' + ((responseLength / 1000) % 10))); + if (responseLength > 99) + buf.append((char)('0' + ((responseLength / 100) % 10))); + if (responseLength > 9) + buf.append((char)('0' + ((responseLength / 10) % 10))); + buf.append((char)('0' + (responseLength) % 10)); + } + buf.append(' '); + } + else + buf.append(" - "); + + if (!_extended && !_logCookies && !_logLatency) + { + synchronized(_writer) + { + buf.append(StringUtil.__LINE_SEPARATOR); + int l=buf.length(); + if (l>_copy.length) + l=_copy.length; + buf.getChars(0,l,_copy,0); + _writer.write(_copy,0,l); + _writer.flush(); + u8buf.reset(); + _buffers.add(u8buf); + } + } + else + { + synchronized(_writer) + { + int l=buf.length(); + if (l>_copy.length) + l=_copy.length; + buf.getChars(0,l,_copy,0); + _writer.write(_copy,0,l); + u8buf.reset(); + _buffers.add(u8buf); + + // TODO do outside synchronized scope + if (_extended) + logExtended(request, response, _writer); + + // TODO do outside synchronized scope + if (_logCookies) + { + Cookie[] cookies = request.getCookies(); + if (cookies == null || cookies.length == 0) + _writer.write(" -"); + else + { + _writer.write(" \""); + for (int i = 0; i < cookies.length; i++) + { + if (i != 0) + _writer.write(';'); + _writer.write(cookies[i].getName()); + _writer.write('='); + _writer.write(cookies[i].getValue()); + } + _writer.write('\"'); + } + } + + if (_logLatency) + { + _writer.write(' '); + _writer.write(TypeUtil.toString(System.currentTimeMillis() - request.getTimeStamp())); + } + + _writer.write(StringUtil.__LINE_SEPARATOR); + _writer.flush(); + } + } + } + catch (IOException e) + { + Log.warn(e); + } + + } + + /* ------------------------------------------------------------ */ + protected void logExtended(Request request, + Response response, + Writer writer) throws IOException + { + String referer = request.getHeader(HttpHeaders.REFERER); + if (referer == null) + writer.write("\"-\" "); + else + { + writer.write('"'); + writer.write(referer); + writer.write("\" "); + } + + String agent = request.getHeader(HttpHeaders.USER_AGENT); + if (agent == null) + writer.write("\"-\" "); + else + { + writer.write('"'); + writer.write(agent); + writer.write('"'); + } + } + + /* ------------------------------------------------------------ */ + protected void doStart() throws Exception + { + if (_logDateFormat != null) + { + _logDateCache = new DateCache(_logDateFormat,_logLocale); + _logDateCache.setTimeZoneID(_logTimeZone); + } + + if (_filename != null) + { + _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null); + _closeOut = true; + Log.info("Opened " + getDatedFilename()); + } + else + _fileOut = System.err; + + _out = _fileOut; + + if (_ignorePaths != null && _ignorePaths.length > 0) + { + _ignorePathMap = new PathMap(); + for (int i = 0; i < _ignorePaths.length; i++) + _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]); + } + else + _ignorePathMap = null; + + _writer = new OutputStreamWriter(_out); + _buffers = new ArrayList(); + _copy = new char[1024]; + super.doStart(); + } + + /* ------------------------------------------------------------ */ + protected void doStop() throws Exception + { + super.doStop(); + try + { + if (_writer != null) + _writer.flush(); + } + catch (IOException e) + { + Log.ignore(e); + } + if (_out != null && _closeOut) + try + { + _out.close(); + } + catch (IOException e) + { + Log.ignore(e); + } + + _out = null; + _fileOut = null; + _closeOut = false; + _logDateCache = null; + _writer = null; + _buffers = null; + _copy = null; + } + + /* ------------------------------------------------------------ */ + /** + * @return the log File Date Format + */ + public String getFilenameDateFormat() + { + return _filenameDateFormat; + } + + /* ------------------------------------------------------------ */ + /** + * Set the log file date format. + * + * @see {@link RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)} + * @param logFileDateFormat + * the logFileDateFormat to pass to + * {@link RolloverFileOutputStream} + */ + public void setFilenameDateFormat(String logFileDateFormat) + { + _filenameDateFormat = logFileDateFormat; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java new file mode 100644 index 00000000000..431ae744c84 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -0,0 +1,1866 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.security.auth.login.LoginException; +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.NIOBuffer; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.ajax.Continuation; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Jetty Request. + *

    + * Implements {@link javax.servlet.http.HttpServletRequest} from the {@link javax.servlet.http} package. + *

    + *

    + * The standard interface of mostly getters, + * is extended with setters so that the request is mutable by the handlers that it is + * passed to. This allows the request object to be as lightweight as possible and not + * actually implement any significant behaviour. For example

      + * + *
    • The {@link Request#getContextPath} method will return null, until the requeset has been + * passed to a {@link ContextHandler} which matches the {@link Request#getPathInfo} with a context + * path and calls {@link Request#setContextPath} as a result.
    • + * + *
    • the HTTP session methods + * will all return null sessions until such time as a request has been passed to + * a {@link org.eclipse.jetty.servlet.SessionHandler} which checks for session cookies + * and enables the ability to create new sessions.
    • + * + *
    • The {@link Request#getServletPath} method will return null until the request has been + * passed to a {@link org.eclipse.jetty.servlet.ServletHandler} and the pathInfo matched + * against the servlet URL patterns and {@link Request#setServletPath} called as a result.
    • + *
    + * + * A request instance is created for each {@link HttpConnection} accepted by the server + * and recycled for each HTTP request received via that connection. An effort is made + * to avoid reparsing headers and cookies that are likely to be the same for + * requests from the same connection. + * + * + * + */ +public class Request implements HttpServletRequest +{ + private static final String __ASYNC_FWD="org.eclipse.asyncfwd"; + private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault()); + private static final int __NONE=0, _STREAM=1, __READER=2; + + /* ------------------------------------------------------------ */ + public static Request getRequest(HttpServletRequest request) + { + if (request instanceof Request) + return (Request) request; + + return HttpConnection.getCurrentConnection().getRequest(); + } + protected final AsyncRequest _async = new AsyncRequest(); + private boolean _asyncSupported=true; + private Attributes _attributes; + private String _authType; + private MultiMap _baseParameters; + private String _characterEncoding; + protected HttpConnection _connection; + private ContextHandler.Context _context; + private String _contextPath; + private Continuation _continuation; + private CookieCutter _cookies; + private boolean _cookiesExtracted=false; + private DispatcherType _dispatcherType; + private boolean _dns=false; + private EndPoint _endp; + private boolean _handled =false; + private int _inputState=__NONE; + private String _method; + private MultiMap _parameters; + private boolean _paramsExtracted; + private String _pathInfo; + private int _port; + private String _protocol=HttpVersions.HTTP_1_1; + private String _queryEncoding; + private String _queryString; + private BufferedReader _reader; + private String _readerEncoding; + private String _remoteAddr; + private String _remoteHost; + private Object _requestAttributeListeners; + private String _requestedSessionId; + private boolean _requestedSessionIdFromCookie=false; + private Object _requestListeners; + private String _requestURI; + private Map _savedNewSessions; + private String _scheme=URIUtil.HTTP; + private String _serverName; + private String _servletName; + private String _servletPath; + private HttpSession _session; + private SessionManager _sessionManager; + private long _timeStamp; + private Buffer _timeStampBuffer; + private HttpURI _uri; + + private UserIdentity _userIdentity = UserIdentity.UNAUTHENTICATED_IDENTITY; + + /* ------------------------------------------------------------ */ + public Request() + { + } + + /* ------------------------------------------------------------ */ + public Request(HttpConnection connection) + { + setConnection(connection); + } + + /* ------------------------------------------------------------ */ + public void addAsyncListener(AsyncListener listener) + { + _async._listeners=LazyList.add(_async._listeners,listener); + } + + /* ------------------------------------------------------------ */ + public void addAsyncListener(final AsyncListener listener, ServletRequest servletRequest, ServletResponse servletResponse) + { + final AsyncEvent event = new AsyncEvent(servletRequest,servletResponse); + + _async._listeners=LazyList.add(_async._listeners,new AsyncListener() + { + public void onComplete(AsyncEvent ev) throws IOException + { + listener.onComplete(event); + } + + public void onTimeout(AsyncEvent ev) throws IOException + { + listener.onComplete(event); + } + }); + } + + /* ------------------------------------------------------------ */ + public void addEventListener(final EventListener listener) + { + if (listener instanceof ServletRequestAttributeListener) + _requestAttributeListeners= LazyList.add(_requestAttributeListeners, listener); + } + + /* ------------------------------------------------------------ */ + /* + * Extract Paramters from query string and/or form _content. + */ + private void extractParameters() + { + if (_baseParameters == null) + _baseParameters = new MultiMap(16); + + if (_paramsExtracted) + { + if (_parameters==null) + _parameters=_baseParameters; + return; + } + + _paramsExtracted = true; + + // Handle query string + if (_uri!=null && _uri.hasQuery()) + { + if (_queryEncoding==null) + _uri.decodeQueryTo(_baseParameters); + else + { + try + { + _uri.decodeQueryTo(_baseParameters,_queryEncoding); + + } + catch (UnsupportedEncodingException e) + { + if (Log.isDebugEnabled()) + Log.warn(e); + else + Log.warn(e.toString()); + } + } + + } + + // handle any _content. + String encoding = getCharacterEncoding(); + String content_type = getContentType(); + if (content_type != null && content_type.length() > 0) + { + content_type = HttpFields.valueParameters(content_type, null); + + if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && + (HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod()))) + { + int content_length = getContentLength(); + if (content_length != 0) + { + try + { + int maxFormContentSize=-1; + + if (_context!=null) + maxFormContentSize=_context.getContextHandler().getMaxFormContentSize(); + else + { + Integer size = (Integer)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); + if (size!=null) + maxFormContentSize =size.intValue(); + } + + if (content_length>maxFormContentSize && maxFormContentSize > 0) + { + throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize); + } + InputStream in = getInputStream(); + + // Add form params to query params + UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1); + } + catch (IOException e) + { + if (Log.isDebugEnabled()) + Log.warn(e); + else + Log.warn(e.toString()); + } + } + } + } + + if (_parameters==null) + _parameters=_baseParameters; + else if (_parameters!=_baseParameters) + { + // Merge parameters (needed if parameters extracted after a forward). + Iterator iter = _baseParameters.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + String name=(String)entry.getKey(); + Object values=entry.getValue(); + for (int i=0;inull if {@link #setContext} has not yet + * been called. + */ + public Context getContext() + { + return _context; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getContextPath() + */ + public String getContextPath() + { + return _contextPath; + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public Continuation getContinuation() + { + return _continuation; + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public Continuation getContinuation(boolean create) + { + if (_continuation==null && create) + _continuation=new Servlet3Continuation(this); + return _continuation; + } + + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getCookies() + */ + public Cookie[] getCookies() + { + if (_cookiesExtracted) + return _cookies==null?null:_cookies.getCookies(); + + // Handle no cookies + if (!_connection.getRequestFields().containsKey(HttpHeaders.COOKIE_BUFFER)) + { + _cookiesExtracted = true; + if (_cookies!=null) + _cookies.reset(); + return null; + } + + if (_cookies==null) + _cookies=new CookieCutter(this); + + Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.COOKIE_BUFFER); + while (enm.hasMoreElements()) + { + String c = (String)enm.nextElement(); + _cookies.addCookieField(c); + } + _cookiesExtracted=true; + + return _cookies.getCookies(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String) + */ + public long getDateHeader(String name) + { + return _connection.getRequestFields().getDateField(name); + } + + /* ------------------------------------------------------------ */ + public DispatcherType getDispatcherType() + { + return _dispatcherType; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String) + */ + public String getHeader(String name) + { + return _connection.getRequestFields().getStringField(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getHeaderNames() + */ + public Enumeration getHeaderNames() + { + return _connection.getRequestFields().getFieldNames(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String) + */ + public Enumeration getHeaders(String name) + { + Enumeration e = _connection.getRequestFields().getValues(name); + if (e==null) + return Collections.enumeration(Collections.EMPTY_LIST); + return e; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the inputState. + */ + public int getInputState() + { + return _inputState; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getInputStream() + */ + public ServletInputStream getInputStream() throws IOException + { + if (_inputState!=__NONE && _inputState!=_STREAM) + throw new IllegalStateException("READER"); + _inputState=_STREAM; + return _connection.getInputStream(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String) + */ + public int getIntHeader(String name) + { + return (int)_connection.getRequestFields().getLongField(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocalAddr() + */ + public String getLocalAddr() + { + return _endp==null?null:_endp.getLocalAddr(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocale() + */ + public Locale getLocale() + { + Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE, HttpFields.__separators); + + // handle no locale + if (enm == null || !enm.hasMoreElements()) + return Locale.getDefault(); + + // sort the list in quality order + List acceptLanguage = HttpFields.qualityList(enm); + if (acceptLanguage.size()==0) + return Locale.getDefault(); + + int size=acceptLanguage.size(); + + // convert to locals + for (int i=0; i -1) + { + country = language.substring(dash + 1).trim(); + language = language.substring(0,dash).trim(); + } + return new Locale(language,country); + } + + return Locale.getDefault(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocales() + */ + public Enumeration getLocales() + { + + Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE, HttpFields.__separators); + + // handle no locale + if (enm == null || !enm.hasMoreElements()) + return Collections.enumeration(__defaultLocale); + + // sort the list in quality order + List acceptLanguage = HttpFields.qualityList(enm); + + if (acceptLanguage.size()==0) + return + Collections.enumeration(__defaultLocale); + + Object langs = null; + int size=acceptLanguage.size(); + + // convert to locals + for (int i=0; i -1) + { + country = language.substring(dash + 1).trim(); + language = language.substring(0,dash).trim(); + } + langs=LazyList.ensureSize(langs,size); + langs=LazyList.add(langs,new Locale(language,country)); + } + + if (LazyList.size(langs)==0) + return Collections.enumeration(__defaultLocale); + + return Collections.enumeration(LazyList.getList(langs)); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocalName() + */ + public String getLocalName() + { + if (_dns) + return _endp==null?null:_endp.getLocalHost(); + return _endp==null?null:_endp.getLocalAddr(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocalPort() + */ + public int getLocalPort() + { + return _endp==null?0:_endp.getLocalPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getMethod() + */ + public String getMethod() + { + return _method; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameter(java.lang.String) + */ + public String getParameter(String name) + { + if (!_paramsExtracted) + extractParameters(); + return (String) _parameters.getValue(name, 0); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameterMap() + */ + public Map getParameterMap() + { + if (!_paramsExtracted) + extractParameters(); + + return Collections.unmodifiableMap(_parameters.toStringArrayMap()); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameterNames() + */ + public Enumeration getParameterNames() + { + if (!_paramsExtracted) + extractParameters(); + return Collections.enumeration(_parameters.keySet()); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the parameters. + */ + public MultiMap getParameters() + { + return _parameters; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String) + */ + public String[] getParameterValues(String name) + { + if (!_paramsExtracted) + extractParameters(); + List vals = _parameters.getValues(name); + if (vals==null) + return null; + return (String[])vals.toArray(new String[vals.size()]); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getPathInfo() + */ + public String getPathInfo() + { + return _pathInfo; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getPathTranslated() + */ + public String getPathTranslated() + { + if (_pathInfo==null || _context==null) + return null; + return _context.getRealPath(_pathInfo); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getProtocol() + */ + public String getProtocol() + { + return _protocol; + } + + /* ------------------------------------------------------------ */ + public String getQueryEncoding() + { + return _queryEncoding; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getQueryString() + */ + public String getQueryString() + { + if (_queryString==null && _uri!=null) + { + if (_queryEncoding==null) + _queryString=_uri.getQuery(); + else + _queryString=_uri.getQuery(_queryEncoding); + } + return _queryString; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getReader() + */ + public BufferedReader getReader() throws IOException + { + if (_inputState!=__NONE && _inputState!=__READER) + throw new IllegalStateException("STREAMED"); + + if (_inputState==__READER) + return _reader; + + String encoding=getCharacterEncoding(); + if (encoding==null) + encoding=StringUtil.__ISO_8859_1; + + if (_reader==null || !encoding.equalsIgnoreCase(_readerEncoding)) + { + final ServletInputStream in = getInputStream(); + _readerEncoding=encoding; + _reader=new BufferedReader(new InputStreamReader(in,encoding)) + { + public void close() throws IOException + { + in.close(); + } + }; + } + _inputState=__READER; + return _reader; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRealPath(java.lang.String) + */ + public String getRealPath(String path) + { + if (_context==null) + return null; + return _context.getRealPath(path); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRemoteAddr() + */ + public String getRemoteAddr() + { + if (_remoteAddr != null) + return _remoteAddr; + return _endp==null?null:_endp.getRemoteAddr(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRemoteHost() + */ + public String getRemoteHost() + { + if (_dns) + { + if (_remoteHost != null) + { + return _remoteHost; + } + return _endp==null?null:_endp.getRemoteHost(); + } + return getRemoteAddr(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRemotePort() + */ + public int getRemotePort() + { + return _endp==null?0:_endp.getRemotePort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getRemoteUser() + */ + public String getRemoteUser() + { + Principal p = getUserPrincipal(); + if (p==null) + return null; + return p.getName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String) + */ + public RequestDispatcher getRequestDispatcher(String path) + { + if (path == null || _context==null) + return null; + + // handle relative path + if (!path.startsWith("/")) + { + String relTo=URIUtil.addPaths(_servletPath,_pathInfo); + int slash=relTo.lastIndexOf("/"); + if (slash>1) + relTo=relTo.substring(0,slash+1); + else + relTo="/"; + path=URIUtil.addPaths(relTo,path); + } + + return _context.getRequestDispatcher(path); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId() + */ + public String getRequestedSessionId() + { + return _requestedSessionId; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getRequestURI() + */ + public String getRequestURI() + { + if (_requestURI==null && _uri!=null) + _requestURI=_uri.getPathAndParam(); + return _requestURI; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getRequestURL() + */ + public StringBuffer getRequestURL() + { + StringBuffer url = new StringBuffer(48); + synchronized (url) + { + String scheme = getScheme(); + int port = getServerPort(); + + url.append(scheme); + url.append("://"); + url.append(getServerName()); + if (_port>0 && + ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) || + (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443))) + { + url.append(':'); + url.append(_port); + } + + url.append(getRequestURI()); + return url; + } + } + + /* ------------------------------------------------------------ */ + public Response getResponse() + { + return _connection._response; + } + + /* ------------------------------------------------------------ */ + /** + * Reconstructs the URL the client used to make the request. The returned URL contains a + * protocol, server name, port number, and, but it does not include a path. + *

    + * Because this method returns a StringBuffer, not a string, you can modify the + * URL easily, for example, to append path and query parameters. + * + * This method is useful for creating redirect messages and for reporting errors. + * + * @return "scheme://host:port" + */ + public StringBuilder getRootURL() + { + StringBuilder url = new StringBuilder(48); + String scheme = getScheme(); + int port = getServerPort(); + + url.append(scheme); + url.append("://"); + url.append(getServerName()); + + if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443))) + { + url.append(':'); + url.append(port); + } + return url; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getScheme() + */ + public String getScheme() + { + return _scheme; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getServerName() + */ + public String getServerName() + { + // Return already determined host + if (_serverName != null) + return _serverName; + + // Return host from absolute URI + _serverName = _uri.getHost(); + _port = _uri.getPort(); + if (_serverName != null) + return _serverName; + + // Return host from header field + Buffer hostPort = _connection.getRequestFields().get(HttpHeaders.HOST_BUFFER); + if (hostPort!=null) + { + for (int i=hostPort.length();i-->0;) + { + if (hostPort.peek(hostPort.getIndex()+i)==':') + { + _serverName=BufferUtil.to8859_1_String(hostPort.peek(hostPort.getIndex(), i)); + _port=BufferUtil.toInt(hostPort.peek(hostPort.getIndex()+i+1, hostPort.length()-i-1)); + return _serverName; + } + } + if (_serverName==null || _port<0) + { + _serverName=BufferUtil.to8859_1_String(hostPort); + _port = 0; + } + + return _serverName; + } + + // Return host from connection + if (_connection != null) + { + _serverName = getLocalName(); + _port = getLocalPort(); + if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName)) + return _serverName; + } + + // Return the local host + try + { + _serverName = InetAddress.getLocalHost().getHostAddress(); + } + catch (java.net.UnknownHostException e) + { + Log.ignore(e); + } + return _serverName; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getServerPort() + */ + public int getServerPort() + { + if (_port<=0) + { + if (_serverName==null) + getServerName(); + + if (_port<=0) + { + if (_serverName!=null && _uri!=null) + _port = _uri.getPort(); + else + _port = _endp==null?0:_endp.getLocalPort(); + } + } + + if (_port<=0) + { + if (getScheme().equalsIgnoreCase(URIUtil.HTTPS)) + return 443; + return 80; + } + return _port; + } + + /* ------------------------------------------------------------ */ + public ServletContext getServletContext() + { + return _context; + } + + /* ------------------------------------------------------------ */ + /* + */ + public String getServletName() + { + return _servletName; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getServletPath() + */ + public String getServletPath() + { + if (_servletPath==null) + _servletPath=""; + return _servletPath; + } + + /* ------------------------------------------------------------ */ + public ServletResponse getServletResponse() + { + return _connection.getResponse(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getSession() + */ + public HttpSession getSession() + { + return getSession(true); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getSession(boolean) + */ + public HttpSession getSession(boolean create) + { + if (_sessionManager==null && create) + throw new IllegalStateException("No SessionHandler or SessionManager"); + + if (_session != null && _sessionManager!=null && _sessionManager.isValid(_session)) + return _session; + + _session=null; + + String id=getRequestedSessionId(); + + if (id != null && _sessionManager!=null) + { + _session=_sessionManager.getHttpSession(id); + if (_session == null && !create) + return null; + } + + if (_session == null && _sessionManager!=null && create ) + { + _session=_sessionManager.newHttpSession(this); + Cookie cookie=_sessionManager.getSessionCookie(_session,getContextPath(),isSecure()); + if (cookie!=null) + _connection.getResponse().addCookie(cookie); + } + + return _session; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionManager. + */ + public SessionManager getSessionManager() + { + return _sessionManager; + } + + + /* ------------------------------------------------------------ */ + /** + * Get Request TimeStamp + * + * @return The time that the request was received. + */ + public long getTimeStamp() + { + return _timeStamp; + } + + /* ------------------------------------------------------------ */ + /** + * Get Request TimeStamp + * + * @return The time that the request was received. + */ + public Buffer getTimeStampBuffer() + { + if (_timeStampBuffer == null && _timeStamp > 0) + _timeStampBuffer = HttpFields.__dateCache.formatBuffer(_timeStamp); + return _timeStampBuffer; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the uri. + */ + public HttpURI getUri() + { + return _uri; + } + + public UserIdentity getUserIdentity() + { + return _userIdentity; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getUserPrincipal() + */ + public Principal getUserPrincipal() + { + return _userIdentity.getUserPrincipal(); + } + + /* ------------------------------------------------------------ */ + public boolean isAsyncStarted() + { + return _async.isAsyncStarted(); + } + + /* ------------------------------------------------------------ */ + public boolean isAsyncSupported() + { + return _asyncSupported; + } + + /* ------------------------------------------------------------ */ + public boolean isHandled() + { + return _handled; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie() + */ + public boolean isRequestedSessionIdFromCookie() + { + return _requestedSessionId!=null && _requestedSessionIdFromCookie; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl() + */ + public boolean isRequestedSessionIdFromUrl() + { + return _requestedSessionId!=null && !_requestedSessionIdFromCookie; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL() + */ + public boolean isRequestedSessionIdFromURL() + { + return _requestedSessionId!=null && !_requestedSessionIdFromCookie; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid() + */ + public boolean isRequestedSessionIdValid() + { + if (_requestedSessionId==null) + return false; + + HttpSession session=getSession(false); + return (session==null?false:_sessionManager.getIdManager().getClusterId(_requestedSessionId).equals(_sessionManager.getClusterId(session))); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#isSecure() + */ + public boolean isSecure() + { + return _connection.isConfidential(this); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String) + */ + public boolean isUserInRole(String role) + { + return _userIdentity!=null && _userIdentity.isUserInRole(role); + } + + /* ------------------------------------------------------------ */ + public HttpSession recoverNewSession(Object key) + { + if (_savedNewSessions==null) + return null; + return (HttpSession) _savedNewSessions.get(key); + } + + /* ------------------------------------------------------------ */ + protected void recycle() + { + _authType=null; + _async.recycle(); + _asyncSupported=true; + _handled=false; + if (_context!=null) + throw new IllegalStateException("Request in context!"); + if(_attributes!=null) + _attributes.clearAttributes(); + _characterEncoding=null; + _queryEncoding=null; + _context=null; + _serverName=null; + _method=null; + _pathInfo=null; + _port=0; + _protocol=HttpVersions.HTTP_1_1; + _queryString=null; + _requestedSessionId=null; + _requestedSessionIdFromCookie=false; + _session=null; + _requestURI=null; + _scheme=URIUtil.HTTP; + _servletPath=null; + _timeStamp=0; + _timeStampBuffer=null; + _uri=null; + _userIdentity=UserIdentity.UNAUTHENTICATED_IDENTITY; + if (_baseParameters!=null) + _baseParameters.clear(); + _parameters=null; + _paramsExtracted=false; + _inputState=__NONE; + + _cookiesExtracted=false; + if (_savedNewSessions!=null) + _savedNewSessions.clear(); + _savedNewSessions=null; + if (_continuation!=null && _continuation.isPending()) + _continuation.reset(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + Object old_value=_attributes==null?null:_attributes.getAttribute(name); + + if (_attributes!=null) + _attributes.removeAttribute(name); + + if (old_value!=null) + { + if (_requestAttributeListeners!=null) + { + final ServletRequestAttributeEvent event = + new ServletRequestAttributeEvent(_context,this,name, old_value); + final int size=LazyList.size(_requestAttributeListeners); + for(int i=0;i(); + _savedNewSessions.put(key,session); + } + + /* ------------------------------------------------------------ */ + public void setAsyncSupported(boolean supported) + { + _asyncSupported=supported; + } + /* ------------------------------------------------------------ */ + public void setAsyncTimeout(long timeout) + { + _async.setAsyncTimeout(timeout); + } + /* ------------------------------------------------------------ */ + /* + * Set a request attribute. + * if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then + * the value is also passed in a call to {@link #setQueryEncoding}. + * + * if the attribute name is "org.eclipse.jetty.server.server.ResponseBuffer", then + * the response buffer is flushed with @{link #flushResponseBuffer} + * + * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object value) + { + Object old_value=_attributes==null?null:_attributes.getAttribute(name); + + if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name)) + setQueryEncoding(value==null?null:value.toString()); + else if("org.eclipse.jetty.server.sendContent".equals(name)) + { + try + { + ((HttpConnection.Output)getServletResponse().getOutputStream()).sendContent(value); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + else if("org.eclipse.jetty.server.ResponseBuffer".equals(name)) + { + try + { + ByteBuffer byteBuffer=(ByteBuffer)value; + synchronized (byteBuffer) + { + NIOBuffer buffer = byteBuffer.isDirect() + ?(NIOBuffer)new DirectNIOBuffer(byteBuffer,true) + :(NIOBuffer)new IndirectNIOBuffer(byteBuffer,true); + ((HttpConnection.Output)getServletResponse().getOutputStream()).sendResponse(buffer); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + if (_attributes==null) + _attributes=new AttributesMap(); + _attributes.setAttribute(name, value); + + if (_requestAttributeListeners!=null) + { + final ServletRequestAttributeEvent event = + new ServletRequestAttributeEvent(_context,this,name, old_value==null?value:old_value); + final int size=LazyList.size(_requestAttributeListeners); + for(int i=0;iRequestLog can be attached to a {@link org.eclipse.jetty.server.server.handler.RequestLogHandler} to enable logging of requests/responses. + * + * @see Server#setRequestLog + */ +public interface RequestLog extends LifeCycle +{ + public void log(Request request, Response response); +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java new file mode 100644 index 00000000000..09b3626015c --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java @@ -0,0 +1,555 @@ +// ======================================================================== +// Copyright (c) 2000-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; + + +/* ------------------------------------------------------------ */ +/** + * + */ +public class ResourceCache extends AbstractLifeCycle implements Serializable +{ + private int _maxCachedFileSize =1024*1024; + private int _maxCachedFiles=2048; + private int _maxCacheSize =16*1024*1024; + private MimeTypes _mimeTypes; + + protected transient Map _cache; + protected transient int _cachedSize; + protected transient int _cachedFiles; + protected transient Content _mostRecentlyUsed; + protected transient Content _leastRecentlyUsed; + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public ResourceCache(MimeTypes mimeTypes) + { + _mimeTypes=mimeTypes; + } + + /* ------------------------------------------------------------ */ + public int getCachedSize() + { + return _cachedSize; + } + + /* ------------------------------------------------------------ */ + public int getCachedFiles() + { + return _cachedFiles; + } + + + /* ------------------------------------------------------------ */ + public int getMaxCachedFileSize() + { + return _maxCachedFileSize; + } + + /* ------------------------------------------------------------ */ + public void setMaxCachedFileSize(int maxCachedFileSize) + { + _maxCachedFileSize = maxCachedFileSize; + flushCache(); + } + + /* ------------------------------------------------------------ */ + public int getMaxCacheSize() + { + return _maxCacheSize; + } + + /* ------------------------------------------------------------ */ + public void setMaxCacheSize(int maxCacheSize) + { + _maxCacheSize = maxCacheSize; + flushCache(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the maxCachedFiles. + */ + public int getMaxCachedFiles() + { + return _maxCachedFiles; + } + + /* ------------------------------------------------------------ */ + /** + * @param maxCachedFiles The maxCachedFiles to set. + */ + public void setMaxCachedFiles(int maxCachedFiles) + { + _maxCachedFiles = maxCachedFiles; + } + + /* ------------------------------------------------------------ */ + public void flushCache() + { + if (_cache!=null) + { + synchronized(this) + { + ArrayList values=new ArrayList(_cache.values()); + for (Content content : values) + content.invalidate(); + + _cache.clear(); + + _cachedSize=0; + _cachedFiles=0; + _mostRecentlyUsed=null; + _leastRecentlyUsed=null; + } + } + } + + /* ------------------------------------------------------------ */ + /** Get a Entry from the cache. + * Get either a valid entry object or create a new one if possible. + * + * @param pathInContext The key into the cache + * @param factory If no matching entry is found, this {@link ResourceFactory} will be used to create the {@link Resource} + * for the new enry that is created. + * @return The entry matching pathInContext, or a new entry if no matching entry was found + */ + public Content lookup(String pathInContext, ResourceFactory factory) + throws IOException + { + Content content=null; + + // Look up cache operations + synchronized(_cache) + { + // Look for it in the cache + content = (Content)_cache.get(pathInContext); + + if (content!=null && content.isValid()) + { + return content; + } + } + Resource resource=factory.getResource(pathInContext); + return load(pathInContext,resource); + } + + /* ------------------------------------------------------------ */ + public Content lookup(String pathInContext, Resource resource) + throws IOException + { + Content content=null; + + // Look up cache operations + synchronized(_cache) + { + // Look for it in the cache + content = (Content)_cache.get(pathInContext); + + if (content!=null && content.isValid()) + { + return content; + } + } + return load(pathInContext,resource); + } + + /* ------------------------------------------------------------ */ + private Content load(String pathInContext, Resource resource) + throws IOException + { + Content content=null; + if (resource!=null && resource.exists() && !resource.isDirectory()) + { + long len = resource.length(); + if (len>0 && len<_maxCachedFileSize && len<_maxCacheSize) + { + int must_be_smaller_than=_maxCacheSize-(int)len; + + synchronized(_cache) + { + // check the cache is not full of locked content before loading content + + while(_leastRecentlyUsed!=null && (_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles))) + _leastRecentlyUsed.invalidate(); + + if(_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles)) + return null; + } + + content = new Content(resource); + fill(content); + + synchronized(_cache) + { + // check that somebody else did not fill this spot. + Content content2 =(Content)_cache.get(pathInContext); + if (content2!=null) + { + content.release(); + return content2; + } + + while(_leastRecentlyUsed!=null && (_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles))) + _leastRecentlyUsed.invalidate(); + + if(_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles)) + return null; // this could waste an allocated File or DirectBuffer + + content.cache(pathInContext); + + return content; + } + } + } + + return null; + } + + /* ------------------------------------------------------------ */ + /** Remember a Resource Miss! + * @param pathInContext + * @param resource + * @throws IOException + */ + public void miss(String pathInContext, Resource resource) + throws IOException + { + synchronized(_cache) + { + while(_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles && _leastRecentlyUsed!=null) + _leastRecentlyUsed.invalidate(); + if (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles) + return; + + // check that somebody else did not fill this spot. + Miss miss = new Miss(resource); + Content content2 =(Content)_cache.get(pathInContext); + if (content2!=null) + { + miss.release(); + return; + } + + miss.cache(pathInContext); + } + } + + /* ------------------------------------------------------------ */ + public synchronized void doStart() + throws Exception + { + _cache=new HashMap(); + _cachedSize=0; + _cachedFiles=0; + } + + /* ------------------------------------------------------------ */ + /** Stop the context. + */ + public void doStop() + throws InterruptedException + { + flushCache(); + } + + /* ------------------------------------------------------------ */ + protected void fill(Content content) + throws IOException + { + try + { + InputStream in = content.getResource().getInputStream(); + int len=(int)content.getResource().length(); + Buffer buffer = new ByteArrayBuffer(len); + buffer.readFrom(in,len); + in.close(); + content.setBuffer(buffer); + } + finally + { + content.getResource().release(); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** MetaData associated with a context Resource. + */ + public class Content implements HttpContent + { + boolean _locked; + String _key; + Resource _resource; + long _lastModified; + Content _prev; + Content _next; + + Buffer _lastModifiedBytes; + Buffer _contentType; + Buffer _buffer; + + /* ------------------------------------------------------------ */ + Content(Resource resource) + { + _resource=resource; + + _next=this; + _prev=this; + _contentType=_mimeTypes.getMimeByExtension(_resource.toString()); + + _lastModified=resource.lastModified(); + } + + + /* ------------------------------------------------------------ */ + /** + * @return true if the content is locked in the cache + */ + public boolean isLocked() + { + return _locked; + } + + + /* ------------------------------------------------------------ */ + /** + * @param locked true if the content is locked in the cache + */ + public void setLocked(boolean locked) + { + synchronized (_cache) + { + if (_locked && !locked) + { + _locked = locked; + _next=_mostRecentlyUsed; + _mostRecentlyUsed=this; + if (_next!=null) + _next._prev=this; + _prev=null; + if (_leastRecentlyUsed==null) + _leastRecentlyUsed=this; + } + else if (!_locked && locked) + { + if (_prev!=null) + _prev._next=_next; + if (_next!=null) + _next._prev=_prev; + _next=_prev=null; + } + else + _locked = locked; + } + } + + + /* ------------------------------------------------------------ */ + void cache(String pathInContext) + { + _key=pathInContext; + + if (!_locked) + { + _next=_mostRecentlyUsed; + _mostRecentlyUsed=this; + if (_next!=null) + _next._prev=this; + _prev=null; + if (_leastRecentlyUsed==null) + _leastRecentlyUsed=this; + } + _cache.put(_key,this); + if (_buffer!=null) + _cachedSize+=_buffer.length(); + _cachedFiles++; + if (_lastModified!=-1) + _lastModifiedBytes=new ByteArrayBuffer(HttpFields.formatDate(_lastModified)); + } + + /* ------------------------------------------------------------ */ + public String getKey() + { + return _key; + } + + /* ------------------------------------------------------------ */ + public boolean isCached() + { + return _key!=null; + } + + /* ------------------------------------------------------------ */ + public Resource getResource() + { + return _resource; + } + + /* ------------------------------------------------------------ */ + boolean isValid() + { + if (_lastModified==_resource.lastModified()) + { + if (!_locked && _mostRecentlyUsed!=this) + { + Content tp = _prev; + Content tn = _next; + + _next=_mostRecentlyUsed; + _mostRecentlyUsed=this; + if (_next!=null) + _next._prev=this; + _prev=null; + + if (tp!=null) + tp._next=tn; + if (tn!=null) + tn._prev=tp; + + if (_leastRecentlyUsed==this && tp!=null) + _leastRecentlyUsed=tp; + } + return true; + } + + invalidate(); + return false; + } + + /* ------------------------------------------------------------ */ + public void invalidate() + { + synchronized(this) + { + // Invalidate it + _cache.remove(_key); + _key=null; + if (_buffer!=null) + _cachedSize=_cachedSize-(int)_buffer.length(); + _cachedFiles--; + + if (_mostRecentlyUsed==this) + _mostRecentlyUsed=_next; + else + _prev._next=_next; + + if (_leastRecentlyUsed==this) + _leastRecentlyUsed=_prev; + else + _next._prev=_prev; + + _prev=null; + _next=null; + if (_resource!=null) + _resource.release(); + _resource=null; + + } + } + + /* ------------------------------------------------------------ */ + public Buffer getLastModified() + { + return _lastModifiedBytes; + } + + /* ------------------------------------------------------------ */ + public Buffer getContentType() + { + return _contentType; + } + + /* ------------------------------------------------------------ */ + public void setContentType(Buffer type) + { + _contentType=type; + } + + /* ------------------------------------------------------------ */ + public void release() + { + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer() + { + if (_buffer==null) + return null; + return new View(_buffer); + } + + /* ------------------------------------------------------------ */ + public void setBuffer(Buffer buffer) + { + _buffer=buffer; + } + + /* ------------------------------------------------------------ */ + public long getContentLength() + { + if (_buffer==null) + return -1; + return _buffer.length(); + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() throws IOException + { + return _resource.getInputStream(); + } + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** MetaData associated with a context Resource. + */ + public class Miss extends Content + { + Miss(Resource resource) + { + super(resource); + } + + /* ------------------------------------------------------------ */ + boolean isValid() + { + if (_resource.exists()) + { + invalidate(); + return false; + } + return true; + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java new file mode 100644 index 00000000000..8fc497162f3 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -0,0 +1,1191 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Locale; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.http.Generator; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.util.ByteArrayISO8859Writer; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Response. + *

    + * Implements {@link javax.servlet.HttpServletResponse} from the {@link javax.servlet} package. + *

    + * + * + * + */ +public class Response implements HttpServletResponse +{ + public static final int + NONE=0, + STREAM=1, + WRITER=2; + + /** + * If a header name starts with this string, the header (stripped of the prefix) + * can be set during include using only {@link #setHeader(String, String)} or + * {@link #addHeader(String, String)}. + */ + public static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include."; + + private static PrintWriter __nullPrintWriter; + private static ServletOutputStream __nullServletOut; + + static + { + try{ + __nullPrintWriter = new PrintWriter(IO.getNullWriter()); + __nullServletOut = new NullOutput(); + } + catch (Exception e) + { + Log.warn(e); + } + } + + private HttpConnection _connection; + private int _status=SC_OK; + private String _reason; + private Locale _locale; + private String _mimeType; + private CachedBuffer _cachedMimeType; + private String _characterEncoding; + private boolean _explicitEncoding; + private String _contentType; + private int _outputState; + private PrintWriter _writer; + + /* ------------------------------------------------------------ */ + /** + * + */ + public Response(HttpConnection connection) + { + _connection=connection; + } + + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#reset() + */ + protected void recycle() + { + _status=SC_OK; + _reason=null; + _locale=null; + _mimeType=null; + _cachedMimeType=null; + _characterEncoding=null; + _explicitEncoding=false; + _contentType=null; + _outputState=NONE; + _writer=null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie) + */ + public void addCookie(Cookie cookie) + { + _connection.getResponseFields().addSetCookie(cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.getComment(), + cookie.getSecure(), + cookie.isHttpOnly(), + cookie.getVersion()); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#containsHeader(java.lang.String) + */ + public boolean containsHeader(String name) + { + return _connection.getResponseFields().containsKey(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String) + */ + public String encodeURL(String url) + { + Request request=_connection.getRequest(); + SessionManager sessionManager = request.getSessionManager(); + if (sessionManager==null) + return url; + String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix(); + if (sessionURLPrefix==null) + return url; + + // should not encode if cookies in evidence + if (url==null || request==null || request.isRequestedSessionIdFromCookie()) + { + int prefix=url.indexOf(sessionURLPrefix); + if (prefix!=-1) + { + int suffix=url.indexOf("?",prefix); + if (suffix<0) + suffix=url.indexOf("#",prefix); + + if (suffix<=prefix) + return url.substring(0,prefix); + return url.substring(0,prefix)+url.substring(suffix); + } + return url; + } + + // get session; + HttpSession session=request.getSession(false); + + // no session + if (session == null) + return url; + + + // invalid session + if (!sessionManager.isValid(session)) + return url; + + String id=sessionManager.getNodeId(session); + + + // TODO Check host and port are for this server + // Already encoded + int prefix=url.indexOf(sessionURLPrefix); + if (prefix!=-1) + { + int suffix=url.indexOf("?",prefix); + if (suffix<0) + suffix=url.indexOf("#",prefix); + + if (suffix<=prefix) + return url.substring(0,prefix+sessionURLPrefix.length())+id; + return url.substring(0,prefix+sessionURLPrefix.length())+id+ + url.substring(suffix); + } + + // edit the session + int suffix=url.indexOf('?'); + if (suffix<0) + suffix=url.indexOf('#'); + if (suffix<0) + return url+sessionURLPrefix+id; + return url.substring(0,suffix)+ + sessionURLPrefix+id+url.substring(suffix); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String) + */ + public String encodeRedirectURL(String url) + { + return encodeURL(url); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#encodeUrl(java.lang.String) + */ + public String encodeUrl(String url) + { + return encodeURL(url); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#encodeRedirectUrl(java.lang.String) + */ + public String encodeRedirectUrl(String url) + { + return encodeURL(url); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#sendError(int, java.lang.String) + */ + public void sendError(int code, String message) throws IOException + { + if (_connection.isIncluding()) + return; + + if (isCommitted()) + Log.warn("Committed before "+code+" "+message); + + resetBuffer(); + _characterEncoding=null; + setHeader(HttpHeaders.EXPIRES,null); + setHeader(HttpHeaders.LAST_MODIFIED,null); + setHeader(HttpHeaders.CACHE_CONTROL,null); + setHeader(HttpHeaders.CONTENT_TYPE,null); + setHeader(HttpHeaders.CONTENT_LENGTH,null); + + _outputState=NONE; + setStatus(code,message); + + if (message==null) + message=HttpStatus.getCode(code).getMessage(); + + // If we are allowed to have a body + if (code!=SC_NO_CONTENT && + code!=SC_NOT_MODIFIED && + code!=SC_PARTIAL_CONTENT && + code>=SC_OK) + { + Request request = _connection.getRequest(); + + ErrorHandler error_handler = null; + ContextHandler.Context context = request.getContext(); + if (context!=null) + error_handler=context.getContextHandler().getErrorHandler(); + if (error_handler!=null) + { + // TODO - probably should reset these after the request? + request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code)); + request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message); + request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI()); + request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName()); + + error_handler.handle(null,_connection.getRequest(),this); + } + else + { + setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); + setContentType(MimeTypes.TEXT_HTML_8859_1); + ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048); + if (message != null) + { + message= StringUtil.replace(message, "&", "&"); + message= StringUtil.replace(message, "<", "<"); + message= StringUtil.replace(message, ">", ">"); + } + String uri= request.getRequestURI(); + if (uri!=null) + { + uri= StringUtil.replace(uri, "&", "&"); + uri= StringUtil.replace(uri, "<", "<"); + uri= StringUtil.replace(uri, ">", ">"); + } + + writer.write("\n\n\n"); + writer.write("Error "); + writer.write(Integer.toString(code)); + writer.write(' '); + if (message==null) + message=HttpStatus.getCode(code).getMessage(); + writer.write(message); + writer.write("\n\n\n

    HTTP ERROR: "); + writer.write(Integer.toString(code)); + writer.write("

    \n

    Problem accessing "); + writer.write(uri); + writer.write(". Reason:\n

        ");
    +                writer.write(message);
    +                writer.write("
    "); + writer.write("

    \n
    Powered by Jetty://"); + + for (int i= 0; i < 20; i++) + writer.write("\n "); + writer.write("\n\n\n"); + + writer.flush(); + setContentLength(writer.size()); + writer.writeTo(getOutputStream()); + writer.destroy(); + } + } + else if (code!=SC_PARTIAL_CONTENT) + { + _connection.getRequestFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER); + _connection.getRequestFields().remove(HttpHeaders.CONTENT_LENGTH_BUFFER); + _characterEncoding=null; + _mimeType=null; + _cachedMimeType=null; + } + + complete(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#sendError(int) + */ + public void sendError(int sc) throws IOException + { + if (sc==102) + sendProcessing(); + else + sendError(sc,null); + } + + /* ------------------------------------------------------------ */ + /* Send a 102-Processing response. + * If the connection is a HTTP connection, the version is 1.1 and the + * request has a Expect header starting with 102, then a 102 response is + * sent. This indicates that the request still be processed and real response + * can still be sent. This method is called by sendError if it is passed 102. + * @see javax.servlet.http.HttpServletResponse#sendError(int) + */ + public void sendProcessing() throws IOException + { + Generator g = _connection.getGenerator(); + if (g instanceof HttpGenerator) + { + HttpGenerator generator = (HttpGenerator)g; + + String expect = _connection.getRequest().getHeader(HttpHeaders.EXPECT); + + if (expect!=null && expect.startsWith("102") && generator.getVersion()>=HttpVersions.HTTP_1_1_ORDINAL) + { + boolean was_persistent=generator.isPersistent(); + generator.setResponse(102,null); + generator.completeHeader(null,true); + generator.setPersistent(true); + generator.complete(); + generator.flushBuffer(); + generator.reset(false); + generator.setPersistent(was_persistent); + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String) + */ + public void sendRedirect(String location) throws IOException + { + if (_connection.isIncluding()) + return; + + if (location==null) + throw new IllegalArgumentException(); + + if (!URIUtil.hasScheme(location)) + { + StringBuilder buf = _connection.getRequest().getRootURL(); + if (location.startsWith("/")) + buf.append(URIUtil.canonicalPath(location)); + else + { + String path=_connection.getRequest().getRequestURI(); + String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path); + location=URIUtil.canonicalPath(URIUtil.addPaths(parent,location)); + if(location==null) + throw new IllegalStateException("path cannot be above root"); + if (!location.startsWith("/")) + buf.append('/'); + buf.append(location); + } + + location=buf.toString(); + } + resetBuffer(); + + setHeader(HttpHeaders.LOCATION,location); + setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + complete(); + + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long) + */ + public void setDateHeader(String name, long date) + { + if (!_connection.isIncluding()) + _connection.getResponseFields().putDateField(name, date); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long) + */ + public void addDateHeader(String name, long date) + { + if (!_connection.isIncluding()) + _connection.getResponseFields().addDateField(name, date); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String) + */ + public void setHeader(String name, String value) + { + if (_connection.isIncluding()) + { + if (name.startsWith(SET_INCLUDE_HEADER_PREFIX)) + name=name.substring(SET_INCLUDE_HEADER_PREFIX.length()); + else + return; + } + _connection.getResponseFields().put(name, value); + if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) + { + if (value==null) + _connection._generator.setContentLength(-1); + else + _connection._generator.setContentLength(Long.parseLong(value)); + } + } + + /* ------------------------------------------------------------ */ + /* + */ + public String getHeader(String name) + { + return _connection.getResponseFields().getStringField(name); + } + + /* ------------------------------------------------------------ */ + /* + */ + public Enumeration getHeaders(String name) + { + Enumeration e = _connection.getResponseFields().getValues(name); + if (e==null) + return Collections.enumeration(Collections.EMPTY_LIST); + return e; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String) + */ + public void addHeader(String name, String value) + { + if (_connection.isIncluding()) + { + if (name.startsWith(SET_INCLUDE_HEADER_PREFIX)) + name=name.substring(SET_INCLUDE_HEADER_PREFIX.length()); + else + return; + } + + _connection.getResponseFields().add(name, value); + if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) + _connection._generator.setContentLength(Long.parseLong(value)); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int) + */ + public void setIntHeader(String name, int value) + { + if (!_connection.isIncluding()) + { + _connection.getResponseFields().putLongField(name, value); + if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) + _connection._generator.setContentLength(value); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int) + */ + public void addIntHeader(String name, int value) + { + if (!_connection.isIncluding()) + { + _connection.getResponseFields().addLongField(name, value); + if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) + _connection._generator.setContentLength(value); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setStatus(int) + */ + public void setStatus(int sc) + { + setStatus(sc,null); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setStatus(int, java.lang.String) + */ + public void setStatus(int sc, String sm) + { + if (sc<=0) + throw new IllegalArgumentException(); + if (!_connection.isIncluding()) + { + _status=sc; + _reason=sm; + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getCharacterEncoding() + */ + public String getCharacterEncoding() + { + if (_characterEncoding==null) + _characterEncoding=StringUtil.__ISO_8859_1; + return _characterEncoding; + } + + /* ------------------------------------------------------------ */ + String getSetCharacterEncoding() + { + return _characterEncoding; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getContentType() + */ + public String getContentType() + { + return _contentType; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getOutputStream() + */ + public ServletOutputStream getOutputStream() throws IOException + { + if (_outputState!=NONE && _outputState!=STREAM) + throw new IllegalStateException("WRITER"); + + _outputState=STREAM; + return _connection.getOutputStream(); + } + + /* ------------------------------------------------------------ */ + public boolean isWriting() + { + return _outputState==WRITER; + } + + /* ------------------------------------------------------------ */ + public boolean isOutputing() + { + return _outputState!=NONE; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getWriter() + */ + public PrintWriter getWriter() throws IOException + { + if (_outputState!=NONE && _outputState!=WRITER) + throw new IllegalStateException("STREAM"); + + /* if there is no writer yet */ + if (_writer==null) + { + /* get encoding from Content-Type header */ + String encoding = _characterEncoding; + + if (encoding==null) + { + /* implementation of educated defaults */ + if(_mimeType!=null) + encoding = null; // TODO getHttpContext().getEncodingByMimeType(_mimeType); + + if (encoding==null) + encoding = StringUtil.__ISO_8859_1; + + setCharacterEncoding(encoding); + } + + /* construct Writer using correct encoding */ + _writer = _connection.getPrintWriter(encoding); + } + _outputState=WRITER; + return _writer; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setCharacterEncoding(java.lang.String) + */ + public void setCharacterEncoding(String encoding) + { + if (_connection.isIncluding()) + return; + + if (this._outputState==0 && !isCommitted()) + { + _explicitEncoding=true; + + if (encoding==null) + { + // Clear any encoding. + if (_characterEncoding!=null) + { + _characterEncoding=null; + if (_cachedMimeType!=null) + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType); + else + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_mimeType); + } + } + else + { + // No, so just add this one to the mimetype + _characterEncoding=encoding; + if (_contentType!=null) + { + int i0=_contentType.indexOf(';'); + if (i0<0) + { + _contentType=null; + if(_cachedMimeType!=null) + { + CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); + if (content_type!=null) + { + _contentType=content_type.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type); + } + } + + if (_contentType==null) + { + _contentType = _mimeType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + int i1=_contentType.indexOf("charset=",i0); + if (i1<0) + { + _contentType = _contentType+" charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + } + else + { + int i8=i1+8; + int i2=_contentType.indexOf(" ",i8); + if (i2<0) + _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quote(_characterEncoding,";= "); + else + _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quote(_characterEncoding,";= ")+_contentType.substring(i2); + } + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setContentLength(int) + */ + public void setContentLength(int len) + { + // Protect from setting after committed as default handling + // of a servlet HEAD request ALWAYS sets _content length, even + // if the getHandling committed the response! + if (isCommitted() || _connection.isIncluding()) + return; + _connection._generator.setContentLength(len); + if (len>=0) + { + _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len); + if (_connection._generator.isContentWritten()) + { + if (_outputState==WRITER) + _writer.close(); + else if (_outputState==STREAM) + { + try + { + getOutputStream().close(); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setContentLength(int) + */ + public void setLongContentLength(long len) + { + // Protect from setting after committed as default handling + // of a servlet HEAD request ALWAYS sets _content length, even + // if the getHandling committed the response! + if (isCommitted() || _connection.isIncluding()) + return; + _connection._generator.setContentLength(len); + _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setContentType(java.lang.String) + */ + public void setContentType(String contentType) + { + if (isCommitted() || _connection.isIncluding()) + return; + + // Yes this method is horribly complex.... but there are lots of special cases and + // as this method is called on every request, it is worth trying to save string creation. + // + + if (contentType==null) + { + if (_locale==null) + _characterEncoding=null; + _mimeType=null; + _cachedMimeType=null; + _contentType=null; + _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER); + } + else + { + // Look for encoding in contentType + int i0=contentType.indexOf(';'); + + if (i0>0) + { + // we have content type parameters + + // Extract params off mimetype + _mimeType=contentType.substring(0,i0).trim(); + _cachedMimeType=MimeTypes.CACHE.get(_mimeType); + + // Look for charset + int i1=contentType.indexOf("charset=",i0+1); + if (i1>=0) + { + _explicitEncoding=true; + int i8=i1+8; + int i2 = contentType.indexOf(' ',i8); + + if (_outputState==WRITER) + { + // strip the charset and ignore; + if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' ')) + { + if (_cachedMimeType!=null) + { + CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); + if (content_type!=null) + { + _contentType=content_type.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type); + } + else + { + _contentType=_mimeType+";charset="+_characterEncoding; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + _contentType=_mimeType+";charset="+_characterEncoding; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else if (i2<0) + { + _contentType=contentType.substring(0,i1)+" charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + else + { + _contentType=contentType.substring(0,i1)+contentType.substring(i2)+" charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' ')) + { + // The params are just the char encoding + _cachedMimeType=MimeTypes.CACHE.get(_mimeType); + _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8)); + + if (_cachedMimeType!=null) + { + CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); + if (content_type!=null) + { + _contentType=content_type.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type); + } + else + { + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else if (i2>0) + { + _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8,i2)); + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + else + { + _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8)); + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else // No encoding in the params. + { + _cachedMimeType=null; + _contentType=_characterEncoding==null?contentType:contentType+" charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else // No params at all + { + _mimeType=contentType; + _cachedMimeType=MimeTypes.CACHE.get(_mimeType); + + if (_characterEncoding!=null) + { + if (_cachedMimeType!=null) + { + CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); + if (content_type!=null) + { + _contentType=content_type.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type); + } + else + { + _contentType=_mimeType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + _contentType=contentType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else if (_cachedMimeType!=null) + { + _contentType=_cachedMimeType.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType); + } + else + { + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setBufferSize(int) + */ + public void setBufferSize(int size) + { + if (isCommitted() || getContentCount()>0) + throw new IllegalStateException("Committed or content written"); + _connection.getGenerator().increaseContentBufferSize(size); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getBufferSize() + */ + public int getBufferSize() + { + return _connection.getGenerator().getContentBufferSize(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#flushBuffer() + */ + public void flushBuffer() throws IOException + { + _connection.flushResponse(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#reset() + */ + public void reset() + { + fwdReset(); + _status=200; + _reason=null; + + HttpFields response_fields=_connection.getResponseFields(); + + response_fields.clear(); + String connection=_connection.getRequestFields().getStringField(HttpHeaders.CONNECTION_BUFFER); + if (connection!=null) + { + String[] values = connection.split(","); + for (int i=0;values!=null && i0) + { + _characterEncoding=charset; + + /* get current MIME type from Content-Type header */ + String type=getContentType(); + if (type!=null) + { + _characterEncoding=charset; + int semi=type.indexOf(';'); + if (semi<0) + { + _mimeType=type; + _contentType= type += ";charset="+charset; + } + else + { + _mimeType=type.substring(0,semi); + _contentType= _mimeType += ";charset="+charset; + } + + _cachedMimeType=MimeTypes.CACHE.get(_mimeType); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getLocale() + */ + public Locale getLocale() + { + if (_locale==null) + return Locale.getDefault(); + return _locale; + } + + /* ------------------------------------------------------------ */ + /** + * @return The HTTP status code that has been set for this request. This will be 200 + * ({@link HttpServletResponse#SC_OK}), unless explicitly set through one of the setStatus methods. + */ + public int getStatus() + { + return _status; + } + + /* ------------------------------------------------------------ */ + /** + * @return The reason associated with the current {@link #getStatus() status}. This will be null, + * unless one of the setStatus methods have been called. + */ + public String getReason() + { + return _reason; + } + + /* ------------------------------------------------------------ */ + /** + */ + public void complete() + throws IOException + { + _connection.completeResponse(); + } + + /* ------------------------------------------------------------- */ + /** + * @return the number of bytes actually written in response body + */ + public long getContentCount() + { + if (_connection==null || _connection.getGenerator()==null) + return -1; + return _connection.getGenerator().getContentWritten(); + } + + /* ------------------------------------------------------------ */ + public HttpFields getHttpFields() + { + return _connection.getResponseFields(); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return "HTTP/1.1 "+_status+" "+ (_reason==null?"":_reason) +System.getProperty("line.separator")+ + _connection.getResponseFields().toString(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private static class NullOutput extends ServletOutputStream + { + public void write(int b) throws IOException + { + } + + public void print(String s) throws IOException + { + } + + public void println(String s) throws IOException + { + } + + public void write(byte[] b, int off, int len) throws IOException + { + } + + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/RetryRequest.java b/jetty-server/src/main/java/org/eclipse/jetty/server/RetryRequest.java new file mode 100644 index 00000000000..e3546c376e3 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/RetryRequest.java @@ -0,0 +1,29 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +/* ------------------------------------------------------------ */ +/** Retry Request + * This is thrown by a non-blocking {@link Continuation} such as + * {@link SuspendableSelectChannelEndPoint}. While it + * extends ThreadDeath, it does not actually stop the thread calling it. + * It extends ThreadDeath so as to be an Error that will not be caught + * by most frameworks. + * + * + * + */ +public class RetryRequest extends ThreadDeath +{ +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java new file mode 100644 index 00000000000..2deaf4be132 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -0,0 +1,779 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import javax.servlet.AsyncContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiException; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.component.Container; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ThreadPool; + +/* ------------------------------------------------------------ */ +/** Jetty HTTP Servlet Server. + * This class is the main class for the Jetty HTTP Servlet server. + * It aggregates Connectors (HTTP request receivers) and request Handlers. + * The server is itself a handler and a ThreadPool. Connectors use the ThreadPool methods + * to run jobs that will eventually call the handle method. + * + * @org.apache.xbean.XBean description="Creates an embedded Jetty web server" + */ +public class Server extends HandlerWrapper implements Attributes +{ + private static ShutdownHookThread hookThread = new ShutdownHookThread(); + private static String _version = (Server.class.getPackage()!=null && Server.class.getPackage().getImplementationVersion()!=null) + ?Server.class.getPackage().getImplementationVersion() + :"7.0.x"; + + private ThreadPool _threadPool; + private Connector[] _connectors; + private Container _container=new Container(); + private SessionIdManager _sessionIdManager; + private boolean _sendServerVersion = true; //send Server: header + private boolean _sendDateHeader = false; //send Date: header + private AttributesMap _attributes = new AttributesMap(); + private List _dependentBeans=new ArrayList(); + private int _graceful=0; + + /* ------------------------------------------------------------ */ + public Server() + { + setServer(this); + } + + /* ------------------------------------------------------------ */ + /** Convenience constructor + * Creates server and a {@link SocketConnector} at the passed port. + */ + public Server(int port) + { + setServer(this); + + Connector connector=new SelectChannelConnector(); + connector.setPort(port); + setConnectors(new Connector[]{connector}); + } + + + /* ------------------------------------------------------------ */ + public static String getVersion() + { + return _version; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the container. + */ + public Container getContainer() + { + return _container; + } + + /* ------------------------------------------------------------ */ + public boolean getStopAtShutdown() + { + return hookThread.contains(this); + } + + /* ------------------------------------------------------------ */ + public void setStopAtShutdown(boolean stop) + { + if (stop) + hookThread.add(this); + else + hookThread.remove(this); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectors. + */ + public Connector[] getConnectors() + { + return _connectors; + } + + + /* ------------------------------------------------------------ */ + public void addConnector(Connector connector) + { + setConnectors((Connector[])LazyList.addToArray(getConnectors(), connector, Connector.class)); + } + + /* ------------------------------------------------------------ */ + /** + * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to + * remove a connector. + * @param connector The connector to remove. + */ + public void removeConnector(Connector connector) { + setConnectors((Connector[])LazyList.removeFromArray (getConnectors(), connector)); + } + + /* ------------------------------------------------------------ */ + /** Set the connectors for this server. + * Each connector has this server set as it's ThreadPool and its Handler. + * @param connectors The connectors to set. + */ + public void setConnectors(Connector[] connectors) + { + if (connectors!=null) + { + for (int i=0;i0) + { + if (_connectors!=null) + { + for (int i=_connectors.length;i-->0;) + { + Log.info("Graceful shutdown {}",_connectors[i]); + try{_connectors[i].close();}catch(Throwable e){mex.add(e);} + } + } + + Handler[] contexts = getChildHandlersByClass(Graceful.class); + for (int c=0;c0;) + try{_connectors[i].stop();}catch(Throwable e){mex.add(e);} + } + + try {super.doStop(); } catch(Throwable e) { mex.add(e);} + + if (_sessionIdManager!=null) + _sessionIdManager.stop(); + + try + { + if (_threadPool instanceof LifeCycle) + ((LifeCycle)_threadPool).stop(); + } + catch(Throwable e){mex.add(e);} + + if (!_dependentBeans.isEmpty()) + { + ListIterator itor = _dependentBeans.listIterator(_dependentBeans.size()); + while (itor.hasPrevious()) + { + try + { + Object o =itor.previous(); + if (o instanceof LifeCycle) + ((LifeCycle)o).stop(); + } + catch (Throwable e) {mex.add(e);} + } + } + + mex.ifExceptionThrow(); + } + + /* ------------------------------------------------------------ */ + /* Handle a request from a connection. + * Called to handle a request on the connection when either the header has been received, + * or after the entire request has been received (for short requests of known length), or + * on the dispatch of an async request. + */ + public void handle(HttpConnection connection) throws IOException, ServletException + { + final String target=connection.getRequest().getPathInfo(); + final HttpServletRequest request=connection.getRequest(); + final HttpServletResponse response=connection.getResponse(); + + if (Log.isDebugEnabled()) + { + Log.debug("REQUEST "+target+" on "+connection); + handle(target, request, response); + Log.debug("RESPONSE "+target+" "+connection.getResponse().getStatus()); + } + else + handle(target, request, response); + } + + /* ------------------------------------------------------------ */ + /* Handle a request from a connection. + * Called to handle a request on the connection when either the header has been received, + * or after the entire request has been received (for short requests of known length), or + * on the dispatch of an async request. + */ + public void handleAsync(HttpConnection connection) throws IOException, ServletException + { + final AsyncRequest async = connection.getRequest().getAsyncRequest(); + final AsyncRequest.AsyncEventState state = async.getAsyncEventState(); + + final Request base_request=connection.getRequest(); + final String path=state.getPath(); + if (path!=null) + { + // this is a dispatch with a path + base_request.setAttribute(AsyncContext.ASYNC_REQUEST_URI,base_request.getRequestURI()); + base_request.setAttribute(AsyncContext.ASYNC_QUERY_STRING,base_request.getQueryString()); + + base_request.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,state.getSuspendedContext().getContextPath()); + + final String contextPath=state.getServletContext().getContextPath(); + HttpURI uri = new HttpURI(URIUtil.addPaths(contextPath,path)); + base_request.setUri(uri); + base_request.setRequestURI(null); + base_request.setPathInfo(base_request.getRequestURI()); + base_request.setQueryString(uri.getQuery()); + } + + final String target=base_request.getPathInfo(); + final HttpServletRequest request=(HttpServletRequest)async.getRequest(); + final HttpServletResponse response=(HttpServletResponse)async.getResponse(); + + if (Log.isDebugEnabled()) + { + Log.debug("REQUEST "+target+" on "+connection); + handle(target, request, response); + Log.debug("RESPONSE "+target+" "+connection.getResponse().getStatus()); + } + else + handle(target, request, response); + } + + + + /* ------------------------------------------------------------ */ + public void join() throws InterruptedException + { + getThreadPool().join(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionIdManager. + */ + public SessionIdManager getSessionIdManager() + { + return _sessionIdManager; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * @param sessionIdManager The sessionIdManager to set. + */ + public void setSessionIdManager(SessionIdManager sessionIdManager) + { + _container.update(this,_sessionIdManager,sessionIdManager, "sessionIdManager",true); + _sessionIdManager = sessionIdManager; + } + + /* ------------------------------------------------------------ */ + public void setSendServerVersion (boolean sendServerVersion) + { + _sendServerVersion = sendServerVersion; + } + + /* ------------------------------------------------------------ */ + public boolean getSendServerVersion() + { + return _sendServerVersion; + } + + /* ------------------------------------------------------------ */ + /** + * @param sendDateHeader + */ + public void setSendDateHeader(boolean sendDateHeader) + { + _sendDateHeader = sendDateHeader; + } + + /* ------------------------------------------------------------ */ + public boolean getSendDateHeader() + { + return _sendDateHeader; + } + + + /* ------------------------------------------------------------ */ + /** + * Add a LifeCycle object to be started/stopped + * along with the Server. + * @deprecated Use {@link #addBean(LifeCycle)} + * @param c + */ + public void addLifeCycle (LifeCycle c) + { + addBean(c); + } + + /* ------------------------------------------------------------ */ + /** + * Add an associated bean. + * The bean will be added to the servers {@link Container} + * and if it is a {@link LifeCycle} instance, it will be + * started/stopped along with the Server. + * @param c + */ + public void addBean(Object o) + { + if (o == null) + return; + + if (!_dependentBeans.contains(o)) + { + _dependentBeans.add(o); + _container.addBean(o); + } + + try + { + if (isStarted() && o instanceof LifeCycle) + ((LifeCycle)o).start(); + } + catch (Exception e) + { + throw new RuntimeException (e); + } + } + + /* ------------------------------------------------------------ */ + /** Get dependent beans of a specific class + * @see #addBean(Object) + * @param clazz + * @return List of beans. + */ + public List getBeans(Class clazz) + { + ArrayList beans = new ArrayList(); + Iterator iter = _dependentBeans.iterator(); + while (iter.hasNext()) + { + Object o = iter.next(); + if (clazz.isInstance(o)) + beans.add((T)o); + } + return beans; + } + + /** + * Remove a LifeCycle object to be started/stopped + * along with the Server + * @deprecated Use {@link #removeBean(Object)} + */ + public void removeLifeCycle (LifeCycle c) + { + removeBean(c); + } + + /** + * Remove an associated bean. + */ + public void removeBean (Object o) + { + if (o == null) + return; + _dependentBeans.remove(o); + _container.removeBean(o); + } + + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * ShutdownHook thread for stopping all servers. + * + * Thread is hooked first time list of servers is changed. + */ + private static class ShutdownHookThread extends Thread + { + private boolean hooked = false; + private ArrayList servers = new ArrayList(); + + /** + * Hooks this thread for shutdown. + * + * @see java.lang.Runtime#addShutdownHook(java.lang.Thread) + */ + private void createShutdownHook() + { + if (!Boolean.getBoolean("JETTY_NO_SHUTDOWN_HOOK") && !hooked) + { + try + { + Method shutdownHook = java.lang.Runtime.class.getMethod("addShutdownHook", new Class[] + { java.lang.Thread.class}); + shutdownHook.invoke(Runtime.getRuntime(), new Object[] + { this}); + this.hooked = true; + } + catch (Exception e) + { + if (Log.isDebugEnabled()) + Log.debug("No shutdown hook in JVM ", e); + } + } + } + + /** + * Add Server to servers list. + */ + public boolean add(Server server) + { + createShutdownHook(); + return this.servers.add(server); + } + + /** + * Contains Server in servers list? + */ + public boolean contains(Server server) + { + return this.servers.contains(server); + } + + /** + * Append all Servers from Collection + */ + public boolean addAll(Collection c) + { + createShutdownHook(); + return this.servers.addAll(c); + } + + /** + * Clear list of Servers. + */ + public void clear() + { + createShutdownHook(); + this.servers.clear(); + } + + /** + * Remove Server from list. + */ + public boolean remove(Server server) + { + createShutdownHook(); + return this.servers.remove(server); + } + + /** + * Remove all Servers in Collection from list. + */ + public boolean removeAll(Collection c) + { + createShutdownHook(); + return this.servers.removeAll(c); + } + + /** + * Stop all Servers in list. + */ + public void run() + { + setName("Shutdown"); + Log.info("Shutdown hook executing"); + Iterator it = servers.iterator(); + while (it.hasNext()) + { + Server svr = (Server) it.next(); + if (svr == null) + continue; + try + { + svr.stop(); + } + catch (Exception e) + { + Log.warn(e); + } + Log.info("Shutdown hook complete"); + + // Try to avoid JVM crash + try + { + Thread.sleep(1000); + } + catch (Exception e) + { + Log.warn(e); + } + } + } + } + + + + + /* ------------------------------------------------------------ */ + /** + */ + public void addHandler(Handler handler) + { + if (getHandler() == null) + setHandler(handler); + else if (getHandler() instanceof HandlerCollection) + ((HandlerCollection)getHandler()).addHandler(handler); + else + { + HandlerCollection collection=new HandlerCollection(); + collection.setHandlers(new Handler[]{getHandler(),handler}); + setHandler(collection); + } + } + + /* ------------------------------------------------------------ */ + /** + */ + public void removeHandler(Handler handler) + { + if (getHandler() instanceof HandlerCollection) + ((HandlerCollection)getHandler()).removeHandler(handler); + } + + /* ------------------------------------------------------------ */ + /** + */ + public Handler[] getHandlers() + { + if (getHandler() instanceof HandlerCollection) + return ((HandlerCollection)getHandler()).getHandlers(); + + return null; + } + + /* ------------------------------------------------------------ */ + /** + */ + public void setHandlers(Handler[] handlers) + { + HandlerCollection collection; + if (getHandler() instanceof HandlerCollection) + collection=(HandlerCollection)getHandler(); + else + { + collection=new HandlerCollection(); + setHandler(collection); + } + + collection.setHandlers(handlers); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#clearAttributes() + */ + public void clearAttributes() + { + _attributes.clearAttributes(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _attributes.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#getAttributeNames() + */ + public Enumeration getAttributeNames() + { + return AttributesMap.getAttributeNamesCopy(_attributes); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + _attributes.removeAttribute(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object attribute) + { + _attributes.setAttribute(name, attribute); + } + + /* ------------------------------------------------------------ */ + /** + * @return the graceful + */ + public int getGracefulShutdown() + { + return _graceful; + } + + /* ------------------------------------------------------------ */ + /** + * Set graceful shutdown timeout. If set, the {@link #doStop()} method will not immediately stop the + * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted + * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests + * will be accepted, but existing requests can complete. The server will then wait the configured timeout + * before stopping. + * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server. + * + */ + public void setGracefulShutdown(int timeoutMS) + { + _graceful=timeoutMS; + } + + public String toString() + { + return this.getClass().getName()+"@"+Integer.toHexString(hashCode()); + } + + + /* ------------------------------------------------------------ */ + /* A handler that can be gracefully shutdown. + * Called by doStop if a {@link #setGracefulShutdown} period is set. + * TODO move this somewhere better + */ + public interface Graceful extends Handler + { + public void setShutdown(boolean shutdown); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Servlet3Continuation.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Servlet3Continuation.java new file mode 100644 index 00000000000..08703c229d3 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Servlet3Continuation.java @@ -0,0 +1,115 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; + +import org.eclipse.jetty.util.ajax.Continuation; + +public class Servlet3Continuation implements Continuation, AsyncListener +{ + AsyncContext _asyncContext; + Request _request; + Object _object; + RetryRequest _retry; + boolean _resumed=false; + boolean _timeout=false; + + Servlet3Continuation(Request request) + { + _request=request; + } + + public Object getObject() + { + return _object; + } + + public boolean isExpired() + { + return _asyncContext!=null && _timeout; + } + + public boolean isNew() + { + return _retry==null; + } + + public boolean isPending() + { + return _asyncContext!=null && (_request.getAsyncRequest().isSuspended() || !_request.getAsyncRequest().isInitial()); + } + + public boolean isResumed() + { + return _asyncContext!=null && _resumed; + } + + public void reset() + { + _resumed=false; + _timeout=false; + } + + public void resume() + { + if (_asyncContext==null) + throw new IllegalStateException(); + _resumed=true; + _asyncContext.dispatch(); + } + + public void setMutex(Object mutex) + { + } + + public void setObject(Object o) + { + _object=o; + } + + public boolean suspend(long timeout) + { + _asyncContext=_request.startAsync();; + if (!_request.getAsyncRequest().isInitial()||_resumed||_timeout) + { + _resumed=false; + _timeout=false; + return _resumed; + } + + _request.setAsyncTimeout(timeout); + _request.addAsyncListener(this); + if (_retry==null) + _retry=new RetryRequest(); + throw _retry; + + } + + public void onComplete(AsyncEvent event) throws IOException + { + + } + + public void onTimeout(AsyncEvent event) throws IOException + { + _timeout=true; + _request.getAsyncRequest().dispatch(); + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java new file mode 100644 index 00000000000..1d765818225 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java @@ -0,0 +1,83 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.util.component.LifeCycle; + +/** Session ID Manager. + * Manages session IDs across multiple contexts. + * + * + */ +/* ------------------------------------------------------------ */ +/** + * + * + */ +public interface SessionIdManager extends LifeCycle +{ + /** + * @param id The session ID without any cluster node extension + * @return True if the session ID is in use by at least one context. + */ + public boolean idInUse(String id); + + /** + * Add a session to the list of known sessions for a given ID. + * @param session The session + */ + public void addSession(HttpSession session); + + /** + * Remove session from the list of known sessions for a given ID. + * @param session + */ + public void removeSession(HttpSession session); + + /** + * Call {@link HttpSession#invalidate()} on all known sessions for the given id. + * @param id The session ID without any cluster node extension + */ + public void invalidateAll(String id); + + /** + * @param request + * @param created + * @return + */ + public String newSessionId(HttpServletRequest request,long created); + + public String getWorkerName(); + + + /* ------------------------------------------------------------ */ + /** Get a cluster ID from a node ID. + * Strip node identifier from a located session ID. + * @param nodeId + * @return + */ + public String getClusterId(String nodeId); + + /* ------------------------------------------------------------ */ + /** Get a node ID from a cluster ID and a request + * @param clusterId The ID of the session + * @param request The request that for the session (or null) + * @return The session ID qualified with the node ID. + */ + public String getNodeId(String clusterId,HttpServletRequest request); + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java new file mode 100644 index 00000000000..6546bf182db --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java @@ -0,0 +1,327 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.util.EventListener; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.util.component.LifeCycle; + +/* --------------------------------------------------------------------- */ +/** + * Session Manager. + * The API required to manage sessions for a servlet context. + * + * + */ +public interface SessionManager extends LifeCycle +{ + /* ------------------------------------------------------------ */ + /** + * Session cookie name. + * Defaults to JSESSIONID, but can be set with the + * org.eclipse.jetty.servlet.SessionCookie context init parameter. + */ + public final static String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie"; + public final static String __DefaultSessionCookie = "JSESSIONID"; + + + /* ------------------------------------------------------------ */ + /** + * Session id path parameter name. + * Defaults to jsessionid, but can be set with the + * org.eclipse.jetty.servlet.SessionIdPathParameterName context init parameter. + * If set to null or "none" no URL rewriting will be done. + */ + public final static String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName"; + public final static String __DefaultSessionIdPathParameterName = "jsessionid"; + + + /* ------------------------------------------------------------ */ + /** + * Session Domain. + * If this property is set as a ServletContext InitParam, then it is + * used as the domain for session cookies. If it is not set, then + * no domain is specified for the session cookie. + */ + public final static String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain"; + public final static String __DefaultSessionDomain = null; + + + /* ------------------------------------------------------------ */ + /** + * Session Path. + * If this property is set as a ServletContext InitParam, then it is + * used as the path for the session cookie. If it is not set, then + * the context path is used as the path for the cookie. + */ + public final static String __SessionPathProperty = "org.eclipse.jetty.servlet.SessionPath"; + + /* ------------------------------------------------------------ */ + /** + * Session Max Age. + * If this property is set as a ServletContext InitParam, then it is + * used as the max age for the session cookie. If it is not set, then + * a max age of -1 is used. + */ + public final static String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge"; + + /* ------------------------------------------------------------ */ + /** + * Returns the HttpSession with the given session id + * + * @param id the session id + * @return the HttpSession with the corresponding id or null if no session with the given id exists + */ + public HttpSession getHttpSession(String id); + + /* ------------------------------------------------------------ */ + /** + * Creates a new HttpSession. + * + * @param request the HttpServletRequest containing the requested session id + * @return the new HttpSession + */ + public HttpSession newHttpSession(HttpServletRequest request); + + /* ------------------------------------------------------------ */ + /** + * @return true if session cookies should be secure + */ + public boolean getSecureCookies(); + + /* ------------------------------------------------------------ */ + /** + * @return true if session cookies should be HTTP-only (Microsoft extension) + * @see Cookie#isHttpOnly() + */ + public boolean getHttpOnly(); + + /* ------------------------------------------------------------ */ + /** + * @return the max period of inactivity, after which the session is invalidated, in seconds. + * @see #setMaxInactiveInterval(int) + */ + public int getMaxInactiveInterval(); + + /* ------------------------------------------------------------ */ + /** + * Sets the max period of inactivity, after which the session is invalidated, in seconds. + * + * @param seconds the max inactivity period, in seconds. + * @see #getMaxInactiveInterval() + */ + public void setMaxInactiveInterval(int seconds); + + /* ------------------------------------------------------------ */ + /** + * Sets the {@link SessionHandler}. + * + * @param handler the SessionHandler object + */ + public void setSessionHandler(SessionHandler handler); + + /* ------------------------------------------------------------ */ + /** + * Adds an event listener for session-related events. + * + * @param listener the session event listener to add + * Individual SessionManagers implementations may accept arbitrary listener types, + * but they are expected to at least handle HttpSessionActivationListener, + * HttpSessionAttributeListener, HttpSessionBindingListener and HttpSessionListener. + * @see #removeEventListener(EventListener) + */ + public void addEventListener(EventListener listener); + + /* ------------------------------------------------------------ */ + /** + * Removes an event listener for for session-related events. + * + * @param listener the session event listener to remove + * @see #addEventListener(EventListener) + */ + public void removeEventListener(EventListener listener); + + /* ------------------------------------------------------------ */ + /** + * Removes all event listeners for session-related events. + * + * @see #removeEventListener(EventListener) + */ + public void clearEventListeners(); + + /* ------------------------------------------------------------ */ + /** + * Gets a Cookie for a session. + * + * @param session the session to which the cookie should refer. + * @param contextPath the context to which the cookie should be linked. + * The client will only send the cookie value when requesting resources under this path. + * @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS). + * @return if this SessionManager uses cookies, then this method will return a new + * {@link Cookie cookie object} that should be set on the client in order to link future HTTP requests + * with the session. If cookies are not in use, this method returns null. + */ + public Cookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure); + + /* ------------------------------------------------------------ */ + /** + * @return the cross context session id manager. + * @see #setIdManager(SessionIdManager) + */ + public SessionIdManager getIdManager(); + + /* ------------------------------------------------------------ */ + /** + * @return the cross context session id manager. + * @deprecated use {@link #getIdManager()} + */ + public SessionIdManager getMetaManager(); + + /* ------------------------------------------------------------ */ + /** + * Sets the cross context session id manager + * + * @param idManager the cross context session id manager. + * @see #getIdManager() + */ + public void setIdManager(SessionIdManager idManager); + + /* ------------------------------------------------------------ */ + /** + * @param session the session to test for validity + * @return whether the given session is valid, that is, it has not been invalidated. + */ + public boolean isValid(HttpSession session); + + /* ------------------------------------------------------------ */ + /** + * @param session the session object + * @return the unique id of the session within the cluster, extended with an optional node id. + * @see #getClusterId(HttpSession) + */ + public String getNodeId(HttpSession session); + + /* ------------------------------------------------------------ */ + /** + * @param session the session object + * @return the unique id of the session within the cluster (without a node id extension) + * @see #getNodeId(HttpSession) + */ + public String getClusterId(HttpSession session); + + /* ------------------------------------------------------------ */ + /** + * Called by the {@link SessionHandler} when a session is first accessed by a request. + * + * @param session the session object + * @param secure whether the request is secure or not + * @return the session cookie. If not null, this cookie should be set on the response to either migrate + * the session or to refresh a session cookie that may expire. + * @see #complete(HttpSession) + */ + public Cookie access(HttpSession session, boolean secure); + + /* ------------------------------------------------------------ */ + /** + * Called by the {@link SessionHandler} when a session is last accessed by a request. + * + * @param session the session object + * @see #access(HttpSession, boolean) + */ + public void complete(HttpSession session); + + /** + * Sets the session cookie name. + * @param cookieName the session cookie name + * @see #getSessionCookie() + */ + public void setSessionCookie(String cookieName); + + /** + * @return the session cookie name, by default "JSESSIONID". + * @see #setSessionCookie(String) + */ + public String getSessionCookie(); + + /** + * Sets the session id URL path parameter name. + * + * @param parameterName the URL path parameter name for session id URL rewriting (null or "none" for no rewriting). + * @see #getSessionIdPathParameterName() + * @see #getSessionIdPathParameterNamePrefix() + */ + public void setSessionIdPathParameterName(String parameterName); + + /** + * @return the URL path parameter name for session id URL rewriting, by default "jsessionid". + * @see #setSessionIdPathParameterName(String) + */ + public String getSessionIdPathParameterName(); + + /** + * @return a formatted version of {@link #getSessionIdPathParameterName()}, by default + * ";" + sessionIdParameterName + "=", for easier lookup in URL strings. + * @see #getSessionIdPathParameterName() + */ + public String getSessionIdPathParameterNamePrefix(); + + /** + * Sets the domain to set on the session cookie + * @param domain the domain to set on the session cookie + * @see #getSessionDomain() + */ + public void setSessionDomain(String domain); + + /** + * @return the domain to set on the session cookie + * @see #setSessionDomain(String) + */ + public String getSessionDomain(); + + /** + * Sets the path to set on the session cookie + * @param path the path to set on the session cookie + * @see #getSessionPath() + */ + public void setSessionPath(String path); + + /** + * @return the path to set on the session cookie + * @see #setSessionPath(String) + */ + public String getSessionPath(); + + /** + * Sets the max age to set on the session cookie, in seconds + * @param maxCookieAge the max age to set on the session cookie, in seconds + * @see #getMaxCookieAge() + */ + public void setMaxCookieAge(int maxCookieAge); + + /** + * @return the max age to set on the session cookie, in seconds + * @see #setMaxCookieAge(int) + */ + public int getMaxCookieAge(); + + /** + * @return whether the session management is handled via cookies. + */ + public boolean isUsingCookies(); +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java b/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java new file mode 100644 index 00000000000..314c6c06fd0 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java @@ -0,0 +1,136 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; +import java.security.Principal; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +/* ------------------------------------------------------------ */ +/** User object that encapsulates user identity and operations such as run-as-role actions, + * checking isUserInRole and getUserPrincipal. + * + * Implementations of UserIdentity should be immutable so that they may be + * cached by Authenticators and LoginServices. + * + */ +public interface UserIdentity +{ + final static String[] NO_ROLES = new String[]{}; + + /* ------------------------------------------------------------ */ + /** + * @return The user subject + */ + Subject getSubject(); + + /* ------------------------------------------------------------ */ + /** + * @return The user principal + */ + Principal getUserPrincipal(); + + /* ------------------------------------------------------------ */ + /** + * @return The users roles + */ + String[] getRoles(); + + /* ------------------------------------------------------------ */ + /** Check if the user is in a role. + * This call is used to satisfy authorization calls from + * container code which will be using translated role names. + * @param role A role name. + * @return True if the user can act in that role. + */ + boolean isUserInRole(String role); + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * A UserIdentity Scope. + * A scope is the environment in which a User Identity is to + * be interpreted. Typically it is set by the target servlet of + * a request. + * @see org.eclipse.jetty.servlet.ServletHolder + */ + interface Scope + { + /* ------------------------------------------------------------ */ + /** + * @return The context path that the identity is being considered within + */ + String getContextPath(); + + /* ------------------------------------------------------------ */ + /** + * @return The name of the identity context. Typically this is the servlet name. + */ + String getName(); + + /* ------------------------------------------------------------ */ + /** + * @return The name of a runAs entity. Typically this is a runAs role applied to a servlet. + */ + String getRunAsRole(); + + /* ------------------------------------------------------------ */ + /** + * @return A map of role reference names that converts from names used by application code + * to names used by the context deployment. + */ + Map getRoleRefMap(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public interface UnauthticatedUserIdentity extends UserIdentity + { + UserIdentity login(ServletRequest request, ServletResponse response); + UserIdentity login(String username, String password); + }; + + public static final UserIdentity UNAUTHENTICATED_IDENTITY = new UserIdentity() + { + public Subject getSubject() + { + return null; + } + + public Principal getUserPrincipal() + { + return null; + } + + public String[] getRoles() + { + return NO_ROLES; + } + + public boolean isUserInRole(String role) + { + return false; + } + + public String toString() + { + return "UNAUTHENTICATED"; + } + }; +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java new file mode 100644 index 00000000000..f89a08c0203 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java @@ -0,0 +1,264 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.bio; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.server.AbstractConnector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------------------------- */ +/** Socket Connector. + * This connector implements a traditional blocking IO and threading model. + * Normal JRE sockets are used and a thread is allocated per connection. + * Buffers are managed so that large buffers are only allocated to active connections. + * + * This Connector should only be used if NIO is not available. + * + * @org.apache.xbean.XBean element="bioConnector" description="Creates a BIO based socket connector" + * + * + */ +public class SocketConnector extends AbstractConnector +{ + protected ServerSocket _serverSocket; + protected Set _connections; + + /* ------------------------------------------------------------ */ + /** Constructor. + * + */ + public SocketConnector() + { + } + + /* ------------------------------------------------------------ */ + public Object getConnection() + { + return _serverSocket; + } + + /* ------------------------------------------------------------ */ + public void open() throws IOException + { + // Create a new server socket and set to non blocking mode + if (_serverSocket==null || _serverSocket.isClosed()) + _serverSocket= newServerSocket(getHost(),getPort(),getAcceptQueueSize()); + _serverSocket.setReuseAddress(getReuseAddress()); + } + + /* ------------------------------------------------------------ */ + protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException + { + ServerSocket ss= host==null? + new ServerSocket(port,backlog): + new ServerSocket(port,backlog,InetAddress.getByName(host)); + + return ss; + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + if (_serverSocket!=null) + _serverSocket.close(); + _serverSocket=null; + } + + /* ------------------------------------------------------------ */ + public void accept(int acceptorID) + throws IOException, InterruptedException + { + Socket socket = _serverSocket.accept(); + configure(socket); + + Connection connection=new Connection(socket); + connection.dispatch(); + } + + /* ------------------------------------------------------------------------------- */ + /** + * Allows subclass to override Conection if required. + */ + protected HttpConnection newHttpConnection(EndPoint endpoint) + { + return new HttpConnection(this, endpoint, getServer()); + } + + /* ------------------------------------------------------------------------------- */ + public Buffer newBuffer(int size) + { + return new ByteArrayBuffer(size); + } + + /* ------------------------------------------------------------------------------- */ + public void customize(EndPoint endpoint, Request request) + throws IOException + { + Connection connection = (Connection)endpoint; + if (connection._sotimeout!=_maxIdleTime) + { + connection._sotimeout=_maxIdleTime; + ((Socket)endpoint.getTransport()).setSoTimeout(_maxIdleTime); + } + + super.customize(endpoint, request); + } + + /* ------------------------------------------------------------------------------- */ + public int getLocalPort() + { + if (_serverSocket==null || _serverSocket.isClosed()) + return -1; + return _serverSocket.getLocalPort(); + } + + /* ------------------------------------------------------------------------------- */ + protected void doStart() throws Exception + { + _connections=new HashSet(); + super.doStart(); + } + + /* ------------------------------------------------------------------------------- */ + protected void doStop() throws Exception + { + super.doStop(); + Set set=null; + + synchronized(_connections) + { + set= new HashSet(_connections); + } + + Iterator iter=set.iterator(); + while(iter.hasNext()) + { + Connection connection = (Connection)iter.next(); + connection.close(); + } + } + + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + protected class Connection extends SocketEndPoint implements Runnable + { + boolean _dispatched=false; + HttpConnection _connection; + int _sotimeout; + protected Socket _socket; + + public Connection(Socket socket) throws IOException + { + super(socket); + _connection = newHttpConnection(this); + _sotimeout=socket.getSoTimeout(); + _socket=socket; + } + + public void dispatch() throws InterruptedException, IOException + { + if (getThreadPool()==null || !getThreadPool().dispatch(this)) + { + Log.warn("dispatch failed for {}",_connection); + close(); + } + } + + public int fill(Buffer buffer) throws IOException + { + int l = super.fill(buffer); + if (l<0) + close(); + return l; + } + + public void close() throws IOException + { + _connection.getRequest().getAsyncRequest().cancel(); + super.close(); + } + + public void run() + { + try + { + connectionOpened(_connection); + synchronized(_connections) + { + _connections.add(this); + } + + while (isStarted() && !isClosed()) + { + if (_connection.isIdle()) + { + if (getServer().getThreadPool().isLowOnThreads()) + { + int lrmit = getLowResourceMaxIdleTime(); + if (lrmit>=0 && _sotimeout!= lrmit) + { + _sotimeout=lrmit; + _socket.setSoTimeout(_sotimeout); + } + } + } + _connection.handle(); + } + } + catch (EofException e) + { + Log.debug("EOF", e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + catch (HttpException e) + { + Log.debug("BAD", e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + catch(Exception e) + { + Log.warn("handle failed?",e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + finally + { + connectionClosed(_connection); + synchronized(_connections) + { + _connections.remove(this); + } + } + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java new file mode 100644 index 00000000000..a09c5989ece --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java @@ -0,0 +1,97 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +/** AbstractHandler. + * + * + */ +public abstract class AbstractHandler extends AbstractLifeCycle implements Handler +{ + protected String _string; + private Server _server; + + /* ------------------------------------------------------------ */ + /** + * + */ + public AbstractHandler() + { + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.LifeCycle#start() + */ + protected void doStart() throws Exception + { + Log.debug("starting {}",this); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.LifeCycle#stop() + */ + protected void doStop() throws Exception + { + Log.debug("stopping {}",this); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + if (_string==null) + { + _string=super.toString(); + _string=_string.substring(_string.lastIndexOf('.')+1); + } + return _string; + } + + /* ------------------------------------------------------------ */ + public void setServer(Server server) + { + Server old_server=_server; + if (old_server!=null && old_server!=server) + old_server.getContainer().removeBean(this); + _server=server; + if (_server!=null && _server!=old_server) + _server.getContainer().addBean(this); + } + + /* ------------------------------------------------------------ */ + public Server getServer() + { + return _server; + } + + + /* ------------------------------------------------------------ */ + public void destroy() + { + if (!isStopped()) + throw new IllegalStateException("!STOPPED"); + if (_server!=null) + _server.getContainer().removeBean(this); + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java new file mode 100644 index 00000000000..b5eeb780bbb --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java @@ -0,0 +1,88 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.util.LazyList; + + +/* ------------------------------------------------------------ */ +/** Abstract Handler Container. + * This is the base class for handlers that may contain other handlers. + * + * + * + */ +public abstract class AbstractHandlerContainer extends AbstractHandler implements HandlerContainer +{ + /* ------------------------------------------------------------ */ + public AbstractHandlerContainer() + { + } + + /* ------------------------------------------------------------ */ + public Handler[] getChildHandlers() + { + Object list = expandChildren(null,null); + return (Handler[])LazyList.toArray(list, Handler.class); + } + + /* ------------------------------------------------------------ */ + public Handler[] getChildHandlersByClass(Class byclass) + { + Object list = expandChildren(null,byclass); + return (Handler[])LazyList.toArray(list, byclass); + } + + /* ------------------------------------------------------------ */ + public Handler getChildHandlerByClass(Class byclass) + { + // TODO this can be more efficient? + Object list = expandChildren(null,byclass); + if (list==null) + return null; + return LazyList.get(list, 0); + } + + /* ------------------------------------------------------------ */ + protected Object expandChildren(Object list, Class byClass) + { + return list; + } + + /* ------------------------------------------------------------ */ + protected Object expandHandler(Handler handler, Object list, Class byClass) + { + if (handler==null) + return list; + + if (handler!=null && (byClass==null || byClass.isAssignableFrom(handler.getClass()))) + list=LazyList.add(list, handler); + + if (handler instanceof AbstractHandlerContainer) + list=((AbstractHandlerContainer)handler).expandChildren(list, byClass); + else if (handler instanceof HandlerContainer) + { + HandlerContainer container = (HandlerContainer)handler; + Handler[] handlers=byClass==null?container.getChildHandlers():container.getChildHandlersByClass(byClass); + list=LazyList.addArray(list, handlers); + } + + return list; + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CompleteHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CompleteHandler.java new file mode 100644 index 00000000000..3311b73c8e4 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CompleteHandler.java @@ -0,0 +1,37 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.util.List; + +import javax.servlet.ServletRequest; + +import org.eclipse.jetty.server.Request; + +/** + * An interface for handlers that wish to be notified of request completion. + * + * If the request attribute COMPLETE_HANDLER_ATTR is set as either a single + * CompleteHandler instance or a {@link List} of CompleteHandler instances, + * then when the {@link ServletRequest#complete()} method is called, then + * the {@link #complete(Request)} method is called for each CompleteHandler. + * + * + * + */ +public interface CompleteHandler +{ + public final static String COMPLETE_HANDLER_ATTR = "org.eclipse.jetty.server.handler.CompleteHandlers"; + void complete(Request request); +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java new file mode 100644 index 00000000000..d764f1a6025 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -0,0 +1,1903 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** ContextHandler. + * + * This handler wraps a call to handle by setting the context and + * servlet path, plus setting the context classloader. + * + *

    + * If the context init parameter "org.eclipse.jetty.servlet.ManagedAttributes" + * is set to a coma separated list of names, then they are treated as context + * attribute names, which if set as attributes are passed to the servers Container + * so that they may be managed with JMX. + * + * @org.apache.xbean.XBean description="Creates a basic HTTP context" + * + * + * + */ +public class ContextHandler extends HandlerWrapper implements Attributes, Server.Graceful, CompleteHandler +{ + private static ThreadLocal __context=new ThreadLocal(); + public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.servlet.ManagedAttributes"; + + /* ------------------------------------------------------------ */ + /** Get the current ServletContext implementation. + * This call is only valid during a call to doStart and is available to + * nested handlers to access the context. + * + * @return ServletContext implementation + */ + public static Context getCurrentContext() + { + Context context = __context.get(); + return context; + } + + protected Context _scontext; + + private AttributesMap _attributes; + private AttributesMap _contextAttributes; + private ClassLoader _classLoader; + private String _contextPath="/"; + private Map _initParams; + private String _displayName; + private Resource _baseResource; + private MimeTypes _mimeTypes; + private Map _localeEncodingMap; + private String[] _welcomeFiles; + private ErrorHandler _errorHandler; + private String[] _vhosts; + private Set _connectors; + private EventListener[] _eventListeners; + private Logger _logger; + private boolean _shutdown; + private boolean _allowNullPathInfo; + private int _maxFormContentSize=Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",200000).intValue(); + private boolean _compactPath=false; + private boolean _aliases=true; + + private Object _contextListeners; + private Object _contextAttributeListeners; + private Object _requestListeners; + private Object _asyncListeners; + private Object _requestAttributeListeners; + private Set _managedAttributes; + + /* ------------------------------------------------------------ */ + /** + * + */ + public ContextHandler() + { + super(); + _scontext=new Context(); + _attributes=new AttributesMap(); + _initParams=new HashMap(); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + protected ContextHandler(Context context) + { + super(); + _scontext=context; + _attributes=new AttributesMap(); + _initParams=new HashMap(); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public ContextHandler(String contextPath) + { + this(); + setContextPath(contextPath); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public ContextHandler(HandlerContainer parent, String contextPath) + { + this(); + setContextPath(contextPath); + parent.addHandler(this); + } + + /* ------------------------------------------------------------ */ + public Context getServletContext() + { + return _scontext; + } + + /* ------------------------------------------------------------ */ + /** + * @return the allowNullPathInfo true if /context is not redirected to /context/ + */ + public boolean getAllowNullPathInfo() + { + return _allowNullPathInfo; + } + + /* ------------------------------------------------------------ */ + /** + * @param allowNullPathInfo true if /context is not redirected to /context/ + */ + public void setAllowNullPathInfo(boolean allowNullPathInfo) + { + _allowNullPathInfo=allowNullPathInfo; + } + + /* ------------------------------------------------------------ */ + public void setServer(Server server) + { + if (_errorHandler!=null) + { + Server old_server=getServer(); + if (old_server!=null && old_server!=server) + old_server.getContainer().update(this, _errorHandler, null, "error",true); + super.setServer(server); + if (server!=null && server!=old_server) + server.getContainer().update(this, null, _errorHandler, "error",true); + _errorHandler.setServer(server); + } + else + super.setServer(server); + } + + /* ------------------------------------------------------------ */ + /** Set the virtual hosts for the context. + * Only requests that have a matching host header or fully qualified + * URL will be passed to that context with a virtual host name. + * A context with no virtual host names or a null virtual host name is + * available to all requests that are not served by a context with a + * matching virtual host name. + * @param vhosts Array of virtual hosts that this context responds to. A + * null host name or null/empty array means any hostname is acceptable. + * Host names may be String representation of IP addresses. Host names may + * start with '*.' to wildcard one level of names. + */ + public void setVirtualHosts( String[] vhosts ) + { + if ( vhosts == null ) + { + _vhosts = vhosts; + } + else + { + _vhosts = new String[vhosts.length]; + for ( int i = 0; i < vhosts.length; i++ ) + _vhosts[i] = normalizeHostname( vhosts[i]); + } + } + + /* ------------------------------------------------------------ */ + /** Get the virtual hosts for the context. + * Only requests that have a matching host header or fully qualified + * URL will be passed to that context with a virtual host name. + * A context with no virtual host names or a null virtual host name is + * available to all requests that are not served by a context with a + * matching virtual host name. + * @return Array of virtual hosts that this context responds to. A + * null host name or empty array means any hostname is acceptable. + * Host names may be String representation of IP addresses. + * Host names may start with '*.' to wildcard one level of names. + */ + public String[] getVirtualHosts() + { + return _vhosts; + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated use {@link #setConnectorNames(String[])} + */ + public void setHosts(String[] hosts) + { + setConnectorNames(hosts); + } + + /* ------------------------------------------------------------ */ + /** Get the hosts for the context. + * @deprecated + */ + public String[] getHosts() + { + return getConnectorNames(); + } + + /* ------------------------------------------------------------ */ + /** + * @return an array of connector names that this context + * will accept a request from. + */ + public String[] getConnectorNames() + { + if (_connectors==null || _connectors.size()==0) + return null; + + return (String[])_connectors.toArray(new String[_connectors.size()]); + } + + /* ------------------------------------------------------------ */ + /** Set the names of accepted connectors. + * + * Names are either "host:port" or a specific configured name for a connector. + * + * @param connectors If non null, an array of connector names that this context + * will accept a request from. + */ + public void setConnectorNames(String[] connectors) + { + if (connectors==null || connectors.length==0) + _connectors=null; + else + _connectors= new HashSet(Arrays.asList(connectors)); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _attributes.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getAttributeNames() + */ + @SuppressWarnings("unchecked") + public Enumeration getAttributeNames() + { + return AttributesMap.getAttributeNamesCopy(_attributes); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the attributes. + */ + public Attributes getAttributes() + { + return _attributes; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the classLoader. + */ + public ClassLoader getClassLoader() + { + return _classLoader; + } + + /* ------------------------------------------------------------ */ + /** + * Make best effort to extract a file classpath from the context classloader + * @return Returns the classLoader. + */ + public String getClassPath() + { + if ( _classLoader==null || !(_classLoader instanceof URLClassLoader)) + return null; + URLClassLoader loader = (URLClassLoader)_classLoader; + URL[] urls =loader.getURLs(); + StringBuilder classpath=new StringBuilder(); + for (int i=0;i0) + classpath.append(File.pathSeparatorChar); + classpath.append(file.getAbsolutePath()); + } + } + catch (IOException e) + { + Log.debug(e); + } + } + if (classpath.length()==0) + return null; + return classpath.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the _contextPath. + */ + public String getContextPath() + { + return _contextPath; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getInitParameter(java.lang.String) + */ + public String getInitParameter(String name) + { + return (String)_initParams.get(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getInitParameterNames() + */ + @SuppressWarnings("unchecked") + public Enumeration getInitParameterNames() + { + return Collections.enumeration(_initParams.keySet()); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the initParams. + */ + public Map getInitParams() + { + return _initParams; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServletContextName() + */ + public String getDisplayName() + { + return _displayName; + } + + /* ------------------------------------------------------------ */ + public EventListener[] getEventListeners() + { + return _eventListeners; + } + + /* ------------------------------------------------------------ */ + /** + * Set the context event listeners. + * @see ServletContextListener + * @see ServletContextAttributeListener + * @see ServletRequestListener + * @see ServletRequestAttributeListener + */ + public void setEventListeners(EventListener[] eventListeners) + { + _contextListeners=null; + _contextAttributeListeners=null; + _requestListeners=null; + _requestAttributeListeners=null; + + _eventListeners=eventListeners; + + for (int i=0; eventListeners!=null && i(); + String[] attributes = managedAttributes.toString().split(","); + for (String s : attributes) + _managedAttributes.add(s); + + Enumeration e = _scontext.getAttributeNames(); + while(e.hasMoreElements()) + { + String name = (String)e.nextElement(); + Object value = _scontext.getAttribute(name); + setManagedAttribute(name,value); + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStop() + */ + protected void doStop() throws Exception + { + ClassLoader old_classloader=null; + Thread current_thread=null; + + Context old_context=__context.get(); + __context.set(_scontext); + try + { + // Set the classloader + if (_classLoader!=null) + { + current_thread=Thread.currentThread(); + old_classloader=current_thread.getContextClassLoader(); + current_thread.setContextClassLoader(_classLoader); + } + + super.doStop(); + + // Context listeners + if (_contextListeners != null ) + { + ServletContextEvent event= new ServletContextEvent(_scontext); + for (int i=LazyList.size(_contextListeners); i-->0;) + { + ((ServletContextListener)LazyList.get(_contextListeners, i)).contextDestroyed(event); + } + } + + if (_errorHandler!=null) + _errorHandler.stop(); + + Enumeration e = _scontext.getAttributeNames(); + while(e.hasMoreElements()) + { + String name = (String)e.nextElement(); + setManagedAttribute(name,null); + } + } + finally + { + __context.set(old_context); + // reset the classloader + if (_classLoader!=null) + current_thread.setContextClassLoader(old_classloader); + } + + if (_contextAttributes!=null) + _contextAttributes.clearAttributes(); + _contextAttributes=null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + Request baseRequest=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + DispatcherType dispatch=request.getDispatcherType(); + + if( !isStarted() || _shutdown || (DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled())) + return; + + // Check the vhosts + if (_vhosts!=null && _vhosts.length>0) + { + String vhost = normalizeHostname( request.getServerName()); + + boolean match=false; + + // TODO non-linear lookup + for (int i=0;!match && i<_vhosts.length;i++) + { + String contextVhost = _vhosts[i]; + if(contextVhost==null) continue; + if(contextVhost.startsWith("*.")) { + // wildcard only at the beginning, and only for one additional subdomain level + match=contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".")+1,contextVhost.length()-2); + } else + match=contextVhost.equalsIgnoreCase(vhost); + } + if (!match) + return; + } + + // Check the connector + if (_connectors!=null && _connectors.size()>0) + { + String connector=HttpConnection.getCurrentConnection().getConnector().getName(); + if (connector==null || !_connectors.contains(connector)) + return; + } + + if (_compactPath) + target=URIUtil.compactPath(target); + + if (target.startsWith(_contextPath)) + { + if (_contextPath.length()==target.length() && _contextPath.length()>1 &&!_allowNullPathInfo) + { + // context request must end with / + baseRequest.setHandled(true); + if (request.getQueryString()!=null) + response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)+"?"+request.getQueryString()); + else + response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)); + return; + } + } + else + { + // Not for this context! + return; + } + + doHandle(target,baseRequest,request,response); + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + boolean new_context=false; + Context old_context=null; + String old_context_path=null; + String old_servlet_path=null; + String old_path_info=null; + ClassLoader old_classloader=null; + Thread current_thread=null; + String pathInfo=null; + + DispatcherType dispatch=request.getDispatcherType(); + + old_context=baseRequest.getContext(); + + // Are we already in this context? + if (old_context!=_scontext) + { + new_context=true; + + // check the target. + if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch)) + { + if (target.length()>_contextPath.length()) + { + if (_contextPath.length()>1) + target=target.substring(_contextPath.length()); + pathInfo=target; + } + else if (_contextPath.length()==1) + { + target=URIUtil.SLASH; + pathInfo=URIUtil.SLASH; + } + else + { + target=URIUtil.SLASH; + pathInfo=null; + } + } + } + + try + { + old_context_path=baseRequest.getContextPath(); + old_servlet_path=baseRequest.getServletPath(); + old_path_info=baseRequest.getPathInfo(); + + // Update the paths + baseRequest.setContext(_scontext); + if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/")) + { + if (_contextPath.length()==1) + baseRequest.setContextPath(""); + else + baseRequest.setContextPath(_contextPath); + baseRequest.setServletPath(null); + baseRequest.setPathInfo(pathInfo); + } + + ServletRequestEvent event=null; + if (new_context) + { + // Set the classloader + if (_classLoader!=null) + { + current_thread=Thread.currentThread(); + old_classloader=current_thread.getContextClassLoader(); + current_thread.setContextClassLoader(_classLoader); + } + + // Handle the REALLY SILLY request events! + baseRequest.setRequestListeners(_requestListeners); + if (_requestAttributeListeners!=null) + { + final int s=LazyList.size(_requestAttributeListeners); + for(int i=0;i0;) + baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i))); + } + } + } + } + finally + { + if (old_context!=_scontext) + { + // reset the classloader + if (_classLoader!=null) + { + current_thread.setContextClassLoader(old_classloader); + } + + // reset the context and servlet path. + baseRequest.setContext(old_context); + baseRequest.setContextPath(old_context_path); + baseRequest.setServletPath(old_servlet_path); + baseRequest.setPathInfo(old_path_info); + } + } + } + + /* ------------------------------------------------------------ */ + /* Handle a runnable in this context + */ + public void handle(Runnable runnable) + { + ClassLoader old_classloader=null; + Thread current_thread=null; + try + { + // Set the classloader + if (_classLoader!=null) + { + current_thread=Thread.currentThread(); + old_classloader=current_thread.getContextClassLoader(); + current_thread.setContextClassLoader(_classLoader); + } + + runnable.run(); + } + finally + { + if (old_classloader!=null) + { + current_thread.setContextClassLoader(old_classloader); + } + } + } + + /* ------------------------------------------------------------ */ + /** Check the target. + * Called by {@link #handle(String, HttpServletRequest, HttpServletResponse)} when a + * target within a context is determined. If the target is protected, 404 is returned. + * The default implementation always returns false. + * @see org.eclipse.jetty.webapp.WebAppContext#isProtectedTarget(String) + */ + /* ------------------------------------------------------------ */ + protected boolean isProtectedTarget(String target) + { + return false; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + setManagedAttribute(name,null); + _attributes.removeAttribute(name); + } + + /* ------------------------------------------------------------ */ + /* Set a context attribute. + * Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. + * Their lifecycle spans the stop/start of a context. No attribute listener events are + * triggered by this API. + * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object value) + { + setManagedAttribute(name,value); + _attributes.setAttribute(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param attributes The attributes to set. + */ + public void setAttributes(Attributes attributes) + { + if (attributes instanceof AttributesMap) + { + _attributes = (AttributesMap)attributes; + Enumeration e = _attributes.getAttributeNames(); + while (e.hasMoreElements()) + { + String name = (String)e.nextElement(); + setManagedAttribute(name,attributes.getAttribute(name)); + } + } + else + { + _attributes=new AttributesMap(); + Enumeration e = attributes.getAttributeNames(); + while (e.hasMoreElements()) + { + String name = (String)e.nextElement(); + Object value=attributes.getAttribute(name); + setManagedAttribute(name,value); + _attributes.setAttribute(name,value); + } + } + } + + /* ------------------------------------------------------------ */ + public void clearAttributes() + { + Enumeration e = _attributes.getAttributeNames(); + while (e.hasMoreElements()) + { + String name = (String)e.nextElement(); + setManagedAttribute(name,null); + } + _attributes.clearAttributes(); + } + + /* ------------------------------------------------------------ */ + private void setManagedAttribute(String name, Object value) + { + if (_managedAttributes!=null && _managedAttributes.contains(name)) + { + Object o =_scontext.getAttribute(name); + if (o!=null) + getServer().getContainer().removeBean(o); + if (value!=null) + getServer().getContainer().addBean(value); + } + } + + /* ------------------------------------------------------------ */ + /** + * @param classLoader The classLoader to set. + */ + public void setClassLoader(ClassLoader classLoader) + { + _classLoader = classLoader; + } + + /* ------------------------------------------------------------ */ + /** + * @param contextPath The _contextPath to set. + */ + public void setContextPath(String contextPath) + { + if (contextPath!=null && contextPath.length()>1 && contextPath.endsWith("/")) + throw new IllegalArgumentException("ends with /"); + _contextPath = contextPath; + + if (getServer()!=null && (getServer().isStarting() || getServer().isStarted())) + { + Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class); + for (int h=0;contextCollections!=null&& h initParams) + { + if (initParams == null) + return; + _initParams = new HashMap(initParams); + } + + /* ------------------------------------------------------------ */ + /** + * @param servletContextName The servletContextName to set. + */ + public void setDisplayName(String servletContextName) + { + _displayName = servletContextName; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the resourceBase. + */ + public Resource getBaseResource() + { + if (_baseResource==null) + return null; + return _baseResource; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the base resource as a string. + */ + public String getResourceBase() + { + if (_baseResource==null) + return null; + return _baseResource.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param base The resourceBase to set. + */ + public void setBaseResource(Resource base) + { + _baseResource=base; + } + + /* ------------------------------------------------------------ */ + /** + * @param resourceBase The base resource as a string. + */ + public void setResourceBase(String resourceBase) + { + try + { + setBaseResource(newResource(resourceBase)); + } + catch (Exception e) + { + Log.warn(e); + throw new IllegalArgumentException(resourceBase); + } + } + /* ------------------------------------------------------------ */ + /** + * @return True if alias checking is performed on resources. + */ + public boolean isAliases() + { + return _aliases; + } + + /* ------------------------------------------------------------ */ + /** + * @param aliases alias checking performed on resources. + */ + public void setAliases(boolean aliases) + { + _aliases = aliases; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the mimeTypes. + */ + public MimeTypes getMimeTypes() + { + return _mimeTypes; + } + + /* ------------------------------------------------------------ */ + /** + * @param mimeTypes The mimeTypes to set. + */ + public void setMimeTypes(MimeTypes mimeTypes) + { + _mimeTypes = mimeTypes; + } + + /* ------------------------------------------------------------ */ + /** + */ + public void setWelcomeFiles(String[] files) + { + _welcomeFiles=files; + } + + /* ------------------------------------------------------------ */ + /** + * @return The names of the files which the server should consider to be welcome files in this context. + * @see The Servlet Specification + * @see #setWelcomeFiles + */ + public String[] getWelcomeFiles() + { + return _welcomeFiles; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the errorHandler. + */ + public ErrorHandler getErrorHandler() + { + return _errorHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @param errorHandler The errorHandler to set. + */ + public void setErrorHandler(ErrorHandler errorHandler) + { + if (errorHandler!=null) + errorHandler.setServer(getServer()); + if (getServer()!=null) + getServer().getContainer().update(this, _errorHandler, errorHandler, "errorHandler",true); + _errorHandler = errorHandler; + } + + /* ------------------------------------------------------------ */ + public int getMaxFormContentSize() + { + return _maxFormContentSize; + } + + /* ------------------------------------------------------------ */ + public void setMaxFormContentSize(int maxSize) + { + _maxFormContentSize=maxSize; + } + + + /* ------------------------------------------------------------ */ + /** + * @return True if URLs are compacted to replace multiple '/'s with a single '/' + */ + public boolean isCompactPath() + { + return _compactPath; + } + + /* ------------------------------------------------------------ */ + /** + * @param compactPath True if URLs are compacted to replace multiple '/'s with a single '/' + */ + public void setCompactPath(boolean compactPath) + { + _compactPath=compactPath; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + + return this.getClass().getName()+"@"+Integer.toHexString(hashCode())+"{"+getContextPath()+","+getBaseResource()+"}"; + } + + /* ------------------------------------------------------------ */ + public synchronized Class loadClass(String className) + throws ClassNotFoundException + { + if (className==null) + return null; + + if (_classLoader==null) + return Loader.loadClass(this.getClass(), className); + + return _classLoader.loadClass(className); + } + + + /* ------------------------------------------------------------ */ + public void addLocaleEncoding(String locale,String encoding) + { + if (_localeEncodingMap==null) + _localeEncodingMap=new HashMap(); + _localeEncodingMap.put(locale, encoding); + } + + /* ------------------------------------------------------------ */ + /** + * Get the character encoding for a locale. The full locale name is first + * looked up in the map of encodings. If no encoding is found, then the + * locale language is looked up. + * + * @param locale a Locale value + * @return a String representing the character encoding for + * the locale or null if none found. + */ + public String getLocaleEncoding(Locale locale) + { + if (_localeEncodingMap==null) + return null; + String encoding = (String)_localeEncodingMap.get(locale.toString()); + if (encoding==null) + encoding = (String)_localeEncodingMap.get(locale.getLanguage()); + return encoding; + } + + /* ------------------------------------------------------------ */ + /* + */ + public Resource getResource(String path) throws MalformedURLException + { + if (path==null || !path.startsWith(URIUtil.SLASH)) + throw new MalformedURLException(path); + + if (_baseResource==null) + return null; + + try + { + path=URIUtil.canonicalPath(path); + Resource resource=_baseResource.addPath(path); + + if (_aliases && resource.getAlias()!=null) + { + if (resource.exists()) + Log.warn("Aliased resource: "+resource+"~="+resource.getAlias()); + else if (Log.isDebugEnabled()) + Log.debug("Aliased resource: "+resource+"~="+resource.getAlias()); + return null; + } + + return resource; + } + catch(Exception e) + { + Log.ignore(e); + } + + return null; + } + + /* ------------------------------------------------------------ */ + /** Convert URL to Resource + * wrapper for {@link Resource#newResource(URL)} enables extensions to + * provide alternate resource implementations. + */ + public Resource newResource(URL url) throws IOException + { + return Resource.newResource(url); + } + + /* ------------------------------------------------------------ */ + /** Convert URL to Resource + * wrapper for {@link Resource#newResource(String)} enables extensions to + * provide alternate resource implementations. + */ + public Resource newResource(String url) throws IOException + { + return Resource.newResource(url); + } + + /* ------------------------------------------------------------ */ + /* + */ + public Set getResourcePaths(String path) + { + try + { + path=URIUtil.canonicalPath(path); + Resource resource=getResource(path); + + if (resource!=null && resource.exists()) + { + if (!path.endsWith(URIUtil.SLASH)) + path=path+URIUtil.SLASH; + + String[] l=resource.list(); + if (l!=null) + { + HashSet set = new HashSet(); + for(int i=0;i + * A partial implementation of {@link javax.servlet.ServletContext}. + * A complete implementation is provided by the derived {@link org.eclipse.jetty.servlet.ServletContextHandler.Context}. + *

    + * + * + */ + public class Context implements ServletContext + { + /* ------------------------------------------------------------ */ + protected Context() + { + } + + /* ------------------------------------------------------------ */ + public ContextHandler getContextHandler() + { + // TODO reduce visibility of this method + return ContextHandler.this; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getContext(java.lang.String) + */ + public ServletContext getContext(String uripath) + { + // TODO this is a very poor implementation! + // TODO move this to Server + ContextHandler context=null; + Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class); + for (int i=0;icontext.getContextPath().length()) + context=ch; + } + } + + if (context!=null) + return context._scontext; + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getMajorVersion() + */ + public int getMajorVersion() + { + return 3; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getMimeType(java.lang.String) + */ + public String getMimeType(String file) + { + if (_mimeTypes==null) + return null; + Buffer mime = _mimeTypes.getMimeByExtension(file); + if (mime!=null) + return mime.toString(); + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getMinorVersion() + */ + public int getMinorVersion() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String) + */ + public RequestDispatcher getNamedDispatcher(String name) + { + return null; + } + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String) + */ + public RequestDispatcher getRequestDispatcher(String uriInContext) + { + if (uriInContext == null) + return null; + + if (!uriInContext.startsWith("/")) + return null; + + try + { + String query=null; + int q=0; + if ((q=uriInContext.indexOf('?'))>0) + { + query=uriInContext.substring(q+1); + uriInContext=uriInContext.substring(0,q); + } + if ((q=uriInContext.indexOf(';'))>0) + uriInContext=uriInContext.substring(0,q); + + String pathInContext=URIUtil.canonicalPath(URIUtil.decodePath(uriInContext)); + String uri=URIUtil.addPaths(getContextPath(), uriInContext); + ContextHandler context=ContextHandler.this; + return new Dispatcher(context,uri, pathInContext, query); + } + catch(Exception e) + { + Log.ignore(e); + } + return null; + } + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getRealPath(java.lang.String) + */ + public String getRealPath(String path) + { + if(path==null) + return null; + if(path.length()==0) + path = URIUtil.SLASH; + else if(path.charAt(0)!='/') + path = URIUtil.SLASH + path; + + try + { + Resource resource=ContextHandler.this.getResource(path); + if(resource!=null) + { + File file = resource.getFile(); + if (file!=null) + return file.getCanonicalPath(); + } + } + catch (Exception e) + { + Log.ignore(e); + } + + return null; + } + + /* ------------------------------------------------------------ */ + public URL getResource(String path) throws MalformedURLException + { + Resource resource=ContextHandler.this.getResource(path); + if (resource!=null && resource.exists()) + return resource.getURL(); + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String) + */ + public InputStream getResourceAsStream(String path) + { + try + { + URL url=getResource(path); + if (url==null) + return null; + return url.openStream(); + } + catch(Exception e) + { + Log.ignore(e); + return null; + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String) + */ + public Set getResourcePaths(String path) + { + return ContextHandler.this.getResourcePaths(path); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServerInfo() + */ + public String getServerInfo() + { + return "jetty/"+Server.getVersion(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServlet(java.lang.String) + */ + public Servlet getServlet(String name) throws ServletException + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServletNames() + */ + @SuppressWarnings("unchecked") + public Enumeration getServletNames() + { + return Collections.enumeration(Collections.EMPTY_LIST); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServlets() + */ + @SuppressWarnings("unchecked") + public Enumeration getServlets() + { + return Collections.enumeration(Collections.EMPTY_LIST); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String) + */ + public void log(Exception exception, String msg) + { + _logger.warn(msg,exception); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#log(java.lang.String) + */ + public void log(String msg) + { + _logger.info(msg, null, null); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable) + */ + public void log(String message, Throwable throwable) + { + _logger.warn(message,throwable); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getInitParameter(java.lang.String) + */ + public String getInitParameter(String name) + { + return ContextHandler.this.getInitParameter(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getInitParameterNames() + */ + @SuppressWarnings("unchecked") + public Enumeration getInitParameterNames() + { + return ContextHandler.this.getInitParameterNames(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getAttribute(java.lang.String) + */ + public synchronized Object getAttribute(String name) + { + Object o = ContextHandler.this.getAttribute(name); + if (o==null && _contextAttributes!=null) + o=_contextAttributes.getAttribute(name); + return o; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getAttributeNames() + */ + @SuppressWarnings("unchecked") + public synchronized Enumeration getAttributeNames() + { + HashSet set = new HashSet(); + if (_contextAttributes!=null) + { + Enumeration e = _contextAttributes.getAttributeNames(); + while(e.hasMoreElements()) + set.add(e.nextElement()); + } + Enumeration e = _attributes.getAttributeNames(); + while(e.hasMoreElements()) + set.add(e.nextElement()); + + return Collections.enumeration(set); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object) + */ + public synchronized void setAttribute(String name, Object value) + { + + if (_contextAttributes==null) + { + // Set it on the handler + ContextHandler.this.setAttribute(name, value); + return; + } + + setManagedAttribute(name,value); + Object old_value=_contextAttributes==null?null:_contextAttributes.getAttribute(name); + + if (value==null) + _contextAttributes.removeAttribute(name); + else + _contextAttributes.setAttribute(name,value); + + if (_contextAttributeListeners!=null) + { + ServletContextAttributeEvent event = + new ServletContextAttributeEvent(_scontext,name, old_value==null?value:old_value); + + for(int i=0;i filterClass) + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addFilter(java.lang.String, java.lang.String) + */ + public FilterRegistration addFilter(String filterName, String className) + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.Class) + */ + public ServletRegistration addServlet(String servletName, Class servletClass) + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.String) + */ + public ServletRegistration addServlet(String servletName, String className) + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#findFilterRegistration(java.lang.String) + */ + public FilterRegistration findFilterRegistration(String filterName) + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#findServletRegistration(java.lang.String) + */ + public ServletRegistration findServletRegistration(String servletName) + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#getDefaultSessionTrackingModes() + */ + public EnumSet getDefaultSessionTrackingModes() + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#getEffectiveSessionTrackingModes() + */ + public EnumSet getEffectiveSessionTrackingModes() + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#getSessionCookieConfig() + */ + public SessionCookieConfig getSessionCookieConfig() + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#setSessionTrackingModes(java.util.EnumSet) + */ + public void setSessionTrackingModes(EnumSet sessionTrackingModes) + { + Log.warn("Use servlet Context"); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addFilter(java.lang.String, javax.servlet.Filter) + */ + public FilterRegistration addFilter(String filterName, Filter filter) + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, javax.servlet.Servlet) + */ + public ServletRegistration addServlet(String servletName, Servlet servlet) + { + Log.warn("Use servlet Context"); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#setSessionTrackingModes(java.util.Set) + */ + public void setSessionTrackingModes(Set sessionTrackingModes) + { + // TODO Auto-generated method stub + Log.warn("Not implemented"); + + } + + + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java new file mode 100644 index 00000000000..4c9a3464b45 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java @@ -0,0 +1,319 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.server.AsyncRequest; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** ContextHandlerCollection. + * + * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a + * {@link org.eclipse.jetty.http.servlet.PathMap} to it's contained handlers based + * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s. + * The contexts do not need to be directly contained, only children of the contained handlers. + * Multiple contexts may have the same context path and they are called in order until one + * handles the request. + * + * @org.apache.xbean.XBean element="contexts" + */ +public class ContextHandlerCollection extends HandlerCollection +{ + private PathMap _contextMap; + private Class _contextClass = ContextHandler.class; + + /* ------------------------------------------------------------ */ + /** + * Remap the context paths. + */ + public void mapContexts() + { + PathMap contextMap = new PathMap(); + Handler[] branches = getHandlers(); + + + for (int b=0;branches!=null && b=0 || contextPath.startsWith("*")) + throw new IllegalArgumentException ("Illegal context spec:"+contextPath); + + if(!contextPath.startsWith("/")) + contextPath='/'+contextPath; + + if (contextPath.length()>1) + { + if (contextPath.endsWith("/")) + contextPath+="*"; + else if (!contextPath.endsWith("/*")) + contextPath+="/*"; + } + + Object contexts=contextMap.get(contextPath); + String[] vhosts=handler.getVirtualHosts(); + + + if (vhosts!=null && vhosts.length>0) + { + Map hosts; + + if (contexts instanceof Map) + hosts=(Map)contexts; + else + { + hosts=new HashMap(); + hosts.put("*",contexts); + contextMap.put(contextPath, hosts); + } + + for (int j=0;j + // { virtual host => context } + // } + PathMap map = _contextMap; + if (map!=null && target!=null && target.startsWith("/")) + { + // first, get all contexts matched by context path + Object contexts = map.getLazyMatches(target); + + for (int i=0; i",">"); + + writer.write("\n\nError 404 - Not Found"); + writer.write("\n\n

    Error 404 - Not Found.

    \n"); + writer.write("No context on this server matched or handled this request.
    "); + writer.write("Contexts known to this server are:
      "); + + + Server server = getServer(); + Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class); + + for (int i=0;handlers!=null && i0) + writer.write("http://"+context.getVirtualHosts()[0]+":"+request.getLocalPort()); + writer.write(context.getContextPath()); + if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/")) + writer.write("/"); + writer.write("\">"); + writer.write(context.getContextPath()); + if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0) + writer.write(" @ "+context.getVirtualHosts()[0]+":"+request.getLocalPort()); + writer.write(" ---> "); + writer.write(context.toString()); + writer.write("\n"); + } + else + { + writer.write("
    • "); + writer.write(context.getContextPath()); + if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0) + writer.write(" @ "+context.getVirtualHosts()[0]+":"+request.getLocalPort()); + writer.write(" ---> "); + writer.write(context.toString()); + if (context.isFailed()) + writer.write(" [failed]"); + if (context.isStopped()) + writer.write(" [stopped]"); + writer.write("
    • \n"); + } + } + + for (int i=0;i<10;i++) + writer.write("\n"); + + writer.write("\n\n\n"); + writer.flush(); + response.setContentLength(writer.size()); + OutputStream out=response.getOutputStream(); + writer.writeTo(out); + out.close(); + + return; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns true if the handle can server the jetty favicon.ico + */ + public boolean getServeIcon() + { + return _serveIcon; + } + + /* ------------------------------------------------------------ */ + /** + * @param serveIcon true if the handle can server the jetty favicon.ico + */ + public void setServeIcon(boolean serveIcon) + { + _serveIcon = serveIcon; + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java new file mode 100644 index 00000000000..cdbd35f5c2f --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java @@ -0,0 +1,175 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.server.handler; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.util.ByteArrayISO8859Writer; +import org.eclipse.jetty.util.StringUtil; + + +/* ------------------------------------------------------------ */ +/** Handler for Error pages + * A handler that is registered at the org.eclipse.http.ErrorHandler + * context attributed and called by the HttpResponse.sendError method to write a + * error page. + * + * + */ +public class ErrorHandler extends AbstractHandler +{ + boolean _showStacks=true; + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + HttpConnection connection = HttpConnection.getCurrentConnection(); + connection.getRequest().setHandled(true); + String method = request.getMethod(); + if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST)) + return; + response.setContentType(MimeTypes.TEXT_HTML_8859_1); + response.setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); + ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096); + handleErrorPage(request, writer, connection.getResponse().getStatus(), connection.getResponse().getReason()); + writer.flush(); + response.setContentLength(writer.size()); + writer.writeTo(response.getOutputStream()); + writer.destroy(); + } + + /* ------------------------------------------------------------ */ + protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) + throws IOException + { + writeErrorPage(request, writer, code, message, _showStacks); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks) + throws IOException + { + if (message == null) + message=HttpStatus.getCode(code).getMessage(); + else + { + message= StringUtil.replace(message, "&", "&"); + message= StringUtil.replace(message, "<", "<"); + message= StringUtil.replace(message, ">", ">"); + } + + writer.write("\n\n"); + writeErrorPageHead(request,writer,code,message); + writer.write("\n"); + writeErrorPageBody(request,writer,code,message,showStacks); + writer.write("\n\n\n"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message) + throws IOException + { + writer.write("\n"); + writer.write("Error "); + writer.write(Integer.toString(code)); + writer.write(' '); + if (message!=null) + writer.write(message); + writer.write("\n"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks) + throws IOException + { + String uri= request.getRequestURI(); + if (uri!=null) + { + uri= StringUtil.replace(uri, "&", "&"); + uri= StringUtil.replace(uri, "<", "<"); + uri= StringUtil.replace(uri, ">", ">"); + } + + writeErrorPageMessage(request,writer,code,message,uri); + if (showStacks) + writeErrorPageStacks(request,writer); + writer.write("
      Powered by Jetty://"); + for (int i= 0; i < 20; i++) + writer.write("
      \n"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri) + throws IOException + { + writer.write("

      HTTP ERROR "); + writer.write(Integer.toString(code)); + writer.write("

      \n

      Problem accessing "); + writer.write(uri); + writer.write(". Reason:\n

          ");
      +        writer.write(message);
      +        writer.write("

      "); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageStacks(HttpServletRequest request, Writer writer) + throws IOException + { + Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception"); + while(th!=null) + { + writer.write("

      Caused by:

      ");
      +            StringWriter sw = new StringWriter();
      +            PrintWriter pw = new PrintWriter(sw);
      +            th.printStackTrace(pw);
      +            pw.flush();
      +            writer.write(sw.getBuffer().toString());
      +            writer.write("
      \n"); + + th =th.getCause(); + } + } + + + /* ------------------------------------------------------------ */ + /** + * @return True if stack traces are shown in the error pages + */ + public boolean isShowStacks() + { + return _showStacks; + } + + /* ------------------------------------------------------------ */ + /** + * @param showStacks True if stack traces are shown in the error pages + */ + public void setShowStacks(boolean showStacks) + { + _showStacks = showStacks; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java new file mode 100644 index 00000000000..51fefd5fb61 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java @@ -0,0 +1,220 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiException; + +/* ------------------------------------------------------------ */ +/** A collection of handlers. + *

      + * For derived HandlerCollections, the order or manner of calling + * the contained handlers is not defined. The default implementations + * calls all handlers in list order, regardless of + * the response status or exceptions. + *

      + * + * + * @org.apache.xbean.XBean + */ +public class HandlerCollection extends AbstractHandlerContainer +{ + private Handler[] _handlers; + + /* ------------------------------------------------------------ */ + public HandlerCollection() + { + super(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the handlers. + */ + public Handler[] getHandlers() + { + return _handlers; + } + + /* ------------------------------------------------------------ */ + /** + * + * @param handlers The handlers to set. + */ + public void setHandlers(Handler[] handlers) + { + Handler [] old_handlers = _handlers==null?null:(Handler[])_handlers.clone(); + + if (getServer()!=null) + getServer().getContainer().update(this, old_handlers, handlers, "handler"); + + Server server = getServer(); + MultiException mex = new MultiException(); + for (int i=0;handlers!=null && i0;) + try{_handlers[i].stop();}catch(Throwable e){mex.add(e);} + } + mex.ifExceptionThrow(); + } + + /* ------------------------------------------------------------ */ + public void setServer(Server server) + { + Server old_server=getServer(); + + super.setServer(server); + + Handler[] h=getHandlers(); + for (int i=0;h!=null && i0 ) + setHandlers((Handler[])LazyList.removeFromArray(handlers, handler)); + } + + /* ------------------------------------------------------------ */ + protected Object expandChildren(Object list, Class byClass) + { + Handler[] handlers = getHandlers(); + for (int i=0;handlers!=null && iHandlerWrapper acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and + * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the Decorator pattern. + * + */ +public class HandlerWrapper extends AbstractHandlerContainer +{ + private Handler _handler; + + /* ------------------------------------------------------------ */ + /** + * + */ + public HandlerWrapper() + { + super(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the handlers. + */ + public Handler getHandler() + { + return _handler; + } + + /* ------------------------------------------------------------ */ + /** + * @param handler Set the {@link Handler} which should be wrapped. + */ + public void setHandler(Handler handler) + { + try + { + Handler old_handler = _handler; + + if (getServer()!=null) + getServer().getContainer().update(this, old_handler, handler, "handler"); + + if (handler!=null) + { + handler.setServer(getServer()); + } + + _handler = handler; + + if (old_handler!=null) + { + if (old_handler.isStarted()) + old_handler.stop(); + } + } + catch(Exception e) + { + IllegalStateException ise= new IllegalStateException(); + ise.initCause(e); + throw ise; + } + } + + /* ------------------------------------------------------------ */ + /** Add a handler. + * This implementation of addHandler calls setHandler with the + * passed handler. If this HandlerWrapper had a previous wrapped + * handler, then it is passed to a call to addHandler on the passed + * handler. Thus this call can add a handler in a chain of + * wrapped handlers. + * + * @param handler + */ + public void addHandler(Handler handler) + { + Handler old = getHandler(); + if (old!=null && !(handler instanceof HandlerContainer)) + throw new IllegalArgumentException("Cannot add"); + setHandler(handler); + if (old!=null) + ((HandlerContainer)handler).addHandler(old); + } + + + public void removeHandler (Handler handler) + { + Handler old = getHandler(); + if (old!=null && (old instanceof HandlerContainer)) + ((HandlerContainer)old).removeHandler(handler); + else if (old!=null && handler==old) + setHandler(null); + else + throw new IllegalStateException("Cannot remove"); + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + if (_handler!=null) + _handler.start(); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStop() + */ + protected void doStop() throws Exception + { + super.doStop(); + if (_handler!=null) + _handler.stop(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (_handler!=null && isStarted()) + { + _handler.handle(target,request, response); + } + } + + + /* ------------------------------------------------------------ */ + public void setServer(Server server) + { + Server old_server=getServer(); + + super.setServer(server); + + Handler h=getHandler(); + if (h!=null) + h.setServer(server); + + if (server!=null && server!=old_server) + server.getContainer().update(this, null,_handler, "handler"); + } + + + /* ------------------------------------------------------------ */ + protected Object expandChildren(Object list, Class byClass) + { + return expandHandler(_handler,list,byClass); + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java new file mode 100644 index 00000000000..8854a5c6e57 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java @@ -0,0 +1,158 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.URIUtil; + +/* ------------------------------------------------------------ */ +/** Moved ContextHandler. + * This context can be used to replace a context that has changed + * location. Requests are redirected (either to a fixed URL or to a + * new context base). + */ +public class MovedContextHandler extends ContextHandler +{ + String _newContextURL; + boolean _discardPathInfo; + boolean _discardQuery; + boolean _permanent; + Redirector _redirector; + String _expires; + + public MovedContextHandler() + { + _redirector=new Redirector(); + addHandler(_redirector); + setAllowNullPathInfo(true); + } + + public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL) + { + super(parent,contextPath); + _newContextURL=newContextURL; + _redirector=new Redirector(); + addHandler(_redirector); + } + + public boolean isDiscardPathInfo() + { + return _discardPathInfo; + } + + public void setDiscardPathInfo(boolean discardPathInfo) + { + _discardPathInfo = discardPathInfo; + } + + public String getNewContextURL() + { + return _newContextURL; + } + + public void setNewContextURL(String newContextURL) + { + _newContextURL = newContextURL; + } + + public boolean isPermanent() + { + return _permanent; + } + + public void setPermanent(boolean permanent) + { + _permanent = permanent; + } + + public boolean isDiscardQuery() + { + return _discardQuery; + } + + public void setDiscardQuery(boolean discardQuery) + { + _discardQuery = discardQuery; + } + + private class Redirector extends AbstractHandler + { + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (_newContextURL==null) + return; + + Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + + String url = _newContextURL; + if (!_discardPathInfo && request.getPathInfo()!=null) + url=URIUtil.addPaths(url, request.getPathInfo()); + if (!_discardQuery && request.getQueryString()!=null) + url+="?"+request.getQueryString(); + + response.sendRedirect(url); + + String path=_newContextURL; + if (!_discardPathInfo && request.getPathInfo()!=null) + path=URIUtil.addPaths(path, request.getPathInfo()); + + StringBuilder location = URIUtil.hasScheme(path)?new StringBuilder():base_request.getRootURL(); + + location.append(path); + if (!_discardQuery && request.getQueryString()!=null) + { + location.append('?'); + location.append(request.getQueryString()); + } + + response.setHeader(HttpHeaders.LOCATION,location.toString()); + + if (_expires!=null) + response.setHeader(HttpHeaders.EXPIRES,_expires); + + response.setStatus(_permanent?HttpServletResponse.SC_MOVED_PERMANENTLY:HttpServletResponse.SC_FOUND); + response.setContentLength(0); + base_request.setHandled(true); + } + + } + + /* ------------------------------------------------------------ */ + /** + * @return the expires header value or null if no expires header + */ + public String getExpires() + { + return _expires; + } + + /* ------------------------------------------------------------ */ + /** + * @param expires the expires header value or null if no expires header + */ + public void setExpires(String expires) + { + _expires = expires; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java new file mode 100644 index 00000000000..bdfebd379a3 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java @@ -0,0 +1,132 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.log.Log; + + + +/** + * RequestLogHandler. + * This handler can be used to wrap an individual context for context logging. + * + * + * @org.apache.xbean.XBean + */ +public class RequestLogHandler extends HandlerWrapper +{ + private RequestLog _requestLog; + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + super.handle(target, request, response); + if (DispatcherType.REQUEST.equals(request.getDispatcherType()) && _requestLog!=null) + _requestLog.log((Request)request, (Response)response); + } + + /* ------------------------------------------------------------ */ + public void setRequestLog(RequestLog requestLog) + { + //are we changing the request log impl? + try + { + if (_requestLog != null) + _requestLog.stop(); + } + catch (Exception e) + { + Log.warn (e); + } + + if (getServer()!=null) + getServer().getContainer().update(this, _requestLog, requestLog, "logimpl",true); + + _requestLog = requestLog; + + //if we're already started, then start our request log + try + { + if (isStarted() && (_requestLog != null)) + _requestLog.start(); + } + catch (Exception e) + { + throw new RuntimeException (e); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#setServer(org.eclipse.jetty.server.server.Server) + */ + public void setServer(Server server) + { + if (_requestLog!=null) + { + if (getServer()!=null && getServer()!=server) + getServer().getContainer().update(this, _requestLog, null, "logimpl",true); + super.setServer(server); + if (server!=null && server!=getServer()) + server.getContainer().update(this, null,_requestLog, "logimpl",true); + } + else + super.setServer(server); + } + + /* ------------------------------------------------------------ */ + public RequestLog getRequestLog() + { + return _requestLog; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStart() + */ + protected void doStart() throws Exception + { + super.doStart(); + if (_requestLog!=null) + _requestLog.start(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStop() + */ + protected void doStop() throws Exception + { + super.doStop(); + if (_requestLog!=null) + _requestLog.stop(); + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java new file mode 100644 index 00000000000..73744ccd114 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -0,0 +1,335 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.MalformedURLException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.WriterOutputStream; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; + + +/* ------------------------------------------------------------ */ +/** Resource Handler. + * + * This handle will serve static content and handle If-Modified-Since headers. + * No caching is done. + * Requests that cannot be handled are let pass (Eg no 404's) + * + * + * @org.apache.xbean.XBean + */ +public class ResourceHandler extends AbstractHandler +{ + ContextHandler _context; + Resource _baseResource; + String[] _welcomeFiles={"index.html"}; + MimeTypes _mimeTypes = new MimeTypes(); + ByteArrayBuffer _cacheControl; + + /* ------------------------------------------------------------ */ + public ResourceHandler() + { + } + + /* ------------------------------------------------------------ */ + public MimeTypes getMimeTypes() + { + return _mimeTypes; + } + + /* ------------------------------------------------------------ */ + public void setMimeTypes(MimeTypes mimeTypes) + { + _mimeTypes = mimeTypes; + } + + /* ------------------------------------------------------------ */ + public void doStart() + throws Exception + { + Context scontext = ContextHandler.getCurrentContext(); + _context = (scontext==null?null:scontext.getContextHandler()); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the resourceBase. + */ + public Resource getBaseResource() + { + if (_baseResource==null) + return null; + return _baseResource; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the base resource as a string. + */ + public String getResourceBase() + { + if (_baseResource==null) + return null; + return _baseResource.toString(); + } + + + /* ------------------------------------------------------------ */ + /** + * @param base The resourceBase to set. + */ + public void setBaseResource(Resource base) + { + _baseResource=base; + } + + /* ------------------------------------------------------------ */ + /** + * @param resourceBase The base resource as a string. + */ + public void setResourceBase(String resourceBase) + { + try + { + setBaseResource(Resource.newResource(resourceBase)); + } + catch (Exception e) + { + Log.warn(e); + throw new IllegalArgumentException(resourceBase); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return the cacheControl header to set on all static content. + */ + public String getCacheControl() + { + return _cacheControl.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param cacheControl the cacheControl header to set on all static content. + */ + public void setCacheControl(String cacheControl) + { + _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl); + } + + /* ------------------------------------------------------------ */ + /* + */ + public Resource getResource(String path) throws MalformedURLException + { + if (path==null || !path.startsWith("/")) + throw new MalformedURLException(path); + + Resource base = _baseResource; + if (base==null) + { + if (_context==null) + return null; + base=_context.getBaseResource(); + if (base==null) + return null; + } + + try + { + path=URIUtil.canonicalPath(path); + Resource resource=base.addPath(path); + return resource; + } + catch(Exception e) + { + Log.ignore(e); + } + + return null; + } + + /* ------------------------------------------------------------ */ + protected Resource getResource(HttpServletRequest request) throws MalformedURLException + { + String path_info=request.getPathInfo(); + if (path_info==null) + return null; + return getResource(path_info); + } + + + /* ------------------------------------------------------------ */ + public String[] getWelcomeFiles() + { + return _welcomeFiles; + } + + /* ------------------------------------------------------------ */ + public void setWelcomeFiles(String[] welcomeFiles) + { + _welcomeFiles=welcomeFiles; + } + + /* ------------------------------------------------------------ */ + protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException + { + for (int i=0;i<_welcomeFiles.length;i++) + { + Resource welcome=directory.addPath(_welcomeFiles[i]); + if (welcome.exists() && !welcome.isDirectory()) + return welcome; + } + + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Request base_request = request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + if (base_request.isHandled()) + return; + + boolean skipContentBody = false; + if(!HttpMethods.GET.equals(request.getMethod())) + { + if(!HttpMethods.HEAD.equals(request.getMethod())) + return; + skipContentBody = true; + } + + Resource resource=getResource(request); + + if (resource==null || !resource.exists()) + return; + + // We are going to server something + base_request.setHandled(true); + + if (resource.isDirectory()) + { + if (!request.getPathInfo().endsWith(URIUtil.SLASH)) + { + response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)); + return; + } + resource=getWelcome(resource); + + if (resource==null || !resource.exists() || resource.isDirectory()) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + } + + // set some headers + long last_modified=resource.lastModified(); + if (last_modified>0) + { + long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE); + if (if_modified>0 && last_modified/1000<=if_modified/1000) + { + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + } + + Buffer mime=_mimeTypes.getMimeByExtension(resource.toString()); + if (mime==null) + mime=_mimeTypes.getMimeByExtension(request.getPathInfo()); + + // set the headers + doResponseHeaders(response,resource,mime!=null?mime.toString():null); + response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified); + if(skipContentBody) + return; + // Send the content + OutputStream out =null; + try {out = response.getOutputStream();} + catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());} + + // See if a short direct method can be used? + if (out instanceof HttpConnection.Output) + { + // TODO file mapped buffers + ((HttpConnection.Output)out).sendContent(resource.getInputStream()); + } + else + { + // Write content normally + resource.writeTo(out,0,resource.length()); + } + } + + /* ------------------------------------------------------------ */ + /** Set the response headers. + * This method is called to set the response headers such as content type and content length. + * May be extended to add additional headers. + * @param response + * @param resource + * @param mimeType + */ + protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType) + { + if (mimeType!=null) + response.setContentType(mimeType); + + long length=resource.length(); + + if (response instanceof Response) + { + HttpFields fields = ((Response)response).getHttpFields(); + + if (length>0) + fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length); + + if (_cacheControl!=null) + fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl); + } + else + { + if (length>0) + response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(length)); + + if (_cacheControl!=null) + response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString()); + } + + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java new file mode 100644 index 00000000000..1aa6c6b00a9 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java @@ -0,0 +1,369 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.AsyncRequest; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.LazyList; + +public class StatisticsHandler extends HandlerWrapper implements CompleteHandler +{ + transient long _statsStartedAt; + + transient int _requests; + + transient long _requestsDurationMin; // min request duration + transient long _requestsDurationMax; // max request duration + transient long _requestsDurationTotal; // total request duration + transient long _requestsActiveDurationMin; // min request active duration + transient long _requestsActiveDurationMax; // max request active duration + transient long _requestsActiveDurationTotal; // total request active duration + + transient int _requestsActive; + transient int _requestsActiveMin; // min number of connections handled simultaneously + transient int _requestsActiveMax; + transient int _requestsResumed; + transient int _requestsTimedout; // requests that timed out while suspended + transient int _responses1xx; // Informal + transient int _responses2xx; // Success + transient int _responses3xx; // Redirection + transient int _responses4xx; // Client Error + transient int _responses5xx; // Server Error + + transient long _responsesBytesTotal; + + /* ------------------------------------------------------------ */ + public void statsReset() + { + synchronized(this) + { + if (isStarted()) + _statsStartedAt=System.currentTimeMillis(); + _requests=0; + _responses1xx=0; + _responses2xx=0; + _responses3xx=0; + _responses4xx=0; + _responses5xx=0; + + _requestsActiveMin=_requestsActive; + _requestsActiveMax=_requestsActive; + + _requestsDurationMin=0; + _requestsDurationMax=0; + _requestsDurationTotal=0; + + _requestsActiveDurationMin=0; + _requestsActiveDurationMax=0; + _requestsActiveDurationTotal=0; + } + } + + + /* ------------------------------------------------------------ */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + final Response base_response=(response instanceof Response)?((Response)response):HttpConnection.getCurrentConnection().getResponse(); + + long timestamp0=base_request.getTimeStamp(); + long timestamp1=timestamp0; + try + { + synchronized(this) + { + AsyncRequest asyncContextState=base_request.getAsyncRequest(); + + if(asyncContextState==null) + { + _requests++; + } + else + { + if(asyncContextState.isInitial()) + _requests++; + else + { + timestamp1=System.currentTimeMillis(); + /* + if (asyncContextState.isTimeout()) + _requestsTimedout++; + if(asyncContextState.isResumed()) + _requestsResumed++; + */ + } + } + + _requestsActive++; + if (_requestsActive>_requestsActiveMax) + _requestsActiveMax=_requestsActive; + } + + super.handle(target, request, response); + } + finally + { + synchronized(this) + { + _requestsActive--; + if (_requestsActive<0) + _requestsActive=0; + if (_requestsActive < _requestsActiveMin) + _requestsActiveMin=_requestsActive; + + long duration = System.currentTimeMillis()-timestamp1; + _requestsActiveDurationTotal+=duration; + if (_requestsActiveDurationMin==0 || duration<_requestsActiveDurationMin) + _requestsActiveDurationMin=duration; + if (duration>_requestsActiveDurationMax) + _requestsActiveDurationMax=duration; + + + if(request.isAsyncStarted()) + { + Object list = base_request.getAttribute(COMPLETE_HANDLER_ATTR); + base_request.setAttribute(COMPLETE_HANDLER_ATTR, LazyList.add(list, this)); + } + else + { + duration = System.currentTimeMillis()-timestamp0; + addRequestsDurationTotal(duration); + + switch(base_response.getStatus()/100) + { + case 1: _responses1xx++;break; + case 2: _responses2xx++;break; + case 3: _responses3xx++;break; + case 4: _responses4xx++;break; + case 5: _responses5xx++;break; + } + + _responsesBytesTotal += base_response.getContentCount(); + } + } + } + } + + /* ------------------------------------------------------------ */ + protected void doStart() throws Exception + { + super.doStart(); + _statsStartedAt=System.currentTimeMillis(); + } + + /* ------------------------------------------------------------ */ + protected void doStop() throws Exception + { + super.doStop(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of requests handled by this context + * since last call of statsReset(), not counting resumed requests. + * If setStatsOn(false) then this is undefined. + */ + public int getRequests() {return _requests;} + + /* ------------------------------------------------------------ */ + /** + * @return Number of requests currently active. + * Undefined if setStatsOn(false). + */ + public int getRequestsActive() {return _requestsActive;} + + /* ------------------------------------------------------------ */ + /** + * @return Number of requests that have been resumed. + * Undefined if setStatsOn(false). + */ + public int getRequestsResumed() {return _requestsResumed;} + + /* ------------------------------------------------------------ */ + /** + * @return Number of requests that timed out while suspended. + * Undefined if setStatsOn(false). + */ + public int getRequestsTimedout() {return _requestsTimedout;} + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of active requests + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getRequestsActiveMax() {return _requestsActiveMax;} + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of responses with a 2xx status returned + * by this context since last call of statsReset(). Undefined if + * if setStatsOn(false). + */ + public int getResponses1xx() {return _responses1xx;} + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of responses with a 100 status returned + * by this context since last call of statsReset(). Undefined if + * if setStatsOn(false). + */ + public int getResponses2xx() {return _responses2xx;} + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of responses with a 3xx status returned + * by this context since last call of statsReset(). Undefined if + * if setStatsOn(false). + */ + public int getResponses3xx() {return _responses3xx;} + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of responses with a 4xx status returned + * by this context since last call of statsReset(). Undefined if + * if setStatsOn(false). + */ + public int getResponses4xx() {return _responses4xx;} + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of responses with a 5xx status returned + * by this context since last call of statsReset(). Undefined if + * if setStatsOn(false). + */ + public int getResponses5xx() {return _responses5xx;} + + /* ------------------------------------------------------------ */ + /** + * @return Timestamp stats were started at. + */ + public long getStatsOnMs() + { + return System.currentTimeMillis()-_statsStartedAt; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestsActiveMin. + */ + public int getRequestsActiveMin() + { + return _requestsActiveMin; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestsDurationMin. + */ + public long getRequestsDurationMin() + { + return _requestsDurationMin; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestsDurationTotal. + */ + public long getRequestsDurationTotal() + { + return _requestsDurationTotal; + } + + /* ------------------------------------------------------------ */ + /** + * @return Average duration of request handling in milliseconds + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getRequestsDurationAve() {return _requests==0?0:(_requestsDurationTotal/_requests);} + + /* ------------------------------------------------------------ */ + /** + * @return Get maximum duration in milliseconds of request handling + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getRequestsDurationMax() {return _requestsDurationMax;} + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestsActiveDurationMin. + */ + public long getRequestsActiveDurationMin() + { + return _requestsActiveDurationMin; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestsActiveDurationTotal. + */ + public long getRequestsActiveDurationTotal() + { + return _requestsActiveDurationTotal; + } + + /* ------------------------------------------------------------ */ + /** + * @return Average duration of request handling in milliseconds + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getRequestsActiveDurationAve() {return _requests==0?0:(_requestsActiveDurationTotal/_requests);} + + /* ------------------------------------------------------------ */ + /** + * @return Get maximum duration in milliseconds of request handling + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getRequestsActiveDurationMax() {return _requestsActiveDurationMax;} + + /* ------------------------------------------------------------ */ + /** + * @return Total bytes of content sent in responses + */ + public long getResponsesBytesTotal() {return _responsesBytesTotal; } + + private void addRequestsDurationTotal(long duration) + { + synchronized(this) + { + _requestsDurationTotal+=duration; + if (_requestsDurationMin==0 || duration<_requestsDurationMin) + _requestsDurationMin=duration; + if (duration>_requestsDurationMax) + _requestsDurationMax=duration; + } + } + + + /* ------------------------------------------------------------ */ + /** + * Handle completed requests. + * + * @param request + * the request which has just completed + */ + public void complete(Request request) + { + long duration = System.currentTimeMillis() - request.getTimeStamp(); + addRequestsDurationTotal(duration); + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java new file mode 100644 index 00000000000..6876144b714 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java @@ -0,0 +1,77 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +/** + * + */ +package org.eclipse.jetty.server.nio; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.NIOBuffer; +import org.eclipse.jetty.server.AbstractConnector; + +/* ------------------------------------------------------------ */ +/** + * + * + */ +public abstract class AbstractNIOConnector extends AbstractConnector implements NIOConnector +{ + private boolean _useDirectBuffers=true; + + /* ------------------------------------------------------------------------------- */ + public boolean getUseDirectBuffers() + { + return _useDirectBuffers; + } + + /* ------------------------------------------------------------------------------- */ + /** + * @param direct If True (the default), the connector can use NIO direct buffers. + * Some JVMs have memory management issues (bugs) with direct buffers. + */ + public void setUseDirectBuffers(boolean direct) + { + _useDirectBuffers=direct; + } + + /* ------------------------------------------------------------------------------- */ + public Buffer newBuffer(int size) + { + // TODO + // Header buffers always byte array buffers (efficiency of random access) + // There are lots of things to consider here... DIRECT buffers are faster to + // send but more expensive to build and access! so we have choices to make... + // + headers are constructed bit by bit and parsed bit by bit, so INDiRECT looks + // good for them. + // + but will a gather write of an INDIRECT header with a DIRECT body be any good? + // this needs to be benchmarked. + // + Will it be possible to get a DIRECT header buffer just for the gather writes of + // content from file mapped buffers? + // + Are gather writes worth the effort? Maybe they will work well with two INDIRECT + // buffers being copied into a single kernel buffer? + // + Buffer buf = null; + if (size==getHeaderBufferSize()) + buf= new IndirectNIOBuffer(size); + else + buf = _useDirectBuffers + ?(NIOBuffer)new DirectNIOBuffer(size) + :(NIOBuffer)new IndirectNIOBuffer(size); + return buf; + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java new file mode 100644 index 00000000000..393bfef14d1 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java @@ -0,0 +1,189 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.channels.ByteChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.io.nio.ChannelEndPoint; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------------------------- */ +/** Blocking NIO connector. + * This connector uses efficient NIO buffers with a traditional blocking thread model. + * Direct NIO buffers are used and a thread is allocated per connections. + * + * This connector is best used when there are a few very active connections. + * + * @org.apache.xbean.XBean element="blockingNioConnector" description="Creates a blocking NIO based socket connector" + * + * + * + */ +public class BlockingChannelConnector extends AbstractNIOConnector +{ + private transient ServerSocketChannel _acceptChannel; + + /* ------------------------------------------------------------ */ + /** Constructor. + * + */ + public BlockingChannelConnector() + { + } + + /* ------------------------------------------------------------ */ + public Object getConnection() + { + return _acceptChannel; + } + + /* ------------------------------------------------------------ */ + public void open() throws IOException + { + // Create a new server socket and set to non blocking mode + _acceptChannel= ServerSocketChannel.open(); + _acceptChannel.configureBlocking(true); + + // Bind the server socket to the local host and port + InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort()); + _acceptChannel.socket().bind(addr,getAcceptQueueSize()); + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + if (_acceptChannel != null) + _acceptChannel.close(); + _acceptChannel=null; + } + + /* ------------------------------------------------------------ */ + public void accept(int acceptorID) + throws IOException, InterruptedException + { + SocketChannel channel = _acceptChannel.accept(); + channel.configureBlocking(true); + Socket socket=channel.socket(); + configure(socket); + + Connection connection=new Connection(channel); + connection.dispatch(); + } + + /* ------------------------------------------------------------------------------- */ + public void customize(EndPoint endpoint, Request request) + throws IOException + { + Connection connection = (Connection)endpoint; + if (connection._sotimeout!=_maxIdleTime) + { + connection._sotimeout=_maxIdleTime; + ((SocketChannel)endpoint.getTransport()).socket().setSoTimeout(_maxIdleTime); + } + + super.customize(endpoint, request); + configure(((SocketChannel)endpoint.getTransport()).socket()); + } + + + /* ------------------------------------------------------------------------------- */ + public int getLocalPort() + { + if (_acceptChannel==null || !_acceptChannel.isOpen()) + return -1; + return _acceptChannel.socket().getLocalPort(); + } + + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + private class Connection extends ChannelEndPoint implements Runnable + { + boolean _dispatched=false; + HttpConnection _connection; + int _sotimeout; + + Connection(ByteChannel channel) + { + super(channel); + _connection = new HttpConnection(BlockingChannelConnector.this,this,getServer()); + } + + void dispatch() throws IOException + { + if (!getThreadPool().dispatch(this)) + { + Log.warn("dispatch failed for {}",_connection); + close(); + } + } + + public void run() + { + try + { + connectionOpened(_connection); + + while (isOpen()) + { + if (_connection.isIdle()) + { + if (getServer().getThreadPool().isLowOnThreads()) + { + if (_sotimeout!=getLowResourceMaxIdleTime()) + { + _sotimeout=getLowResourceMaxIdleTime(); + ((SocketChannel)getTransport()).socket().setSoTimeout(_sotimeout); + } + } + } + _connection.handle(); + } + } + catch (EofException e) + { + Log.debug("EOF", e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + catch (HttpException e) + { + Log.debug("BAD", e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + catch(Throwable e) + { + Log.warn("handle failed",e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + finally + { + connectionClosed(_connection); + } + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java new file mode 100644 index 00000000000..fca291ba7ee --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java @@ -0,0 +1,66 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.nio; + +import java.io.IOException; +import java.nio.channels.Channel; +import java.nio.channels.ServerSocketChannel; + +import org.eclipse.jetty.util.log.Log; + +/** + * An implementation of the SelectChannelConnector which first tries to + * inherit from a channel provided by the system. If there is no inherited + * channel available, or if the inherited channel provided not usable, then + * it will fall back upon normal ServerSocketChannel creation. + *

      + * Note that System.inheritedChannel() is only available from Java 1.5 onwards. + * Trying to use this class under Java 1.4 will be the same as using a normal + * SelectChannelConnector. + *

      + * Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port + * used to access pages on the Jetty instance is the same as the port used to + * launch Jetty. + * + * @author athena + */ +public class InheritedChannelConnector extends SelectChannelConnector +{ + /* ------------------------------------------------------------ */ + public void open() throws IOException + { + synchronized(this) + { + try + { + Channel channel = System.inheritedChannel(); + if ( channel instanceof ServerSocketChannel ) + _acceptChannel = (ServerSocketChannel)channel; + else + Log.warn("Unable to use System.inheritedChannel() [" +channel+ "]. Trying a new ServerSocketChannel at " + getHost() + ":" + getPort()); + + if ( _acceptChannel != null ) + _acceptChannel.configureBlocking(false); + } + catch(NoSuchMethodError e) + { + Log.warn("Need at least Java 5 to use socket inherited from xinetd/inetd."); + } + + if (_acceptChannel == null) + super.open(); + } + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java new file mode 100644 index 00000000000..16d27566fa6 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java @@ -0,0 +1,26 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.nio; + +/** + * NIOConnector. + * A marker interface that indicates that NIOBuffers can be handled (efficiently) by this Connector. + * + * + * + */ +public interface NIOConnector +{ + boolean getUseDirectBuffers(); +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java new file mode 100644 index 00000000000..14ad86adee8 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java @@ -0,0 +1,328 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.channels.SelectionKey; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectorManager; +import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.thread.Timeout.Task; + +/* ------------------------------------------------------------------------------- */ +/** + * Selecting NIO connector. + *

      + * This connector uses efficient NIO buffers with a non blocking threading model. Direct NIO buffers + * are used and threads are only allocated to connections with requests. Synchronization is used to + * simulate blocking for the servlet API, and any unflushed content at the end of request handling + * is written asynchronously. + *

      + *

      + * This connector is best used when there are a many connections that have idle periods. + *

      + *

      + * When used with {@link org.eclipse.jetty.util.ajax.Continuation}, threadless waits are supported. When + * a filter or servlet calls getEvent on a Continuation, a {@link org.eclipse.jetty.server.RetryRequest} + * runtime exception is thrown to allow the thread to exit the current request handling. Jetty will + * catch this exception and will not send a response to the client. Instead the thread is released + * and the Continuation is placed on the timer queue. If the Continuation timeout expires, or it's + * resume method is called, then the request is again allocated a thread and the request is retried. + * The limitation of this approach is that request content is not available on the retried request, + * thus if possible it should be read after the continuation or saved as a request attribute or as the + * associated object of the Continuation instance. + *

      + * + * @org.apache.xbean.XBean element="nioConnector" description="Creates an NIO based socket connector" + * + * + * + */ +public class SelectChannelConnector extends AbstractNIOConnector +{ + protected transient ServerSocketChannel _acceptChannel; + private long _lowResourcesConnections; + private long _lowResourcesMaxIdleTime; + + private SelectorManager _manager = new SelectorManager() + { + protected SocketChannel acceptChannel(SelectionKey key) throws IOException + { + // TODO handle max connections + SocketChannel channel = ((ServerSocketChannel)key.channel()).accept(); + if (channel==null) + return null; + channel.configureBlocking(false); + Socket socket = channel.socket(); + configure(socket); + return channel; + } + + public boolean dispatch(Runnable task) + { + return getThreadPool().dispatch(task); + } + + protected void endPointClosed(SelectChannelEndPoint endpoint) + { + // TODO handle max connections and low resources + connectionClosed((HttpConnection)endpoint.getConnection()); + } + + protected void endPointOpened(SelectChannelEndPoint endpoint) + { + // TODO handle max connections and low resources + connectionOpened((HttpConnection)endpoint.getConnection()); + } + + protected Connection newConnection(SocketChannel channel,SelectChannelEndPoint endpoint) + { + return SelectChannelConnector.this.newConnection(channel,endpoint); + } + + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey sKey) throws IOException + { + return SelectChannelConnector.this.newEndPoint(channel,selectSet,sKey); + } + }; + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + * + */ + public SelectChannelConnector() + { + } + + /* ------------------------------------------------------------ */ + public void accept(int acceptorID) throws IOException + { + _manager.doSelect(acceptorID); + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + synchronized(this) + { + if(_manager.isRunning()) + { + try + { + _manager.stop(); + } + catch (Exception e) + { + Log.warn(e); + } + } + if (_acceptChannel != null) + _acceptChannel.close(); + _acceptChannel = null; + } + } + + /* ------------------------------------------------------------------------------- */ + @Override + public void customize(EndPoint endpoint, Request request) throws IOException + { + SelectChannelEndPoint cep = ((SelectChannelEndPoint)endpoint); + cep.cancelIdle(); + request.setTimeStamp(cep.getSelectSet().getNow()); + super.customize(endpoint, request); + } + + /* ------------------------------------------------------------------------------- */ + @Override + public void persist(EndPoint endpoint) throws IOException + { + ((SelectChannelEndPoint)endpoint).scheduleIdle(); + super.persist(endpoint); + } + + /* ------------------------------------------------------------ */ + public Object getConnection() + { + return _acceptChannel; + } + + /* ------------------------------------------------------------------------------- */ + public int getLocalPort() + { + synchronized(this) + { + if (_acceptChannel==null || !_acceptChannel.isOpen()) + return -1; + return _acceptChannel.socket().getLocalPort(); + } + } + + /* ------------------------------------------------------------ */ + public void open() throws IOException + { + synchronized(this) + { + if (_acceptChannel == null) + { + // Create a new server socket + _acceptChannel = ServerSocketChannel.open(); + + // Bind the server socket to the local host and port + _acceptChannel.socket().setReuseAddress(getReuseAddress()); + InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort()); + _acceptChannel.socket().bind(addr,getAcceptQueueSize()); + + // Set to non blocking mode + _acceptChannel.configureBlocking(false); + + } + } + } + + /* ------------------------------------------------------------ */ + public void setMaxIdleTime(int maxIdleTime) + { + _manager.setMaxIdleTime(maxIdleTime); + super.setMaxIdleTime(maxIdleTime); + } + + /* ------------------------------------------------------------ */ + /** + * @return the lowResourcesConnections + */ + public long getLowResourcesConnections() + { + return _lowResourcesConnections; + } + + /* ------------------------------------------------------------ */ + /** + * Set the number of connections, which if exceeded places this manager in low resources state. + * This is not an exact measure as the connection count is averaged over the select sets. + * @param lowResourcesConnections the number of connections + * @see {@link #setLowResourcesMaxIdleTime(long)} + */ + public void setLowResourcesConnections(long lowResourcesConnections) + { + _lowResourcesConnections=lowResourcesConnections; + } + + /* ------------------------------------------------------------ */ + /** + * @return the lowResourcesMaxIdleTime + */ + public long getLowResourcesMaxIdleTime() + { + return _lowResourcesMaxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * Set the period in ms that a connection is allowed to be idle when this there are more + * than {@link #getLowResourcesConnections()} connections. This allows the server to rapidly close idle connections + * in order to gracefully handle high load situations. + * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low. + * @see {@link #setMaxIdleTime(long)} + * @deprecated use {@link #setLowResourceMaxIdleTime(int)} + */ + public void setLowResourcesMaxIdleTime(long lowResourcesMaxIdleTime) + { + _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime; + super.setLowResourceMaxIdleTime((int)lowResourcesMaxIdleTime); // TODO fix the name duplications + } + + /* ------------------------------------------------------------ */ + /** + * Set the period in ms that a connection is allowed to be idle when this there are more + * than {@link #getLowResourcesConnections()} connections. This allows the server to rapidly close idle connections + * in order to gracefully handle high load situations. + * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low. + * @see {@link #setMaxIdleTime(long)} + */ + public void setLowResourceMaxIdleTime(int lowResourcesMaxIdleTime) + { + _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime; + super.setLowResourceMaxIdleTime(lowResourcesMaxIdleTime); + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.AbstractConnector#doStart() + */ + protected void doStart() throws Exception + { + _manager.setSelectSets(getAcceptors()); + _manager.setMaxIdleTime(getMaxIdleTime()); + _manager.setLowResourcesConnections(getLowResourcesConnections()); + _manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime()); + _manager.start(); + open(); + _manager.register(_acceptChannel); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.AbstractConnector#doStop() + */ + protected void doStop() throws Exception + { + super.doStop(); + } + + /* ------------------------------------------------------------ */ + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException + { + return new SelectChannelEndPoint(channel,selectSet,key) + { + // TODO remove this hack + public boolean isReadyForDispatch() + { + Request request = ((HttpConnection)getConnection()).getRequest(); + return super.isReadyForDispatch() && !(request.getAsyncRequest().isSuspended()); + } + }; + } + + /* ------------------------------------------------------------------------------- */ + protected Connection newConnection(SocketChannel channel,final SelectChannelEndPoint endpoint) + { + return new HttpConnection(SelectChannelConnector.this,endpoint,getServer()) + { + /* ------------------------------------------------------------ */ + public void cancelTimeout(Task task) + { + endpoint.getSelectSet().cancelTimeout(task); + } + + /* ------------------------------------------------------------ */ + public void scheduleTimeout(Task task, long timeoutMs) + { + endpoint.getSelectSet().scheduleTimeout(task,timeoutMs); + } + }; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java new file mode 100644 index 00000000000..f02388c7dcc --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java @@ -0,0 +1,161 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.session; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionIdManager; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + +public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager +{ + private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId"; + protected final static String SESSION_ID_RANDOM_ALGORITHM = "SHA1PRNG"; + protected final static String SESSION_ID_RANDOM_ALGORITHM_ALT = "IBMSecureRandom"; + + protected Random _random; + protected boolean _weakRandom; + protected String _workerName; + protected Server _server; + + + public AbstractSessionIdManager(Server server) + { + _server=server; + } + + + public AbstractSessionIdManager(Server server, Random random) + { + _random=random; + _server=server; + } + + public String getWorkerName() + { + return _workerName; + } + + public void setWorkerName (String name) + { + _workerName=name; + } + + /* ------------------------------------------------------------ */ + public Random getRandom() + { + return _random; + } + + /* ------------------------------------------------------------ */ + public void setRandom(Random random) + { + _random=random; + _weakRandom=false; + } + /** + * Create a new session id if necessary. + * + * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long) + */ + public String newSessionId(HttpServletRequest request, long created) + { + synchronized (this) + { + // A requested session ID can only be used if it is in use already. + String requested_id=request.getRequestedSessionId(); + if (requested_id!=null) + { + String cluster_id=getClusterId(requested_id); + if (idInUse(cluster_id)) + return cluster_id; + } + + // Else reuse any new session ID already defined for this request. + String new_id=(String)request.getAttribute(__NEW_SESSION_ID); + if (new_id!=null&&idInUse(new_id)) + return new_id; + + + + // pick a new unique ID! + String id=null; + while (id==null||id.length()==0||idInUse(id)) + { + long r=_weakRandom + ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32)) + :_random.nextLong(); + r^=created; + if (request!=null && request.getRemoteAddr()!=null) + r^=request.getRemoteAddr().hashCode(); + if (r<0) + r=-r; + id=Long.toString(r,36); + + //add in the id of the node to ensure unique id across cluster + //NOTE this is different to the node suffix which denotes which node the request was received on + id=_workerName + id; + } + + request.setAttribute(__NEW_SESSION_ID,id); + return id; + } + } + + + public void doStart() + { + initRandom(); + } + + + + + /** + * Set up a random number generator for the sessionids. + * + * By preference, use a SecureRandom but allow to be injected. + */ + public void initRandom () + { + if (_random==null) + { + try + { + _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM); + } + catch (NoSuchAlgorithmException e) + { + try + { + _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM_ALT); + _weakRandom=false; + } + catch (NoSuchAlgorithmException e_alt) + { + Log.warn("Could not generate SecureRandom for session-id randomness",e); + _random=new Random(); + _weakRandom=true; + } + } + } + _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory()); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java new file mode 100644 index 00000000000..d346aa78e66 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java @@ -0,0 +1,1182 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.session; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionActivationListener; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; +import javax.servlet.http.HttpSessionContext; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionIdManager; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.component.AbstractLifeCycle; + +/* ------------------------------------------------------------ */ +/** + * An Abstract implementation of SessionManager. The partial implementation of + * SessionManager interface provides the majority of the handling required to + * implement a SessionManager. Concrete implementations of SessionManager based + * on AbstractSessionManager need only implement the newSession method to return + * a specialized version of the Session inner class that provides an attribute + * Map. + *

      + * If the property + * org.eclipse.jetty.servlet.AbstractSessionManager.23Notifications is set to + * true, the 2.3 servlet spec notification style will be used. + *

      + * + * + */ +public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager +{ + /* ------------------------------------------------------------ */ + public final static int __distantFuture=60*60*24*7*52*20; + + private static final HttpSessionContext __nullSessionContext=new NullSessionContext(); + + private boolean _usingCookies=true; + + /* ------------------------------------------------------------ */ + // Setting of max inactive interval for new sessions + // -1 means no timeout + protected int _dftMaxIdleSecs=-1; + protected SessionHandler _sessionHandler; + protected boolean _httpOnly=false; + protected int _maxSessions=0; + + protected int _minSessions=0; + protected SessionIdManager _sessionIdManager; + protected boolean _secureCookies=false; + protected Object _sessionAttributeListeners; + protected Object _sessionListeners; + + protected ClassLoader _loader; + protected ContextHandler.Context _context; + protected String _sessionCookie=__DefaultSessionCookie; + protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName; + protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"="; + protected String _sessionDomain; + protected String _sessionPath; + protected int _maxCookieAge=-1; + protected int _refreshCookieAge; + protected boolean _nodeIdInSessionId; + + /* ------------------------------------------------------------ */ + public AbstractSessionManager() + { + } + + /* ------------------------------------------------------------ */ + public Cookie access(HttpSession session,boolean secure) + { + long now=System.currentTimeMillis(); + + Session s = ((SessionIf)session).getSession(); + s.access(now); + + // Do we need to refresh the cookie? + if (isUsingCookies() && + (s.isIdChanged() || + (getMaxCookieAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge())) + ) + ) + { + Cookie cookie=getSessionCookie(session,_context.getContextPath(),secure); + s.cookieSet(); + s.setIdChanged(false); + return cookie; + } + + return null; + } + + /* ------------------------------------------------------------ */ + public void addEventListener(EventListener listener) + { + if (listener instanceof HttpSessionAttributeListener) + _sessionAttributeListeners=LazyList.add(_sessionAttributeListeners,listener); + if (listener instanceof HttpSessionListener) + _sessionListeners=LazyList.add(_sessionListeners,listener); + } + + /* ------------------------------------------------------------ */ + public void clearEventListeners() + { + _sessionAttributeListeners=null; + _sessionListeners=null; + } + + /* ------------------------------------------------------------ */ + public void complete(HttpSession session) + { + Session s = ((SessionIf)session).getSession(); + s.complete(); + } + + /* ------------------------------------------------------------ */ + public void doStart() throws Exception + { + _context=ContextHandler.getCurrentContext(); + _loader=Thread.currentThread().getContextClassLoader(); + + if (_sessionIdManager==null) + { + Server server=getSessionHandler().getServer(); + synchronized (server) + { + _sessionIdManager=server.getSessionIdManager(); + if (_sessionIdManager==null) + { + _sessionIdManager=new HashSessionIdManager(); + server.setSessionIdManager(_sessionIdManager); + } + } + } + if (!_sessionIdManager.isStarted()) + _sessionIdManager.start(); + + // Look for a session cookie name + String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty); + if (tmp!=null) + _sessionCookie=tmp; + + tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty); + if (tmp!=null) + { + setSessionIdPathParameterName(tmp); + } + + // set up the max session cookie age if it isn't already + if (_maxCookieAge==-1) + { + if (_context!=null) + { + String str=_context.getInitParameter(SessionManager.__MaxAgeProperty); + if (str!=null) + _maxCookieAge=Integer.parseInt(str.trim()); + } + } + // set up the session domain if it isn't already + if (_sessionDomain==null) + { + // only try the context initParams + if (_context!=null) + _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty); + } + + // set up the sessionPath if it isn't already + if (_sessionPath==null) + { + // only the context initParams + if (_context!=null) + _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty); + } + + super.doStart(); + } + + /* ------------------------------------------------------------ */ + public void doStop() throws Exception + { + super.doStop(); + + invalidateSessions(); + + _loader=null; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the httpOnly. + */ + public boolean getHttpOnly() + { + return _httpOnly; + } + + /* ------------------------------------------------------------ */ + public HttpSession getHttpSession(String nodeId) + { + String cluster_id = getIdManager().getClusterId(nodeId); + + synchronized (this) + { + Session session = getSession(cluster_id); + + if (session!=null && !session.getNodeId().equals(nodeId)) + session.setIdChanged(true); + return session; + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * @return Returns the metaManager used for cross context session management + */ + public SessionIdManager getIdManager() + { + return _sessionIdManager; + } + + /* ------------------------------------------------------------ */ + public int getMaxCookieAge() + { + return _maxCookieAge; + } + + /* ------------------------------------------------------------ */ + /** + * @return seconds + */ + public int getMaxInactiveInterval() + { + return _dftMaxIdleSecs; + } + + /* ------------------------------------------------------------ */ + public int getMaxSessions() + { + return _maxSessions; + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated use {@link #getIdManager()} + */ + public SessionIdManager getMetaManager() + { + return getIdManager(); + } + + /* ------------------------------------------------------------ */ + public int getMinSessions() + { + return _minSessions; + } + + /* ------------------------------------------------------------ */ + public int getRefreshCookieAge() + { + return _refreshCookieAge; + } + + + /* ------------------------------------------------------------ */ + /** + * @return Returns the secureCookies. + */ + public boolean getSecureCookies() + { + return _secureCookies; + } + + /* ------------------------------------------------------------ */ + public String getSessionCookie() + { + return _sessionCookie; + } + + /* ------------------------------------------------------------ */ + public Cookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure) + { + if (isUsingCookies()) + { + String id = getNodeId(session); + Cookie cookie=new Cookie(_sessionCookie,id); + cookie.setHttpOnly(getHttpOnly()); + + cookie.setPath((contextPath==null||contextPath.length()==0)?"/":contextPath); + cookie.setMaxAge(getMaxCookieAge()); + cookie.setSecure(requestIsSecure&&getSecureCookies()); + + // set up the overrides + if (_sessionDomain!=null) + cookie.setDomain(_sessionDomain); + if (_sessionPath!=null) + cookie.setPath(_sessionPath); + + return cookie; + } + return null; + } + + public String getSessionDomain() + { + return _sessionDomain; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionHandler. + */ + public SessionHandler getSessionHandler() + { + return _sessionHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated. Need to review if it is needed. + */ + public abstract Map getSessionMap(); + + /* ------------------------------------------------------------ */ + public String getSessionPath() + { + return _sessionPath; + } + + /* ------------------------------------------------------------ */ + public abstract int getSessions(); + + /* ------------------------------------------------------------ */ + public String getSessionIdPathParameterName() + { + return _sessionIdPathParameterName; + } + + /* ------------------------------------------------------------ */ + public String getSessionIdPathParameterNamePrefix() + { + return _sessionIdPathParameterNamePrefix; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the usingCookies. + */ + public boolean isUsingCookies() + { + return _usingCookies; + } + + /* ------------------------------------------------------------ */ + public boolean isValid(HttpSession session) + { + Session s = ((SessionIf)session).getSession(); + return s.isValid(); + } + + /* ------------------------------------------------------------ */ + public String getClusterId(HttpSession session) + { + Session s = ((SessionIf)session).getSession(); + return s.getClusterId(); + } + + /* ------------------------------------------------------------ */ + public String getNodeId(HttpSession session) + { + Session s = ((SessionIf)session).getSession(); + return s.getNodeId(); + } + + /* ------------------------------------------------------------ */ + /** + * Create a new HttpSession for a request + */ + public HttpSession newHttpSession(HttpServletRequest request) + { + Session session=newSession(request); + session.setMaxInactiveInterval(_dftMaxIdleSecs); + addSession(session,true); + return session; + } + + /* ------------------------------------------------------------ */ + public void removeEventListener(EventListener listener) + { + if (listener instanceof HttpSessionAttributeListener) + _sessionAttributeListeners=LazyList.remove(_sessionAttributeListeners,listener); + if (listener instanceof HttpSessionListener) + _sessionListeners=LazyList.remove(_sessionListeners,listener); + } + + /* ------------------------------------------------------------ */ + public void resetStats() + { + _minSessions=getSessions(); + _maxSessions=getSessions(); + } + + /* ------------------------------------------------------------ */ + /** + * @param httpOnly + * The httpOnly to set. + */ + public void setHttpOnly(boolean httpOnly) + { + _httpOnly=httpOnly; + } + + + /* ------------------------------------------------------------ */ + /** + * @param metaManager The metaManager used for cross context session management. + */ + public void setIdManager(SessionIdManager metaManager) + { + _sessionIdManager=metaManager; + } + + /* ------------------------------------------------------------ */ + public void setMaxCookieAge(int maxCookieAgeInSeconds) + { + _maxCookieAge=maxCookieAgeInSeconds; + + if (_maxCookieAge>0 && _refreshCookieAge==0) + _refreshCookieAge=_maxCookieAge/3; + + } + + /* ------------------------------------------------------------ */ + /** + * @param seconds + */ + public void setMaxInactiveInterval(int seconds) + { + _dftMaxIdleSecs=seconds; + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated use {@link #setIdManager(SessionIdManager)} + */ + public void setMetaManager(SessionIdManager metaManager) + { + setIdManager(metaManager); + } + + /* ------------------------------------------------------------ */ + public void setRefreshCookieAge(int ageInSeconds) + { + _refreshCookieAge=ageInSeconds; + } + + + /* ------------------------------------------------------------ */ + /** + * @param secureCookies + * The secureCookies to set. + */ + public void setSecureCookies(boolean secureCookies) + { + _secureCookies=secureCookies; + } + + public void setSessionCookie(String cookieName) + { + _sessionCookie=cookieName; + } + + public void setSessionDomain(String domain) + { + _sessionDomain=domain; + } + + /* ------------------------------------------------------------ */ + /** + * @param sessionHandler + * The sessionHandler to set. + */ + public void setSessionHandler(SessionHandler sessionHandler) + { + _sessionHandler=sessionHandler; + } + + /* ------------------------------------------------------------ */ + public void setSessionPath(String path) + { + _sessionPath=path; + } + + /* ------------------------------------------------------------ */ + public void setSessionIdPathParameterName(String param) + { + _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param; + _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"="); + } + /* ------------------------------------------------------------ */ + /** + * @param usingCookies + * The usingCookies to set. + */ + public void setUsingCookies(boolean usingCookies) + { + _usingCookies=usingCookies; + } + + + protected abstract void addSession(Session session); + + /* ------------------------------------------------------------ */ + /** + * Add the session Registers the session with this manager and registers the + * session ID with the sessionIDManager; + */ + protected void addSession(Session session, boolean created) + { + synchronized (_sessionIdManager) + { + _sessionIdManager.addSession(session); + synchronized (this) + { + addSession(session); + if (getSessions()>this._maxSessions) + this._maxSessions=getSessions(); + } + } + + if (!created) + { + session.didActivate(); + } + else if (_sessionListeners!=null) + { + HttpSessionEvent event=new HttpSessionEvent(session); + for (int i=0; i0;) + ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionDestroyed(event); + } + if (!invalidate) + { + session.willPassivate(); + } + } + + /* ------------------------------------------------------------ */ + protected abstract void removeSession(String idInCluster); + + /* ------------------------------------------------------------ */ + /** + * Null returning implementation of HttpSessionContext + * + * + */ + public static class NullSessionContext implements HttpSessionContext + { + /* ------------------------------------------------------------ */ + private NullSessionContext() + { + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated From HttpSessionContext + */ + public Enumeration getIds() + { + return Collections.enumeration(Collections.EMPTY_LIST); + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated From HttpSessionContext + */ + public HttpSession getSession(String id) + { + return null; + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * Interface that any session wrapper should implement so that + * SessionManager may access the Jetty session implementation. + * + */ + public interface SessionIf extends HttpSession + { + public Session getSession(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * + *

      + * Implements {@link javax.servlet.HttpSession} from the {@link javax.servlet} package. + *

      + * + * + */ + public abstract class Session implements SessionIf, Serializable + { + protected final String _clusterId; // ID unique within cluster + protected final String _nodeId; // ID unique within node + protected boolean _idChanged; + protected final long _created; + protected long _cookieSet; + protected long _accessed; + protected long _lastAccessed; + protected boolean _invalid; + protected boolean _doInvalidate; + protected long _maxIdleMs=_dftMaxIdleSecs*1000; + protected boolean _newSession; + protected Map _values; + protected int _requests; + + /* ------------------------------------------------------------- */ + protected Session(HttpServletRequest request) + { + _newSession=true; + _created=System.currentTimeMillis(); + _clusterId=_sessionIdManager.newSessionId(request,_created); + _nodeId=_sessionIdManager.getNodeId(_clusterId,request); + _accessed=_created; + _requests=1; + } + + /* ------------------------------------------------------------- */ + protected Session(long created, String clusterId) + { + _created=created; + _clusterId=clusterId; + _nodeId=_sessionIdManager.getNodeId(_clusterId,null); + _accessed=_created; + } + + /* ------------------------------------------------------------- */ + public Session getSession() + { + return this; + } + + /* ------------------------------------------------------------- */ + protected void initValues() + { + _values=newAttributeMap(); + } + + /* ------------------------------------------------------------ */ + public Object getAttribute(String name) + { + synchronized (this) + { + if (_invalid) + throw new IllegalStateException(); + + if (null == _values) + return null; + + return _values.get(name); + } + } + + /* ------------------------------------------------------------ */ + public Enumeration getAttributeNames() + { + synchronized (this) + { + if (_invalid) + throw new IllegalStateException(); + List names=_values==null?Collections.EMPTY_LIST:new ArrayList(_values.keySet()); + return Collections.enumeration(names); + } + } + + /* ------------------------------------------------------------- */ + public long getCookieSetTime() + { + return _cookieSet; + } + + /* ------------------------------------------------------------- */ + public long getCreationTime() throws IllegalStateException + { + if (_invalid) + throw new IllegalStateException(); + return _created; + } + + /* ------------------------------------------------------------ */ + public String getId() throws IllegalStateException + { + return _nodeIdInSessionId?_nodeId:_clusterId; + } + + /* ------------------------------------------------------------- */ + protected String getNodeId() + { + return _nodeId; + } + + /* ------------------------------------------------------------- */ + protected String getClusterId() + { + return _clusterId; + } + + /* ------------------------------------------------------------- */ + public long getLastAccessedTime() throws IllegalStateException + { + if (_invalid) + throw new IllegalStateException(); + return _lastAccessed; + } + + /* ------------------------------------------------------------- */ + public int getMaxInactiveInterval() + { + if (_invalid) + throw new IllegalStateException(); + return (int)(_maxIdleMs/1000); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpSession#getServletContext() + */ + public ServletContext getServletContext() + { + return _context; + } + + /* ------------------------------------------------------------- */ + /** + * @deprecated + */ + public HttpSessionContext getSessionContext() throws IllegalStateException + { + if (_invalid) + throw new IllegalStateException(); + return __nullSessionContext; + } + + /* ------------------------------------------------------------- */ + /** + * @deprecated As of Version 2.2, this method is replaced by + * {@link #getAttribute} + */ + public Object getValue(String name) throws IllegalStateException + { + return getAttribute(name); + } + + /* ------------------------------------------------------------- */ + /** + * @deprecated As of Version 2.2, this method is replaced by + * {@link #getAttributeNames} + */ + public String[] getValueNames() throws IllegalStateException + { + synchronized(this) + { + if (_invalid) + throw new IllegalStateException(); + if (_values==null) + return new String[0]; + String[] a=new String[_values.size()]; + return (String[])_values.keySet().toArray(a); + } + } + + /* ------------------------------------------------------------ */ + protected void access(long time) + { + synchronized(this) + { + _newSession=false; + _lastAccessed=_accessed; + _accessed=time; + _requests++; + } + } + + /* ------------------------------------------------------------ */ + protected void complete() + { + synchronized(this) + { + _requests--; + if (_doInvalidate && _requests<=0 ) + doInvalidate(); + } + } + + + /* ------------------------------------------------------------- */ + protected void timeout() throws IllegalStateException + { + // remove session from context and invalidate other sessions with same ID. + removeSession(this,true); + + // Notify listeners and unbind values + synchronized (this) + { + if (_requests<=0) + doInvalidate(); + else + _doInvalidate=true; + } + } + + /* ------------------------------------------------------------- */ + public void invalidate() throws IllegalStateException + { + // remove session from context and invalidate other sessions with same ID. + removeSession(this,true); + doInvalidate(); + } + + /* ------------------------------------------------------------- */ + protected void doInvalidate() throws IllegalStateException + { + try + { + // Notify listeners and unbind values + if (_invalid) + throw new IllegalStateException(); + + while (_values!=null && _values.size()>0) + { + ArrayList keys; + synchronized (this) + { + keys=new ArrayList(_values.keySet()); + } + + Iterator iter=keys.iterator(); + while (iter.hasNext()) + { + String key=(String)iter.next(); + + Object value; + synchronized (this) + { + value=_values.remove(key); + } + unbindValue(key,value); + + if (_sessionAttributeListeners!=null) + { + HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,key,value); + + for (int i=0; i _sessions; + protected Random _random; + private boolean _weakRandom; + private String _workerName; + + /* ------------------------------------------------------------ */ + public HashSessionIdManager() + { + } + + /* ------------------------------------------------------------ */ + public HashSessionIdManager(Random random) + { + _random=random; + + } + + /* ------------------------------------------------------------ */ + /** + * Get the workname. If set, the workername is dot appended to the session + * ID and can be used to assist session affinity in a load balancer. + * + * @return String or null + */ + public String getWorkerName() + { + return _workerName; + } + + /* ------------------------------------------------------------ */ + /** + * Set the workname. If set, the workername is dot appended to the session + * ID and can be used to assist session affinity in a load balancer. + * + * @param workerName + */ + public void setWorkerName(String workerName) + { + _workerName=workerName; + } + + /* ------------------------------------------------------------ */ + /** Get the session ID with any worker ID. + * + * @param request + * @return sessionId plus any worker ID. + */ + public String getNodeId(String clusterId,HttpServletRequest request) + { + String worker=request==null?null:(String)request.getAttribute("org.eclipse.http.ajp.JVMRoute"); + if (worker!=null) + return clusterId+'.'+worker; + + if (_workerName!=null) + return clusterId+'.'+_workerName; + + return clusterId; + } + + /* ------------------------------------------------------------ */ + /** Get the session ID without any worker ID. + * + * @param request + * @return sessionId without any worker ID. + */ + public String getClusterId(String nodeId) + { + int dot=nodeId.lastIndexOf('.'); + return (dot>0)?nodeId.substring(0,dot):nodeId; + } + + /* ------------------------------------------------------------ */ + protected void doStart() + { + if (_random==null) + { + try + { + //This operation may block on some systems with low entropy. See this page + //for workaround suggestions: + //http://docs.codehaus.org/display/JETTY/Connectors+slow+to+startup + Log.debug("Init SecureRandom."); + _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM); + } + catch (NoSuchAlgorithmException e) + { + try + { + _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM_ALT); + _weakRandom=false; + } + catch (NoSuchAlgorithmException e_alt) + { + Log.warn("Could not generate SecureRandom for session-id randomness",e); + _random=new Random(); + _weakRandom=true; + } + } + } + _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory()); + _sessions=new MultiMap(true); + } + + /* ------------------------------------------------------------ */ + protected void doStop() + { + if (_sessions!=null) + _sessions.clear(); // Maybe invalidate? + _sessions=null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#idInUse(java.lang.String) + */ + public boolean idInUse(String id) + { + return _sessions.containsKey(id); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#addSession(javax.servlet.http.HttpSession) + */ + public void addSession(HttpSession session) + { + _sessions.add(getClusterId(session.getId()),session); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#addSession(javax.servlet.http.HttpSession) + */ + public void removeSession(HttpSession session) + { + _sessions.removeValue(getClusterId(session.getId()),session); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#invalidateAll(java.lang.String) + */ + public void invalidateAll(String id) + { + // Do not use interators as this method tends to be called recursively + // by the invalidate calls. + while (_sessions.containsKey(id)) + { + Session session=(Session)_sessions.getValue(id,0); + if (session.isValid()) + session.invalidate(); + else + _sessions.removeValue(id,session); + } + } + + /* ------------------------------------------------------------ */ + /* + * new Session ID. If the request has a requestedSessionID which is unique, + * that is used. The session ID is created as a unique random long XORed with + * connection specific information, base 36. + * @param request + * @param created + * @return Session ID. + */ + public String newSessionId(HttpServletRequest request, long created) + { + synchronized (this) + { + // A requested session ID can only be used if it is in use already. + String requested_id=request.getRequestedSessionId(); + + if (requested_id!=null) + { + String cluster_id=getClusterId(requested_id); + if (idInUse(cluster_id)) + return cluster_id; + } + + // Else reuse any new session ID already defined for this request. + String new_id=(String)request.getAttribute(__NEW_SESSION_ID); + if (new_id!=null&&idInUse(new_id)) + return new_id; + + // pick a new unique ID! + String id=null; + while (id==null||id.length()==0||idInUse(id)) + { + long r=_weakRandom + ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32)) + :_random.nextLong(); + r^=created; + if (request!=null && request.getRemoteAddr()!=null) + r^=request.getRemoteAddr().hashCode(); + if (r<0) + r=-r; + id=Long.toString(r,36); + } + + request.setAttribute(__NEW_SESSION_ID,id); + return id; + } + } + + /* ------------------------------------------------------------ */ + public Random getRandom() + { + return _random; + } + + /* ------------------------------------------------------------ */ + public void setRandom(Random random) + { + _random=random; + _weakRandom=false; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java new file mode 100644 index 00000000000..a7b7c977278 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java @@ -0,0 +1,652 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.session; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +/** An in-memory implementation of SessionManager. + * + * + */ +public class HashSessionManager extends AbstractSessionManager +{ + private static int __id; + private Timer _timer; + private TimerTask _task; + private int _scavengePeriodMs=30000; + private int _savePeriodMs=0; //don't do period saves by default + private TimerTask _saveTask; + protected Map _sessions; + private File _storeDir; + private boolean _lazyLoad=false; + private boolean _sessionsLoaded=false; + + /* ------------------------------------------------------------ */ + public HashSessionManager() + { + super(); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart() + */ + public void doStart() throws Exception + { + _sessions=new ConcurrentHashMap(); // TODO: use syncronizedMap for JDK 1.4 + super.doStart(); + + _timer=new Timer("HashSessionScavenger-"+__id++, true); + + setScavengePeriod(getScavengePeriod()); + + if (_storeDir!=null) + { + if (!_storeDir.exists()) + _storeDir.mkdir(); + + if (!_lazyLoad) + restoreSessions(); + } + + setSavePeriod(getSavePeriod()); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop() + */ + public void doStop() throws Exception + { + + if (_storeDir != null) + saveSessions(); + + super.doStop(); + + _sessions.clear(); + _sessions=null; + + // stop the scavenger + synchronized(this) + { + if (_saveTask!=null) + _saveTask.cancel(); + if (_task!=null) + _task.cancel(); + if (_timer!=null) + _timer.cancel(); + _timer=null; + } + } + + /* ------------------------------------------------------------ */ + /** + * @return seconds + */ + public int getScavengePeriod() + { + return _scavengePeriodMs/1000; + } + + + /* ------------------------------------------------------------ */ + public Map getSessionMap() + { + return Collections.unmodifiableMap(_sessions); + } + + + /* ------------------------------------------------------------ */ + public int getSessions() + { + return _sessions.size(); + } + + + /* ------------------------------------------------------------ */ + public void setMaxInactiveInterval(int seconds) + { + super.setMaxInactiveInterval(seconds); + if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000) + setScavengePeriod((_dftMaxIdleSecs+9)/10); + } + + /* ------------------------------------------------------------ */ + public void setSavePeriod (int seconds) + { + int oldSavePeriod = _savePeriodMs; + int period = (seconds * 1000); + if (period < 0) + period=0; + _savePeriodMs=period; + + if (_timer!=null) + { + synchronized (this) + { + if (_saveTask!=null) + _saveTask.cancel(); + if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured + { + _saveTask = new TimerTask() + { + public void run() + { + try + { + saveSessions(); + } + catch (Exception e) + { + Log.warn(e); + } + } + }; + _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs); + } + } + } + } + + /* ------------------------------------------------------------ */ + public int getSavePeriod () + { + if (_savePeriodMs<=0) + return 0; + + return _savePeriodMs/1000; + } + + /* ------------------------------------------------------------ */ + /** + * @param seconds + */ + public void setScavengePeriod(int seconds) + { + if (seconds==0) + seconds=60; + + int old_period=_scavengePeriodMs; + int period=seconds*1000; + if (period>60000) + period=60000; + if (period<1000) + period=1000; + + _scavengePeriodMs=period; + if (_timer!=null && (period!=old_period || _task==null)) + { + synchronized (this) + { + if (_task!=null) + _task.cancel(); + _task = new TimerTask() + { + public void run() + { + scavenge(); + } + }; + _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs); + } + } + } + + /* -------------------------------------------------------------- */ + /** + * Find sessions that have timed out and invalidate them. This runs in the + * SessionScavenger thread. + */ + private void scavenge() + { + //don't attempt to scavenge if we are shutting down + if (isStopping() || isStopped()) + return; + + Thread thread=Thread.currentThread(); + ClassLoader old_loader=thread.getContextClassLoader(); + try + { + if (_loader!=null) + thread.setContextClassLoader(_loader); + + long now=System.currentTimeMillis(); + + try + { + if (!_sessionsLoaded && _lazyLoad) + restoreSessions(); + } + catch(Exception e) + { + Log.debug(e); + } + + // Since Hashtable enumeration is not safe over deletes, + // we build a list of stale sessions, then go back and invalidate + // them + Object stale=null; + + synchronized (HashSessionManager.this) + { + // For each session + for (Iterator i=_sessions.values().iterator(); i.hasNext();) + { + Session session=(Session)i.next(); + long idleTime=session._maxIdleMs; + if (idleTime>0&&session._accessed+idleTime0;) + { + // check it has not been accessed in the meantime + Session session=(Session)LazyList.get(stale,i); + long idleTime=session._maxIdleMs; + if (idleTime>0&&session._accessed+idleTime 0) + { + ArrayList keys = new ArrayList(); + for (int i=0; i0&&(_maxIdleMs/10)<_scavengePeriodMs) + HashSessionManager.this.setScavengePeriod((secs+9)/10); + } + + /* ------------------------------------------------------------ */ + protected Map newAttributeMap() + { + return new HashMap(3); + } + + + /* ------------------------------------------------------------ */ + public void invalidate () + throws IllegalStateException + { + super.invalidate(); + + remove(); + } + + /* ------------------------------------------------------------ */ + public void remove() + { + String id=getId(); + if (id==null) + return; + + //all sessions are invalidated when jetty is stopped, make sure we don't + //remove all the sessions in this case + if (isStopping() || isStopped()) + return; + + if (_storeDir==null || !_storeDir.exists()) + { + return; + } + + File f = new File(_storeDir, id); + f.delete(); + } + + /* ------------------------------------------------------------ */ + public void save(OutputStream os) throws IOException + { + DataOutputStream out = new DataOutputStream(os); + out.writeUTF(_clusterId); + out.writeUTF(_nodeId); + out.writeBoolean(_idChanged); + out.writeLong( _created); + out.writeLong(_cookieSet); + out.writeLong(_accessed); + out.writeLong(_lastAccessed); + /* Don't write these out, as they don't make sense to store because they + * either they cannot be true or their value will be restored in the + * Session constructor. + */ + //out.writeBoolean(_invalid); + //out.writeBoolean(_doInvalidate); + //out.writeLong(_maxIdleMs); + //out.writeBoolean( _newSession); + out.writeInt(_requests); + if (_values != null) + { + out.writeInt(_values.size()); + Iterator itor = _values.keySet().iterator(); + while (itor.hasNext()) + { + String key = (String)itor.next(); + out.writeUTF(key); + } + itor = _values.values().iterator(); + ObjectOutputStream oos = new ObjectOutputStream(out); + while (itor.hasNext()) + { + oos.writeObject(itor.next()); + } + oos.close(); + } + else + out.writeInt(0); + out.close(); + } + + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected class ClassLoadingObjectInputStream extends ObjectInputStream + { + /* ------------------------------------------------------------ */ + public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException + { + super(in); + } + + /* ------------------------------------------------------------ */ + public ClassLoadingObjectInputStream () throws IOException + { + super(); + } + + /* ------------------------------------------------------------ */ + public Class resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException + { + try + { + return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader()); + } + catch (ClassNotFoundException e) + { + return super.resolveClass(cl); + } + } + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java new file mode 100644 index 00000000000..0e386e1f288 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java @@ -0,0 +1,695 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.session; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.sql.Blob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; + +import javax.naming.InitialContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.sql.DataSource; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; + + + +/** + * JDBCSessionIdManager + * + * SessionIdManager implementation that uses a database to store in-use session ids, + * to support distributed sessions. + * + */ +public class JDBCSessionIdManager extends AbstractSessionIdManager +{ + protected HashSet _sessionIds = new HashSet(); + protected String _driverClassName; + protected String _connectionUrl; + protected DataSource _datasource; + protected String _jndiName; + protected String _sessionIdTable = "JettySessionIds"; + protected String _sessionTable = "JettySessions"; + protected Timer _timer; //scavenge timer + protected TimerTask _task; //scavenge task + protected long _lastScavengeTime; + protected long _scavengeIntervalMs = 1000 * 60 * 10; //10mins + + + protected String _createSessionIdTable; + protected String _createSessionTable; + + protected String _selectExpiredSessions; + protected String _deleteOldExpiredSessions; + + protected String _insertId; + protected String _deleteId; + protected String _queryId; + + protected DatabaseAdaptor _dbAdaptor; + + + /** + * DatabaseAdaptor + * + * Handles differences between databases. + * + * Postgres uses the getBytes and setBinaryStream methods to access + * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL + * is happy to use the "blob" type and getBlob() methods instead. + * + * TODO if the differences become more major it would be worthwhile + * refactoring this class. + */ + public class DatabaseAdaptor + { + String _dbName; + boolean _isLower; + boolean _isUpper; + + + public DatabaseAdaptor (DatabaseMetaData dbMeta) + throws SQLException + { + _dbName = dbMeta.getDatabaseProductName().toLowerCase(); + Log.debug ("Using database "+_dbName); + _isLower = dbMeta.storesLowerCaseIdentifiers(); + _isUpper = dbMeta.storesUpperCaseIdentifiers(); + } + + /** + * Convert a camel case identifier into either upper or lower + * depending on the way the db stores identifiers. + * + * @param identifier + * @return + */ + public String convertIdentifier (String identifier) + { + if (_isLower) + return identifier.toLowerCase(); + if (_isUpper) + return identifier.toUpperCase(); + + return identifier; + } + + public String getBlobType () + { + if (_dbName.startsWith("postgres")) + return "bytea"; + + return "blob"; + } + + public InputStream getBlobInputStream (ResultSet result, String columnName) + throws SQLException + { + if (_dbName.startsWith("postgres")) + { + byte[] bytes = result.getBytes(columnName); + return new ByteArrayInputStream(bytes); + } + + Blob blob = result.getBlob(columnName); + return blob.getBinaryStream(); + } + } + + + + public JDBCSessionIdManager(Server server) + { + super(server); + } + + public JDBCSessionIdManager(Server server, Random random) + { + super(server, random); + } + + /** + * Configure jdbc connection information via a jdbc Driver + * + * @param driverClassName + * @param connectionUrl + */ + public void setDriverInfo (String driverClassName, String connectionUrl) + { + _driverClassName=driverClassName; + _connectionUrl=connectionUrl; + } + + public String getDriverClassName() + { + return _driverClassName; + } + + public String getConnectionUrl () + { + return _connectionUrl; + } + + public void setDatasourceName (String jndi) + { + _jndiName=jndi; + } + + public String getDatasourceName () + { + return _jndiName; + } + + + public void setScavengeInterval (long sec) + { + if (sec<=0) + sec=60; + + long old_period=_scavengeIntervalMs; + long period=sec*1000; + + _scavengeIntervalMs=period; + + //add a bit of variability into the scavenge time so that not all + //nodes with the same scavenge time sync up + long tenPercent = _scavengeIntervalMs/10; + if ((System.currentTimeMillis()%2) == 0) + _scavengeIntervalMs += tenPercent; + + if (Log.isDebugEnabled()) Log.debug("Scavenging every "+_scavengeIntervalMs+" ms"); + if (_timer!=null && (period!=old_period || _task==null)) + { + synchronized (this) + { + if (_task!=null) + _task.cancel(); + _task = new TimerTask() + { + public void run() + { + scavenge(); + } + }; + _timer.schedule(_task,_scavengeIntervalMs,_scavengeIntervalMs); + } + } + } + + public long getScavengeInterval () + { + return _scavengeIntervalMs/1000; + } + + + public void addSession(HttpSession session) + { + if (session == null) + return; + + synchronized (_sessionIds) + { + String id = ((JDBCSessionManager.Session)session).getClusterId(); + try + { + insert(id); + _sessionIds.add(id); + } + catch (Exception e) + { + Log.warn("Problem storing session id="+id, e); + } + } + } + + public void removeSession(HttpSession session) + { + if (session == null) + return; + + removeSession(((JDBCSessionManager.Session)session).getClusterId()); + } + + + + public void removeSession (String id) + { + + if (id == null) + return; + + synchronized (_sessionIds) + { + if (Log.isDebugEnabled()) + Log.debug("Removing session id="+id); + try + { + _sessionIds.remove(id); + delete(id); + } + catch (Exception e) + { + Log.warn("Problem removing session id="+id, e); + } + } + + } + + + /** + * Get the session id without any node identifier suffix. + * + * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String) + */ + public String getClusterId(String nodeId) + { + int dot=nodeId.lastIndexOf('.'); + return (dot>0)?nodeId.substring(0,dot):nodeId; + } + + + /** + * Get the session id, including this node's id as a suffix. + * + * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest) + */ + public String getNodeId(String clusterId, HttpServletRequest request) + { + if (_workerName!=null) + return clusterId+'.'+_workerName; + + return clusterId; + } + + + public boolean idInUse(String id) + { + if (id == null) + return false; + + String clusterId = getClusterId(id); + + synchronized (_sessionIds) + { + if (_sessionIds.contains(clusterId)) + return true; //optimisation - if this session is one we've been managing, we can check locally + + //otherwise, we need to go to the database to check + try + { + return exists(clusterId); + } + catch (Exception e) + { + Log.warn("Problem checking inUse for id="+clusterId, e); + return false; + } + } + } + + /** + * Invalidate the session matching the id on all contexts. + * + * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String) + */ + public void invalidateAll(String id) + { + //take the id out of the list of known sessionids for this node + removeSession(id); + + synchronized (_sessionIds) + { + //tell all contexts that may have a session object with this id to + //get rid of them + Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); + for (int i=0; contexts!=null && i= ? and expiryTime <= ?"; + _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?"; + + _insertId = "insert into "+_sessionIdTable+" (id) values (?)"; + _deleteId = "delete from "+_sessionIdTable+" where id = ?"; + _queryId = "select * from "+_sessionIdTable+" where id = ?"; + + Connection connection = null; + try + { + //make the id table + connection = getConnection(); + connection.setAutoCommit(true); + DatabaseMetaData metaData = connection.getMetaData(); + _dbAdaptor = new DatabaseAdaptor(metaData); + + //checking for table existence is case-sensitive, but table creation is not + String tableName = _dbAdaptor.convertIdentifier(_sessionIdTable); + ResultSet result = metaData.getTables(null, null, tableName, null); + if (!result.next()) + { + //table does not exist, so create it + connection.createStatement().executeUpdate(_createSessionIdTable); + } + + //make the session table if necessary + tableName = _dbAdaptor.convertIdentifier(_sessionTable); + result = metaData.getTables(null, null, tableName, null); + if (!result.next()) + { + //table does not exist, so create it + String blobType = _dbAdaptor.getBlobType(); + _createSessionTable = "create table "+_sessionTable+" (rowId varchar(60), sessionId varchar(60), "+ + " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime bigint, "+ + " lastAccessTime bigint, createTime bigint, cookieTime bigint, "+ + " lastSavedTime bigint, expiryTime bigint, map "+blobType+", primary key(rowId))"; + connection.createStatement().executeUpdate(_createSessionTable); + } + + //make some indexes on the JettySessions table + String index1 = "idx_"+_sessionTable+"_expiry"; + String index2 = "idx_"+_sessionTable+"_session"; + + result = metaData.getIndexInfo(null, null, tableName, false, false); + boolean index1Exists = false; + boolean index2Exists = false; + while (result.next()) + { + String idxName = result.getString("INDEX_NAME"); + if (index1.equalsIgnoreCase(idxName)) + index1Exists = true; + else if (index2.equalsIgnoreCase(idxName)) + index2Exists = true; + } + if (!(index1Exists && index2Exists)) + { + Statement statement = connection.createStatement(); + if (!index1Exists) + statement.executeUpdate("create index "+index1+" on "+_sessionTable+" (expiryTime)"); + if (!index2Exists) + statement.executeUpdate("create index "+index2+" on "+_sessionTable+" (sessionId, contextPath)"); + } + } + finally + { + if (connection != null) + connection.close(); + } + } + + /** + * Insert a new used session id into the table. + * + * @param id + * @throws SQLException + */ + private void insert (String id) + throws SQLException + { + Connection connection = null; + try + { + connection = getConnection(); + connection.setAutoCommit(true); + PreparedStatement query = connection.prepareStatement(_queryId); + query.setString(1, id); + ResultSet result = query.executeQuery(); + //only insert the id if it isn't in the db already + if (!result.next()) + { + PreparedStatement statement = connection.prepareStatement(_insertId); + statement.setString(1, id); + statement.executeUpdate(); + } + } + finally + { + if (connection != null) + connection.close(); + } + } + + /** + * Remove a session id from the table. + * + * @param id + * @throws SQLException + */ + private void delete (String id) + throws SQLException + { + Connection connection = null; + try + { + connection = getConnection(); + connection.setAutoCommit(true); + PreparedStatement statement = connection.prepareStatement(_deleteId); + statement.setString(1, id); + statement.executeUpdate(); + } + finally + { + if (connection != null) + connection.close(); + } + } + + + /** + * Check if a session id exists. + * + * @param id + * @return + * @throws SQLException + */ + private boolean exists (String id) + throws SQLException + { + Connection connection = null; + try + { + connection = getConnection(); + connection.setAutoCommit(true); + PreparedStatement statement = connection.prepareStatement(_queryId); + statement.setString(1, id); + ResultSet result = statement.executeQuery(); + if (result.next()) + return true; + else + return false; + } + finally + { + if (connection != null) + connection.close(); + } + } + + /** + * Look for sessions in the database that have expired. + * + * We do this in the SessionIdManager and not the SessionManager so + * that we only have 1 scavenger, otherwise if there are n SessionManagers + * there would be n scavengers, all contending for the database. + * + * We look first for sessions that expired in the previous interval, then + * for sessions that expired previously - these are old sessions that no + * node is managing any more and have become stuck in the database. + */ + private void scavenge () + { + Connection connection = null; + List expiredSessionIds = new ArrayList(); + try + { + if (Log.isDebugEnabled()) Log.debug("Scavenge sweep started at "+System.currentTimeMillis()); + if (_lastScavengeTime > 0) + { + connection = getConnection(); + connection.setAutoCommit(true); + //"select sessionId from JettySessions where expiryTime > (lastScavengeTime - scanInterval) and expiryTime < lastScavengeTime"; + PreparedStatement statement = connection.prepareStatement(_selectExpiredSessions); + long lowerBound = (_lastScavengeTime - _scavengeIntervalMs); + long upperBound = _lastScavengeTime; + if (Log.isDebugEnabled()) Log.debug("Searching for sessions expired between "+lowerBound + " and "+upperBound); + statement.setLong(1, lowerBound); + statement.setLong(2, upperBound); + ResultSet result = statement.executeQuery(); + while (result.next()) + { + String sessionId = result.getString("sessionId"); + expiredSessionIds.add(sessionId); + if (Log.isDebugEnabled()) Log.debug("Found expired sessionId="+sessionId); + } + + + //tell the SessionManagers to expire any sessions with a matching sessionId in memory + Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); + for (int i=0; contexts!=null && i 0) + { + if (Log.isDebugEnabled()) Log.debug("Deleting old expired sessions expired before "+upperBound); + statement = connection.prepareStatement(_deleteOldExpiredSessions); + statement.setLong(1, upperBound); + statement.executeUpdate(); + } + } + } + catch (Exception e) + { + Log.warn("Problem selecting expired sessions", e); + } + finally + { + _lastScavengeTime=System.currentTimeMillis(); + if (Log.isDebugEnabled()) Log.debug("Scavenge sweep ended at "+_lastScavengeTime); + if (connection != null) + { + try + { + connection.close(); + } + catch (SQLException e) + { + Log.warn(e); + } + } + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java new file mode 100644 index 00000000000..963a0a57dc4 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java @@ -0,0 +1,1080 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.server.session; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.eclipse.jetty.server.SessionIdManager; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; + +/** + * JDBCSessionManager + * + * SessionManager that persists sessions to a database to enable clustering. + * + * Session data is persisted to the JettySessions table: + * + * rowId (unique in cluster: webapp name/path + virtualhost + sessionId) + * contextPath (of the context owning the session) + * sessionId (unique in a context) + * lastNode (name of node last handled session) + * accessTime (time in ms session was accessed) + * lastAccessTime (previous time in ms session was accessed) + * createTime (time in ms session created) + * cookieTime (time in ms session cookie created) + * lastSavedTime (last time in ms session access times were saved) + * expiryTime (time in ms that the session is due to expire) + * map (attribute map) + * + * As an optimisation, to prevent thrashing the database, we do not persist + * the accessTime and lastAccessTime every time the session is accessed. Rather, + * we write it out every so often. The frequency is controlled by the saveIntervalSec + * field. + */ +public class JDBCSessionManager extends AbstractSessionManager +{ + protected String __insertSession; + protected String __deleteSession; + protected String __selectSession; + protected String __updateSession; + protected String __updateSessionNode; + protected String __updateSessionAccessTime; + + private ConcurrentHashMap _sessions; + protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs + + /** + * SessionData + * + * Persistable data about a session. + */ + public class SessionData + { + private String _id; + private String _rowId; + private long _accessed; + private long _lastAccessed; + private long _maxIdleMs; + private long _cookieSet; + private long _created; + private Map _attributes; + private String _lastNode; + private String _canonicalContext; + private long _lastSaved; + private long _expiryTime; + private String _virtualHost; + + public SessionData (String sessionId) + { + _id=sessionId; + _created=System.currentTimeMillis(); + _accessed = _created; + _attributes = new ConcurrentHashMap(); + _lastNode = getIdManager().getWorkerName(); + } + + public synchronized String getId () + { + return _id; + } + + public synchronized long getCreated () + { + return _created; + } + + protected synchronized void setCreated (long ms) + { + _created = ms; + } + + public synchronized long getAccessed () + { + return _accessed; + } + + protected synchronized void setAccessed (long ms) + { + _accessed = ms; + } + + + public synchronized void setMaxIdleMs (long ms) + { + _maxIdleMs = ms; + } + + public synchronized long getMaxIdleMs() + { + return _maxIdleMs; + } + + public synchronized void setLastAccessed (long ms) + { + _lastAccessed = ms; + } + + public synchronized long getLastAccessed() + { + return _lastAccessed; + } + + public void setCookieSet (long ms) + { + _cookieSet = ms; + } + + public synchronized long getCookieSet () + { + return _cookieSet; + } + + public synchronized void setRowId (String rowId) + { + _rowId=rowId; + } + + protected synchronized String getRowId() + { + return _rowId; + } + + protected synchronized Map getAttributeMap () + { + return _attributes; + } + + protected synchronized void setAttributeMap (ConcurrentHashMap map) + { + _attributes = map; + } + + public synchronized void setLastNode (String node) + { + _lastNode=node; + } + + public synchronized String getLastNode () + { + return _lastNode; + } + + public synchronized void setCanonicalContext(String str) + { + _canonicalContext=str; + } + + public synchronized String getCanonicalContext () + { + return _canonicalContext; + } + + public synchronized long getLastSaved () + { + return _lastSaved; + } + + public synchronized void setLastSaved (long time) + { + _lastSaved=time; + } + + public synchronized void setExpiryTime (long time) + { + _expiryTime=time; + } + + public synchronized long getExpiryTime () + { + return _expiryTime; + } + + public synchronized void setVirtualHost (String vhost) + { + _virtualHost=vhost; + } + + public synchronized String getVirtualHost () + { + return _virtualHost; + } + + public String toString () + { + return "Session rowId="+_rowId+",id="+_id+",lastNode="+_lastNode+ + ",created="+_created+",accessed="+_accessed+ + ",lastAccessed="+_lastAccessed+",cookieSet="+_cookieSet+ + "lastSaved="+_lastSaved; + } + } + + + + /** + * Session + * + * Session instance in memory of this node. + */ + public class Session extends AbstractSessionManager.Session + { + private SessionData _data; + private boolean _dirty=false; + + /** + * Session from a request. + * + * @param request + */ + protected Session (HttpServletRequest request) + { + + super(request); + _data = new SessionData(_clusterId); + _data.setMaxIdleMs(_dftMaxIdleSecs*1000); + _data.setCanonicalContext(canonicalize(_context.getContextPath())); + _data.setVirtualHost(getVirtualHost(_context)); + _data.setExpiryTime(_maxIdleMs < 0 ? 0 : (System.currentTimeMillis() + _maxIdleMs)); + _values=_data.getAttributeMap(); + } + + /** + * Session restored in database. + * @param row + */ + protected Session (SessionData data) + { + super(data.getCreated(), data.getId()); + _data=data; + _values=data.getAttributeMap(); + } + + protected Map newAttributeMap() + { + return _data.getAttributeMap(); + } + + public void setAttribute (String name, Object value) + { + super.setAttribute(name, value); + _dirty=true; + } + + public void removeAttribute (String name) + { + super.removeAttribute(name); + _dirty=true; + } + + protected void cookieSet() + { + _data.setCookieSet(_data.getAccessed()); + } + + /** + * Entry to session. + * Called by SessionHandler on inbound request and the session already exists in this node's memory. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager.Session#access(long) + */ + protected void access(long time) + { + super.access(time); + _data.setLastAccessed(_data.getAccessed()); + _data.setAccessed(time); + _data.setExpiryTime(_maxIdleMs < 0 ? 0 : (time + _maxIdleMs)); + } + + /** + * Exit from session + * @see org.eclipse.jetty.server.session.AbstractSessionManager.Session#complete() + */ + protected void complete() + { + super.complete(); + try + { + if (_dirty) + { + //The session attributes have changed, write to the db, ensuring + //http passivation/activation listeners called + willPassivate(); + updateSession(_data); + didActivate(); + } + else if ((_data._accessed - _data._lastSaved) >= (getSaveInterval() * 1000)) + updateSessionAccessTime(_data); + + } + catch (Exception e) + { + Log.warn("Problem persisting changed session data id="+getId(), e); + } + finally + { + _dirty=false; + } + } + + protected void timeout() throws IllegalStateException + { + if (Log.isDebugEnabled()) Log.debug("Timing out session id="+getClusterId()); + super.timeout(); + } + } + + + + + /** + * ClassLoadingObjectInputStream + * + * + */ + protected class ClassLoadingObjectInputStream extends ObjectInputStream + { + public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException + { + super(in); + } + + public ClassLoadingObjectInputStream () throws IOException + { + super(); + } + + public Class resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException + { + try + { + return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader()); + } + catch (ClassNotFoundException e) + { + return super.resolveClass(cl); + } + } + } + + + + + /** + * Set the time in seconds which is the interval between + * saving the session access time to the database. + * + * This is an optimization that prevents the database from + * being overloaded when a session is accessed very frequently. + * + * On session exit, if the session attributes have NOT changed, + * the time at which we last saved the accessed + * time is compared to the current accessed time. If the interval + * is at least saveIntervalSecs, then the access time will be + * persisted to the database. + * + * If any session attribute does change, then the attributes and + * the accessed time are persisted. + * + * @param sec + */ + public void setSaveInterval (long sec) + { + _saveIntervalSec=sec; + } + + public long getSaveInterval () + { + return _saveIntervalSec; + } + + + /** + * A session has been requested by it's id on this node. + * + * Load the session by id AND context path from the database. + * Multiple contexts may share the same session id (due to dispatching) + * but they CANNOT share the same contents. + * + * Check if last node id is my node id, if so, then the session we have + * in memory cannot be stale. If another node used the session last, then + * we need to refresh from the db. + * + * NOTE: this method will go to the database, so if you only want to check + * for the existence of a Session in memory, use _sessions.get(id) instead. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String) + */ + public Session getSession(String idInCluster) + { + Session session = (Session)_sessions.get(idInCluster); + + synchronized (this) + { + try + { + //check if we need to reload the session - don't do it on every call + //to reduce the load on the database. This introduces a window of + //possibility that the node may decide that the session is local to it, + //when the session has actually been live on another node, and then + //re-migrated to this node. This should be an extremely rare occurrence, + //as load-balancers are generally well-behaved and consistently send + //sessions to the same node, changing only iff that node fails. + SessionData data = null; + long now = System.currentTimeMillis(); + if (Log.isDebugEnabled()) Log.debug("now="+now+ + " lastSaved="+(session==null?0:session._data._lastSaved)+ + " interval="+(_saveIntervalSec * 1000)+ + " difference="+(now - (session==null?0:session._data._lastSaved))); + if (session==null || ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000))) + { + data = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); + } + else + data = session._data; + + if (data != null) + { + if (!data.getLastNode().equals(getIdManager().getWorkerName()) || session==null) + { + //session last used on a different node, or we don't have it in memory + session = new Session(data); + _sessions.put(idInCluster, session); + session.didActivate(); + //TODO is this the best way to do this? Or do this on the way out using + //the _dirty flag? + updateSessionNode(data); + } + else + if (Log.isDebugEnabled()) Log.debug("Session not stale "+session._data); + //session in db shares same id, but is not for this context + } + else + { + //No session in db with matching id and context path. + session=null; + if (Log.isDebugEnabled()) Log.debug("No session in database matching id="+idInCluster); + } + + return session; + } + catch (Exception e) + { + Log.warn("Unable to load session from database", e); + return null; + } + } + } + + + /** + * Get all the sessions as a map of id to Session. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessionMap() + */ + public Map getSessionMap() + { + return Collections.unmodifiableMap(_sessions); + } + + + /** + * Get the number of sessions. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions() + */ + public int getSessions() + { + int size = 0; + synchronized (this) + { + size = _sessions.size(); + } + return size; + } + + + /** + * Start the session manager. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart() + */ + public void doStart() throws Exception + { + if (_sessionIdManager==null) + throw new IllegalStateException("No session id manager defined"); + + prepareTables(); + + _sessions = new ConcurrentHashMap(); + super.doStart(); + } + + + /** + * Stop the session manager. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop() + */ + public void doStop() throws Exception + { + _sessions.clear(); + _sessions = null; + + super.doStop(); + } + + protected void invalidateSessions() + { + //Do nothing - we don't want to remove and + //invalidate all the sessions because this + //method is called from doStop(), and just + //because this context is stopping does not + //mean that we should remove the session from + //any other nodes + } + + + /** + * Invalidate a session. + * + * @param idInCluster + */ + protected void invalidateSession (String idInCluster) + { + synchronized (this) + { + Session session = (Session)_sessions.get(idInCluster); + if (session != null) + { + session.invalidate(); + } + } + } + + /** + * Delete an existing session, both from the in-memory map and + * the database. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String) + */ + protected void removeSession(String idInCluster) + { + synchronized (this) + { + try + { + Session session = (Session)_sessions.remove(idInCluster); + deleteSession(session._data); + } + catch (Exception e) + { + Log.warn("Problem deleting session id="+idInCluster, e); + } + } + } + + + /** + * Add a newly created session to our in-memory list for this node and persist it. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSessionManager.Session) + */ + protected void addSession(AbstractSessionManager.Session session) + { + if (session==null) + return; + + synchronized (this) + { + _sessions.put(session.getClusterId(), session); + //TODO or delay the store until exit out of session? If we crash before we store it + //then session data will be lost. + try + { + ((JDBCSessionManager.Session)session).willPassivate(); + storeSession(((JDBCSessionManager.Session)session)._data); + ((JDBCSessionManager.Session)session).didActivate(); + } + catch (Exception e) + { + Log.warn("Unable to store new session id="+session.getId() , e); + } + } + } + + + /** + * Make a new Session. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest) + */ + protected AbstractSessionManager.Session newSession(HttpServletRequest request) + { + return new Session(request); + } + + /* ------------------------------------------------------------ */ + /** Remove session from manager + * @param session The session to remove + * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and + * {@link SessionIdManager#invalidateAll(String)} should be called. + */ + public void removeSession(AbstractSessionManager.Session session, boolean invalidate) + { + // Remove session from context and global maps + synchronized (_sessionIdManager) + { + boolean removed = false; + + synchronized (this) + { + //take this session out of the map of sessions for this context + if (_sessions.get(session.getClusterId()) != null) + { + removed = true; + removeSession(session.getClusterId()); + } + } + + if (removed) + { + // Remove session from all context and global id maps + _sessionIdManager.removeSession(session); + if (invalidate) + _sessionIdManager.invalidateAll(session.getClusterId()); + } + } + + if (invalidate && _sessionListeners!=null) + { + HttpSessionEvent event=new HttpSessionEvent(session); + for (int i=LazyList.size(_sessionListeners); i-->0;) + ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionDestroyed(event); + } + if (!invalidate) + { + session.willPassivate(); + } + } + + + /** + * Expire any Sessions we have in memory matching the list of + * expired Session ids. + * + * @param sessionIds + */ + protected void expire (List sessionIds) + { + //don't attempt to scavenge if we are shutting down + if (isStopping() || isStopped()) + return; + + //Remove any sessions we already have in memory that match the ids + Thread thread=Thread.currentThread(); + ClassLoader old_loader=thread.getContextClassLoader(); + ListIterator itor = sessionIds.listIterator(); + + try + { + while (itor.hasNext()) + { + String sessionId = (String)itor.next(); + if (Log.isDebugEnabled()) Log.debug("Expiring session id "+sessionId); + Session session = (Session)_sessions.get(sessionId); + if (session != null) + { + session.timeout(); + itor.remove(); + int count = this._sessions.size(); + if (count < this._minSessions) + this._minSessions=count; + } + else + { + if (Log.isDebugEnabled()) Log.debug("Unrecognized session id="+sessionId); + } + } + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + throw ((ThreadDeath)t); + else + Log.warn("Problem expiring sessions", t); + } + finally + { + thread.setContextClassLoader(old_loader); + } + } + + + protected void prepareTables () + { + __insertSession = "insert into "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+ + " (rowId, sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+ + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + __deleteSession = "delete from "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+ + " where rowId = ?"; + + __selectSession = "select * from "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+ + " where sessionId = ? and contextPath = ? and virtualHost = ?"; + + __updateSession = "update "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+ + " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where rowId = ?"; + + __updateSessionNode = "update "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+ + " set lastNode = ? where rowId = ?"; + + __updateSessionAccessTime = "update "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+ + " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where rowId = ?"; + } + + /** + * Load a session from the database + * @param id + * @return + * @throws Exception + */ + protected SessionData loadSession (String id, String canonicalContextPath, String vhost) + throws Exception + { + SessionData data = null; + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + statement = connection.prepareStatement(__selectSession); + statement.setString(1, id); + statement.setString(2, canonicalContextPath); + statement.setString(3, vhost); + ResultSet result = statement.executeQuery(); + if (result.next()) + { + data = new SessionData(id); + data.setRowId(result.getString("rowId")); + data.setCookieSet(result.getLong("cookieTime")); + data.setLastAccessed(result.getLong("lastAccessTime")); + data.setAccessed (result.getLong("accessTime")); + data.setCreated(result.getLong("createTime")); + data.setLastNode(result.getString("lastNode")); + data.setLastSaved(result.getLong("lastSavedTime")); + data.setExpiryTime(result.getLong("expiryTime")); + data.setCanonicalContext(result.getString("contextPath")); + data.setVirtualHost(result.getString("virtualHost")); + + InputStream is = ((JDBCSessionIdManager)getIdManager())._dbAdaptor.getBlobInputStream(result, "map"); + ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is); + Object o = ois.readObject(); + data.setAttributeMap((ConcurrentHashMap)o); + ois.close(); + + if (Log.isDebugEnabled()) + Log.debug("LOADED session "+data); + } + return data; + } + finally + { + if (connection!=null) + connection.close(); + } + } + + /** + * Insert a session into the database. + * + * @param data + * @throws Exception + */ + protected void storeSession (SessionData data) + throws Exception + { + if (data==null) + return; + + //put into the database + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + String rowId = calculateRowId(data); + + long now = System.currentTimeMillis(); + connection.setAutoCommit(true); + statement = connection.prepareStatement(__insertSession); + statement.setString(1, rowId); //rowId + statement.setString(2, data.getId()); //session id + statement.setString(3, data.getCanonicalContext()); //context path + statement.setString(4, data.getVirtualHost()); //first vhost + statement.setString(5, getIdManager().getWorkerName());//my node id + statement.setLong(6, data.getAccessed());//accessTime + statement.setLong(7, data.getLastAccessed()); //lastAccessTime + statement.setLong(8, data.getCreated()); //time created + statement.setLong(9, data.getCookieSet());//time cookie was set + statement.setLong(10, now); //last saved time + statement.setLong(11, data.getExpiryTime()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(data.getAttributeMap()); + byte[] bytes = baos.toByteArray(); + + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob + + statement.executeUpdate(); + data.setRowId(rowId); //set it on the in-memory data as well as in db + data.setLastSaved(now); + + + if (Log.isDebugEnabled()) + Log.debug("Stored session "+data); + } + finally + { + if (connection!=null) + connection.close(); + } + } + + + /** + * Update data on an existing persisted session. + * + * @param data + * @throws Exception + */ + protected void updateSession (SessionData data) + throws Exception + { + if (data==null) + return; + + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + long now = System.currentTimeMillis(); + connection.setAutoCommit(true); + statement = connection.prepareStatement(__updateSession); + statement.setString(1, getIdManager().getWorkerName());//my node id + statement.setLong(2, data.getAccessed());//accessTime + statement.setLong(3, data.getLastAccessed()); //lastAccessTime + statement.setLong(4, now); //last saved time + statement.setLong(5, data.getExpiryTime()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(data.getAttributeMap()); + byte[] bytes = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + + statement.setBinaryStream(6, bais, bytes.length);//attribute map as blob + statement.setString(7, data.getRowId()); //rowId + statement.executeUpdate(); + + data.setLastSaved(now); + if (Log.isDebugEnabled()) + Log.debug("Updated session "+data); + } + finally + { + if (connection!=null) + connection.close(); + } + } + + + /** + * Update the node on which the session was last seen to be my node. + * + * @param data + * @throws Exception + */ + protected void updateSessionNode (SessionData data) + throws Exception + { + String nodeId = getIdManager().getWorkerName(); + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + connection.setAutoCommit(true); + statement = connection.prepareStatement(__updateSessionNode); + statement.setString(1, nodeId); + statement.setString(2, data.getRowId()); + statement.executeUpdate(); + statement.close(); + if (Log.isDebugEnabled()) + Log.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId); + } + finally + { + if (connection!=null) + connection.close(); + } + } + + /** + * Persist the time the session was last accessed. + * + * @param data + * @throws Exception + */ + private void updateSessionAccessTime (SessionData data) + throws Exception + { + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + long now = System.currentTimeMillis(); + connection.setAutoCommit(true); + statement = connection.prepareStatement(__updateSessionAccessTime); + statement.setString(1, getIdManager().getWorkerName()); + statement.setLong(2, data.getAccessed()); + statement.setLong(3, data.getLastAccessed()); + statement.setLong(4, now); + statement.setLong(5, data.getExpiryTime()); + statement.setString(6, data.getRowId()); + statement.executeUpdate(); + data.setLastSaved(now); + statement.close(); + if (Log.isDebugEnabled()) + Log.debug("Updated access time session id="+data.getId()); + } + finally + { + if (connection!=null) + connection.close(); + } + } + + + + + /** + * Delete a session from the database. Should only be called + * when the session has been invalidated. + * + * @param data + * @throws Exception + */ + protected void deleteSession (SessionData data) + throws Exception + { + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + connection.setAutoCommit(true); + statement = connection.prepareStatement(__deleteSession); + statement.setString(1, data.getRowId()); + statement.executeUpdate(); + if (Log.isDebugEnabled()) + Log.debug("Deleted Session "+data); + } + finally + { + if (connection!=null) + connection.close(); + } + } + + + + /** + * Get a connection from the driver. + * @return + * @throws SQLException + */ + private Connection getConnection () + throws SQLException + { + return ((JDBCSessionIdManager)getIdManager()).getConnection(); + } + + /** + * Calculate a unique id for this session across the cluster. + * + * Unique id is composed of: contextpath_virtualhost0_sessionid + * @param data + * @return + */ + private String calculateRowId (SessionData data) + { + String rowId = canonicalize(_context.getContextPath()); + rowId = rowId + "_" + getVirtualHost(_context); + rowId = rowId+"_"+data.getId(); + return rowId; + } + + /** + * Get the first virtual host for the context. + * + * Used to help identify the exact session/contextPath. + * + * @return 0.0.0.0 if no virtual host is defined + */ + private String getVirtualHost (ContextHandler.Context context) + { + String vhost = "0.0.0.0"; + + if (context==null) + return vhost; + + String [] vhosts = context.getContextHandler().getVirtualHosts(); + if (vhosts==null || vhosts.length==0 || vhosts[0]==null) + return vhost; + + return vhosts[0]; + } + + /** + * Make an acceptable file name from a context path. + * + * @param path + * @return + */ + private String canonicalize (String path) + { + if (path==null) + return ""; + + return path.replace('/', '_').replace('.','_').replace('\\','_'); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java new file mode 100644 index 00000000000..cac62c2e41e --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java @@ -0,0 +1,291 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.session; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.EventListener; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.SessionTrackingMode; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RetryRequest; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** SessionHandler. + * + * + * + */ +public class SessionHandler extends HandlerWrapper +{ + public final static EnumSet DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL); + + /* -------------------------------------------------------------- */ + private SessionManager _sessionManager; + + /* ------------------------------------------------------------ */ + /** Constructor. + * Construct a SessionHandler witha a HashSessionManager with a standard + * java.util.Random generator is created. + */ + public SessionHandler() + { + this(new HashSessionManager()); + } + + /* ------------------------------------------------------------ */ + /** + * @param manager The session manager + */ + public SessionHandler(SessionManager manager) + { + setSessionManager(manager); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionManager. + */ + public SessionManager getSessionManager() + { + return _sessionManager; + } + + /* ------------------------------------------------------------ */ + /** + * @param sessionManager The sessionManager to set. + */ + public void setSessionManager(SessionManager sessionManager) + { + if (isStarted()) + throw new IllegalStateException(); + SessionManager old_session_manager = _sessionManager; + + if (getServer()!=null) + getServer().getContainer().update(this, old_session_manager, sessionManager, "sessionManager",true); + + if (sessionManager!=null) + sessionManager.setSessionHandler(this); + + _sessionManager = sessionManager; + + if (old_session_manager!=null) + old_session_manager.setSessionHandler(null); + } + + + /* ------------------------------------------------------------ */ + public void setServer(Server server) + { + Server old_server=getServer(); + if (old_server!=null && old_server!=server) + old_server.getContainer().update(this, _sessionManager, null, "sessionManager",true); + super.setServer(server); + if (server!=null && server!=old_server) + server.getContainer().update(this, null,_sessionManager, "sessionManager",true); + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + _sessionManager.start(); + super.doStart(); + } + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStop() + */ + protected void doStop() throws Exception + { + super.doStop(); + _sessionManager.stop(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + setRequestedId(request); + + Request base_request = (request instanceof Request) ? (Request)request:HttpConnection.getCurrentConnection().getRequest(); + SessionManager old_session_manager=null; + HttpSession old_session=null; + + try + { + old_session_manager = base_request.getSessionManager(); + old_session = base_request.getSession(false); + + if (old_session_manager != _sessionManager) + { + // new session context + base_request.setSessionManager(_sessionManager); + base_request.setSession(null); + } + + // access any existing session + HttpSession session=null; + if (_sessionManager!=null) + { + session=base_request.getSession(false); + if (session!=null) + { + if(session!=old_session) + { + Cookie cookie = _sessionManager.access(session,request.isSecure()); + if (cookie!=null ) // Handle changed ID or max-age refresh + response.addCookie(cookie); + } + } + else + { + session=base_request.recoverNewSession(_sessionManager); + if (session!=null) + base_request.setSession(session); + } + } + + if(Log.isDebugEnabled()) + { + Log.debug("sessionManager="+_sessionManager); + Log.debug("session="+session); + } + + getHandler().handle(target, request, response); + } + catch (RetryRequest r) + { + HttpSession session=base_request.getSession(false); + if (session!=null && session.isNew()) + base_request.saveNewSession(_sessionManager,session); + throw r; + } + finally + { + HttpSession session=request.getSession(false); + + if (old_session_manager != _sessionManager) + { + //leaving context, free up the session + if (session!=null) + _sessionManager.complete(session); + base_request.setSessionManager(old_session_manager); + base_request.setSession(old_session); + } + } + } + + /* ------------------------------------------------------------ */ + /** Look for a requested session ID in cookies and URI parameters + * @param request + * @param dispatch + */ + protected void setRequestedId(HttpServletRequest request) + { + Request base_request = (request instanceof Request) ? (Request)request:HttpConnection.getCurrentConnection().getRequest(); + String requested_session_id=request.getRequestedSessionId(); + if (!DispatcherType.REQUEST.equals(request.getDispatcherType()) || requested_session_id!=null) + { + return; + } + + SessionManager sessionManager = getSessionManager(); + boolean requested_session_id_from_cookie=false; + + // Look for session id cookie + if (_sessionManager.isUsingCookies()) + { + Cookie[] cookies=request.getCookies(); + if (cookies!=null && cookies.length>0) + { + for (int i=0;i=0) + { + String path_params=uri.substring(semi+1); + + // check if there is a url encoded session param. + String param=sessionManager.getSessionIdPathParameterName(); + if (param!=null && path_params!=null && path_params.startsWith(param)) + { + requested_session_id = path_params.substring(sessionManager.getSessionIdPathParameterName().length()+1); + if(Log.isDebugEnabled())Log.debug("Got Session ID "+requested_session_id+" from URL"); + } + } + } + + base_request.setRequestedSessionId(requested_session_id); + base_request.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie); + } + + /* ------------------------------------------------------------ */ + /** + * @param listener + */ + public void addEventListener(EventListener listener) + { + if(_sessionManager!=null) + _sessionManager.addEventListener(listener); + } + + /* ------------------------------------------------------------ */ + public void clearEventListeners() + { + if(_sessionManager!=null) + _sessionManager.clearEventListeners(); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java new file mode 100644 index 00000000000..3c1cabf1d06 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java @@ -0,0 +1,83 @@ +// ======================================================================== +// Copyright (c) 2001-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.ssl; + +/* --------------------------------------------------------------------- */ +/** + * Jetty Servlet SSL support utilities. + *

      + * A collection of utilities required to support the SSL requirements of the Servlet 2.2 and 2.3 + * specs. + * + *

      + * Used by the SSL listener classes. + * + * + */ +public class ServletSSL +{ + /* ------------------------------------------------------------ */ + /** + * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream + * cipher key strength. i.e. How much entropy material is in the key material being fed into the + * encryption routines. + * + *

      + * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol + * Version 1.0, Appendix C. CipherSuite definitions: + * + *

      +     *                         Effective 
      +     *     Cipher       Type    Key Bits 
      +     * 		       	       
      +     *     NULL       * Stream     0     
      +     *     IDEA_CBC     Block    128     
      +     *     RC2_CBC_40 * Block     40     
      +     *     RC4_40     * Stream    40     
      +     *     RC4_128      Stream   128     
      +     *     DES40_CBC  * Block     40     
      +     *     DES_CBC      Block     56     
      +     *     3DES_EDE_CBC Block    168     
      +     * 
      + * + * @param cipherSuite String name of the TLS cipher suite. + * @return int indicating the effective key entropy bit-length. + */ + public static final int deduceKeyLength(String cipherSuite) + { + // Roughly ordered from most common to least common. + if (cipherSuite == null) + return 0; + else if (cipherSuite.indexOf("WITH_AES_256_") >= 0) + return 256; + else if (cipherSuite.indexOf("WITH_RC4_128_") >= 0) + return 128; + else if (cipherSuite.indexOf("WITH_AES_128_") >= 0) + return 128; + else if (cipherSuite.indexOf("WITH_RC4_40_") >= 0) + return 40; + else if (cipherSuite.indexOf("WITH_3DES_EDE_CBC_") >= 0) + return 168; + else if (cipherSuite.indexOf("WITH_IDEA_CBC_") >= 0) + return 128; + else if (cipherSuite.indexOf("WITH_RC2_CBC_40_") >= 0) + return 40; + else if (cipherSuite.indexOf("WITH_DES40_CBC_") >= 0) + return 40; + else if (cipherSuite.indexOf("WITH_DES_CBC_") >= 0) + return 56; + else + return 0; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java new file mode 100644 index 00000000000..4169cc3b659 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java @@ -0,0 +1,703 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.ssl; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.http.ssl.SslSelectChannelEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.NIOBuffer; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** + * SslSelectChannelConnector. + * + * @org.apache.xbean.XBean element="sslConnector" description="Creates an NIO ssl connector" + * + * + * + */ +public class SslSelectChannelConnector extends SelectChannelConnector +{ + /** + * The name of the SSLSession attribute that will contain any cached + * information. + */ + static final String CACHED_INFO_ATTR=CachedInfo.class.getName(); + + /** Default value for the keystore location path. */ + public static final String DEFAULT_KEYSTORE=System.getProperty("user.home")+File.separator+".keystore"; + + /** String name of key password property. */ + public static final String KEYPASSWORD_PROPERTY="jetty.ssl.keypassword"; + + /** String name of keystore password property. */ + public static final String PASSWORD_PROPERTY="jetty.ssl.password"; + + /** Default value for the cipher Suites. */ + private String _excludeCipherSuites[]=null; + + /** Default value for the keystore location path. */ + private String _keystore=DEFAULT_KEYSTORE; + private String _keystoreType="JKS"; // type of the key store + + /** Set to true if we require client certificate authentication. */ + private boolean _needClientAuth=false; + private boolean _wantClientAuth=false; + + private transient Password _password; + private transient Password _keyPassword; + private transient Password _trustPassword; + private String _protocol="TLS"; + private String _algorithm="SunX509"; // cert algorithm + private String _provider; + private String _secureRandomAlgorithm; // cert algorithm + private String _sslKeyManagerFactoryAlgorithm=(Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security + .getProperty("ssl.KeyManagerFactory.algorithm")); // cert + // algorithm + + private String _sslTrustManagerFactoryAlgorithm=(Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security + .getProperty("ssl.TrustManagerFactory.algorithm")); // cert + // algorithm + + private String _truststore; + private String _truststoreType="JKS"; // type of the key store + private SSLContext _context; + + private int _packetBufferSize; + private int _applicationBufferSize; + private ConcurrentLinkedQueue _packetBuffers = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue _applicationBuffers = new ConcurrentLinkedQueue(); + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.io.AbstractBuffers#getBuffer(int) + */ + public Buffer getBuffer(int size) + { + // TODO why is this reimplemented? + Buffer buffer; + if (size==_applicationBufferSize) + { + buffer = _applicationBuffers.poll(); + if (buffer==null) + buffer=new IndirectNIOBuffer(size); + } + else if (size==_packetBufferSize) + { + buffer = _packetBuffers.poll(); + if (buffer==null) + buffer=getUseDirectBuffers() + ?(NIOBuffer)new DirectNIOBuffer(size) + :(NIOBuffer)new IndirectNIOBuffer(size); + } + else + buffer=super.getBuffer(size); + + return buffer; + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.io.AbstractBuffers#returnBuffer(org.eclipse.io.Buffer) + */ + public void returnBuffer(Buffer buffer) + { + buffer.clear(); + int size=buffer.capacity(); + ByteBuffer bbuf = ((NIOBuffer)buffer).getByteBuffer(); + bbuf.position(0); + bbuf.limit(size); + + if (size==_applicationBufferSize) + _applicationBuffers.add(buffer); + else if (size==_packetBufferSize) + _packetBuffers.add(buffer); + else + super.returnBuffer(buffer); + } + + + + /** + * Return the chain of X509 certificates used to negotiate the SSL Session. + *

      + * Note: in order to do this we must convert a + * javax.security.cert.X509Certificate[], as used by JSSE to a + * java.security.cert.X509Certificate[],as required by the Servlet specs. + * + * @param sslSession + * the javax.net.ssl.SSLSession to use as the source of the + * cert chain. + * @return the chain of java.security.cert.X509Certificates used to + * negotiate the SSL connection.
      + * Will be null if the chain is missing or empty. + */ + private static X509Certificate[] getCertChain(SSLSession sslSession) + { + try + { + javax.security.cert.X509Certificate javaxCerts[]=sslSession.getPeerCertificateChain(); + if (javaxCerts==null||javaxCerts.length==0) + return null; + + int length=javaxCerts.length; + X509Certificate[] javaCerts=new X509Certificate[length]; + + java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509"); + for (int i=0; i + * This allows the required attributes to be set for SSL requests.
      + * The requirements of the Servlet specs are: + *

        + *
      • an attribute named "javax.servlet.request.ssl_session_id" of type + * String (since Servlet Spec 3.0).
      • + *
      • an attribute named "javax.servlet.request.cipher_suite" of type + * String.
      • + *
      • an attribute named "javax.servlet.request.key_size" of type Integer.
      • + *
      • an attribute named "javax.servlet.request.X509Certificate" of type + * java.security.cert.X509Certificate[]. This is an array of objects of type + * X509Certificate, the order of this array is defined as being in ascending + * order of trust. The first certificate in the chain is the one set by the + * client, the next is the one used to authenticate the first, and so on. + *
      • + *
      + * + * @param endpoint + * The Socket the request arrived on. This should be a + * {@link SocketEndPoint} wrapping a {@link SSLSocket}. + * @param request + * HttpRequest to be customised. + */ + @Override + public void customize(EndPoint endpoint, Request request) throws IOException + { + super.customize(endpoint,request); + request.setScheme(HttpSchemes.HTTPS); + + SslSelectChannelEndPoint sslHttpChannelEndpoint=(SslSelectChannelEndPoint)endpoint; + + SSLEngine sslEngine=sslHttpChannelEndpoint.getSSLEngine(); + + try + { + SSLSession sslSession=sslEngine.getSession(); + String cipherSuite=sslSession.getCipherSuite(); + Integer keySize; + X509Certificate[] certs; + String idStr; + + CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR); + if (cachedInfo!=null) + { + keySize=cachedInfo.getKeySize(); + certs=cachedInfo.getCerts(); + idStr=cachedInfo.getIdStr(); + } + else + { + keySize=new Integer(ServletSSL.deduceKeyLength(cipherSuite)); + certs=getCertChain(sslSession); + byte[] bytes = sslSession.getId(); + idStr = TypeUtil.toHexString(bytes); + cachedInfo=new CachedInfo(keySize,certs,idStr); + sslSession.putValue(CACHED_INFO_ATTR,cachedInfo); + } + + if (certs!=null) + request.setAttribute("javax.servlet.request.X509Certificate",certs); + + request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite); + request.setAttribute("javax.servlet.request.key_size",keySize); + request.setAttribute("javax.servlet.request.ssl_session_id", idStr); + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION,e); + } + } + + /* ------------------------------------------------------------ */ + public SslSelectChannelConnector() + { + } + + /** + * + * @deprecated As of Java Servlet API 2.0, with no replacement. + * + */ + public String[] getCipherSuites() + { + return getExcludeCipherSuites(); + } + + public String[] getExcludeCipherSuites() + { + return _excludeCipherSuites; + } + + /** + * + * @deprecated As of Java Servlet API 2.0, with no replacement. + * + * + */ + public void setCipherSuites(String[] cipherSuites) + { + setExcludeCipherSuites(cipherSuites); + } + + public void setExcludeCipherSuites(String[] cipherSuites) + { + this._excludeCipherSuites=cipherSuites; + } + + /* ------------------------------------------------------------ */ + public void setPassword(String password) + { + _password=Password.getPassword(PASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + public void setTrustPassword(String password) + { + _trustPassword=Password.getPassword(PASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + public void setKeyPassword(String password) + { + _keyPassword=Password.getPassword(KEYPASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + public String getAlgorithm() + { + return (this._algorithm); + } + + /* ------------------------------------------------------------ */ + public void setAlgorithm(String algorithm) + { + this._algorithm=algorithm; + } + + /* ------------------------------------------------------------ */ + public String getProtocol() + { + return _protocol; + } + + /* ------------------------------------------------------------ */ + public void setProtocol(String protocol) + { + _protocol=protocol; + } + + /* ------------------------------------------------------------ */ + public void setKeystore(String keystore) + { + _keystore=keystore; + } + + /* ------------------------------------------------------------ */ + public String getKeystore() + { + return _keystore; + } + + /* ------------------------------------------------------------ */ + public String getKeystoreType() + { + return (_keystoreType); + } + + /* ------------------------------------------------------------ */ + public boolean getNeedClientAuth() + { + return _needClientAuth; + } + + /* ------------------------------------------------------------ */ + public boolean getWantClientAuth() + { + return _wantClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * Set the value of the needClientAuth property + * + * @param needClientAuth + * true iff we require client certificate authentication. + */ + public void setNeedClientAuth(boolean needClientAuth) + { + _needClientAuth=needClientAuth; + } + + public void setWantClientAuth(boolean wantClientAuth) + { + _wantClientAuth=wantClientAuth; + } + + /* ------------------------------------------------------------ */ + public void setKeystoreType(String keystoreType) + { + _keystoreType=keystoreType; + } + + /* ------------------------------------------------------------ */ + public String getProvider() + { + return _provider; + } + + public String getSecureRandomAlgorithm() + { + return (this._secureRandomAlgorithm); + } + + /* ------------------------------------------------------------ */ + public String getSslKeyManagerFactoryAlgorithm() + { + return (this._sslKeyManagerFactoryAlgorithm); + } + + /* ------------------------------------------------------------ */ + public String getSslTrustManagerFactoryAlgorithm() + { + return (this._sslTrustManagerFactoryAlgorithm); + } + + /* ------------------------------------------------------------ */ + public String getTruststore() + { + return _truststore; + } + + /* ------------------------------------------------------------ */ + public String getTruststoreType() + { + return _truststoreType; + } + + /* ------------------------------------------------------------ */ + public void setProvider(String _provider) + { + this._provider=_provider; + } + + /* ------------------------------------------------------------ */ + public void setSecureRandomAlgorithm(String algorithm) + { + this._secureRandomAlgorithm=algorithm; + } + + /* ------------------------------------------------------------ */ + public void setSslKeyManagerFactoryAlgorithm(String algorithm) + { + this._sslKeyManagerFactoryAlgorithm=algorithm; + } + + /* ------------------------------------------------------------ */ + public void setSslTrustManagerFactoryAlgorithm(String algorithm) + { + this._sslTrustManagerFactoryAlgorithm=algorithm; + } + + public void setTruststore(String truststore) + { + _truststore=truststore; + } + + public void setTruststoreType(String truststoreType) + { + _truststoreType=truststoreType; + } + + public void setSslContext(SSLContext sslContext) { + this._context = sslContext; + } + + /* ------------------------------------------------------------ */ + /** + * By default, we're confidential, given we speak SSL. But, if we've been + * told about an confidential port, and said port is not our port, then + * we're not. This allows separation of listeners providing INTEGRAL versus + * CONFIDENTIAL constraints, such as one SSL listener configured to require + * client certs providing CONFIDENTIAL, whereas another SSL listener not + * requiring client certs providing mere INTEGRAL constraints. + */ + public boolean isConfidential(Request request) + { + final int confidentialPort=getConfidentialPort(); + return confidentialPort==0||confidentialPort==request.getServerPort(); + } + + /* ------------------------------------------------------------ */ + /** + * By default, we're integral, given we speak SSL. But, if we've been told + * about an integral port, and said port is not our port, then we're not. + * This allows separation of listeners providing INTEGRAL versus + * CONFIDENTIAL constraints, such as one SSL listener configured to require + * client certs providing CONFIDENTIAL, whereas another SSL listener not + * requiring client certs providing mere INTEGRAL constraints. + */ + public boolean isIntegral(Request request) + { + final int integralPort=getIntegralPort(); + return integralPort==0||integralPort==request.getServerPort(); + } + + /* ------------------------------------------------------------------------------- */ + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException + { + return new SslSelectChannelEndPoint(this,channel,selectSet,key,createSSLEngine()) + { + // TODO remove this hack + public boolean isReadyForDispatch() + { + Request request = ((HttpConnection)getConnection()).getRequest(); + return super.isReadyForDispatch() && !(request.getAsyncRequest().isSuspended()); + } + }; + } + + /* ------------------------------------------------------------------------------- */ + protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) + { + HttpConnection connection=(HttpConnection)super.newConnection(channel,endpoint); + ((HttpParser)connection.getParser()).setForceContentBuffer(true); + return connection; + } + + /* ------------------------------------------------------------ */ + protected SSLEngine createSSLEngine() throws IOException + { + SSLEngine engine=null; + try + { + engine=_context.createSSLEngine(); + engine.setUseClientMode(false); + + if (_wantClientAuth) + engine.setWantClientAuth(_wantClientAuth); + if (_needClientAuth) + engine.setNeedClientAuth(_needClientAuth); + + if (_excludeCipherSuites!=null&&_excludeCipherSuites.length>0) + { + List excludedCSList=Arrays.asList(_excludeCipherSuites); + String[] enabledCipherSuites=engine.getEnabledCipherSuites(); + List enabledCSList=new ArrayList(Arrays.asList(enabledCipherSuites)); + + for (String cipherName : excludedCSList) + { + if (enabledCSList.contains(cipherName)) + { + enabledCSList.remove(cipherName); + } + } + enabledCipherSuites=enabledCSList.toArray(new String[enabledCSList.size()]); + + engine.setEnabledCipherSuites(enabledCipherSuites); + } + + } + catch (Exception e) + { + Log.warn("Error creating sslEngine -- closing this connector",e); + close(); + throw new IllegalStateException(e); + } + return engine; + } + + + protected void doStart() throws Exception + { + if (_context == null) { + _context=createSSLContext(); + } + + SSLEngine engine=createSSLEngine(); + SSLSession ssl_session=engine.getSession(); + + setHeaderBufferSize(ssl_session.getApplicationBufferSize()); + setRequestBufferSize(ssl_session.getApplicationBufferSize()); + setResponseBufferSize(ssl_session.getApplicationBufferSize()); + + super.doStart(); + } + + protected SSLContext createSSLContext() throws Exception + { + if (_truststore==null) + { + _truststore=_keystore; + _truststoreType=_keystoreType; + } + + InputStream keystoreInputStream = null; + + KeyManager[] keyManagers=null; + KeyStore keyStore = null; + try + { + if (_keystore!=null) + { + keystoreInputStream=Resource.newResource(_keystore).getInputStream(); + keyStore = KeyStore.getInstance(_keystoreType); + keyStore.load(keystoreInputStream,_password==null?null:_password.toString().toCharArray()); + } + } + finally + { + if (keystoreInputStream != null) + keystoreInputStream.close(); + } + + KeyManagerFactory keyManagerFactory=KeyManagerFactory.getInstance(_sslKeyManagerFactoryAlgorithm); + keyManagerFactory.init(keyStore,_keyPassword==null?(_password==null?null:_password.toString().toCharArray()):_keyPassword.toString().toCharArray()); + keyManagers=keyManagerFactory.getKeyManagers(); + + + TrustManager[] trustManagers=null; + InputStream truststoreInputStream = null; + KeyStore trustStore = null; + try + { + if (_truststore!=null) + { + truststoreInputStream = Resource.newResource(_truststore).getInputStream(); + trustStore=KeyStore.getInstance(_truststoreType); + trustStore.load(truststoreInputStream,_trustPassword==null?null:_trustPassword.toString().toCharArray()); + } + } + finally + { + if (truststoreInputStream != null) + truststoreInputStream.close(); + } + + + TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(_sslTrustManagerFactoryAlgorithm); + trustManagerFactory.init(trustStore); + trustManagers=trustManagerFactory.getTrustManagers(); + + SecureRandom secureRandom=_secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm); + SSLContext context=_provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol,_provider); + context.init(keyManagers,trustManagers,secureRandom); + return context; + } + + /** + * Simple bundle of information that is cached in the SSLSession. Stores the + * effective keySize and the client certificate chain. + */ + private class CachedInfo + { + private X509Certificate[] _certs; + private Integer _keySize; + private String _idStr; + + CachedInfo(Integer keySize, X509Certificate[] certs,String idStr) + { + this._keySize=keySize; + this._certs=certs; + this._idStr=idStr; + } + + X509Certificate[] getCerts() + { + return _certs; + } + + Integer getKeySize() + { + return _keySize; + } + + String getIdStr() + { + return _idStr; + } + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java new file mode 100644 index 00000000000..29193027927 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java @@ -0,0 +1,665 @@ +// ======================================================================== +// Copyright (c) 2000-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.ssl; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** + * JSSE Socket Listener. + * + * This specialization of HttpListener is an abstract listener that can be used as the basis for a + * specific JSSE listener. + * + * This is heavily based on the work from Court Demas, which in turn is based on the work from Forge + * Research. + * + * @org.apache.xbean.XBean element="sslSocketConnector" description="Creates an ssl socket connector" + * + * + * + * + * + */ +public class SslSocketConnector extends SocketConnector +{ + /** + * The name of the SSLSession attribute that will contain any cached information. + */ + static final String CACHED_INFO_ATTR = CachedInfo.class.getName(); + + /** Default value for the keystore location path. */ + public static final String DEFAULT_KEYSTORE = System.getProperty("user.home") + File.separator + + ".keystore"; + + /** String name of key password property. */ + public static final String KEYPASSWORD_PROPERTY = "jetty.ssl.keypassword"; + + /** String name of keystore password property. */ + public static final String PASSWORD_PROPERTY = "jetty.ssl.password"; + + /** + * Return the chain of X509 certificates used to negotiate the SSL Session. + *

      + * Note: in order to do this we must convert a javax.security.cert.X509Certificate[], as used by + * JSSE to a java.security.cert.X509Certificate[],as required by the Servlet specs. + * + * @param sslSession the javax.net.ssl.SSLSession to use as the source of the cert chain. + * @return the chain of java.security.cert.X509Certificates used to negotiate the SSL + * connection.
      + * Will be null if the chain is missing or empty. + */ + private static X509Certificate[] getCertChain(SSLSession sslSession) + { + try + { + javax.security.cert.X509Certificate javaxCerts[] = sslSession.getPeerCertificateChain(); + if (javaxCerts == null || javaxCerts.length == 0) + return null; + + int length = javaxCerts.length; + X509Certificate[] javaCerts = new X509Certificate[length]; + + java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509"); + for (int i = 0; i < length; i++) + { + byte bytes[] = javaxCerts[i].getEncoded(); + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + javaCerts[i] = (X509Certificate) cf.generateCertificate(stream); + } + + return javaCerts; + } + catch (SSLPeerUnverifiedException pue) + { + return null; + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION, e); + return null; + } + } + + + + /** Default value for the cipher Suites. */ + private String _excludeCipherSuites[] = null; + + /** Default value for the keystore location path. */ + private String _keystore=DEFAULT_KEYSTORE ; + private String _keystoreType = "JKS"; // type of the key store + + /** Set to true if we require client certificate authentication. */ + private boolean _needClientAuth = false; + private transient Password _password; + private transient Password _keyPassword; + private transient Password _trustPassword; + private String _protocol= "TLS"; + private String _provider; + private String _secureRandomAlgorithm; // cert algorithm + private String _sslKeyManagerFactoryAlgorithm = (Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.KeyManagerFactory.algorithm")); // cert algorithm + private String _sslTrustManagerFactoryAlgorithm = (Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.TrustManagerFactory.algorithm")); // cert algorithm + + private String _truststore; + private String _truststoreType = "JKS"; // type of the key store + + /** Set to true if we would like client certificate authentication. */ + private boolean _wantClientAuth = false; + private int _handshakeTimeout = 0; //0 means use maxIdleTime + + private SSLContext _context; + + + /* ------------------------------------------------------------ */ + /** + * Constructor. + */ + public SslSocketConnector() + { + super(); + } + + + /* ------------------------------------------------------------ */ + public void accept(int acceptorID) + throws IOException, InterruptedException + { + Socket socket = _serverSocket.accept(); + configure(socket); + + Connection connection=new SslConnection(socket); + connection.dispatch(); + } + + /* ------------------------------------------------------------ */ + protected void configure(Socket socket) + throws IOException + { + super.configure(socket); + } + + /* ------------------------------------------------------------ */ + protected SSLServerSocketFactory createFactory() + throws Exception + { + SSLContext context = _context; + if (context == null) { + if (_truststore==null) + { + _truststore=_keystore; + _truststoreType=_keystoreType; + } + + KeyManager[] keyManagers = null; + InputStream keystoreInputStream = null; + if (_keystore != null) + keystoreInputStream = Resource.newResource(_keystore).getInputStream(); + KeyStore keyStore = KeyStore.getInstance(_keystoreType); + keyStore.load(keystoreInputStream, _password==null?null:_password.toString().toCharArray()); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_sslKeyManagerFactoryAlgorithm); + keyManagerFactory.init(keyStore,_keyPassword==null?null:_keyPassword.toString().toCharArray()); + keyManagers = keyManagerFactory.getKeyManagers(); + + TrustManager[] trustManagers = null; + InputStream truststoreInputStream = null; + if (_truststore != null) + truststoreInputStream = Resource.newResource(_truststore).getInputStream(); + KeyStore trustStore = KeyStore.getInstance(_truststoreType); + trustStore.load(truststoreInputStream,_trustPassword==null?null:_trustPassword.toString().toCharArray()); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_sslTrustManagerFactoryAlgorithm); + trustManagerFactory.init(trustStore); + trustManagers = trustManagerFactory.getTrustManagers(); + + + SecureRandom secureRandom = _secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm); + + context = _provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol, _provider); + + context.init(keyManagers, trustManagers, secureRandom); + } + + return context.getServerSocketFactory(); + } + + /* ------------------------------------------------------------ */ + /** + * Allow the Listener a chance to customise the request. before the server does its stuff.
      + * This allows the required attributes to be set for SSL requests.
      + * The requirements of the Servlet specs are: + *

        + *
      • an attribute named "javax.servlet.request.ssl_id" of type String (since Spec 3.0).
      • + *
      • an attribute named "javax.servlet.request.cipher_suite" of type String.
      • + *
      • an attribute named "javax.servlet.request.key_size" of type Integer.
      • + *
      • an attribute named "javax.servlet.request.X509Certificate" of type + * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate, + * the order of this array is defined as being in ascending order of trust. The first + * certificate in the chain is the one set by the client, the next is the one used to + * authenticate the first, and so on.
      • + *
      + * + * @param endpoint The Socket the request arrived on. + * This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}. + * @param request HttpRequest to be customised. + */ + public void customize(EndPoint endpoint, Request request) + throws IOException + { + super.customize(endpoint, request); + request.setScheme(HttpSchemes.HTTPS); + + SocketEndPoint socket_end_point = (SocketEndPoint)endpoint; + SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport(); + + try + { + SSLSession sslSession = sslSocket.getSession(); + String cipherSuite = sslSession.getCipherSuite(); + Integer keySize; + String idStr; + X509Certificate[] certs; + + CachedInfo cachedInfo = (CachedInfo) sslSession.getValue(CACHED_INFO_ATTR); + if (cachedInfo != null) + { + keySize = cachedInfo.getKeySize(); + certs = cachedInfo.getCerts(); + idStr = cachedInfo.getIdStr(); + } + else + { + keySize = new Integer(ServletSSL.deduceKeyLength(cipherSuite)); + certs = getCertChain(sslSession); + byte[] idBytes = sslSession.getId(); + idStr = TypeUtil.toHexString(idBytes); + cachedInfo = new CachedInfo(keySize, certs,idStr); + sslSession.putValue(CACHED_INFO_ATTR, cachedInfo); + } + + if (certs != null) + request.setAttribute("javax.servlet.request.X509Certificate", certs); + else if (_needClientAuth) // Sanity check + throw new IllegalStateException("no client auth"); + + request.setAttribute("javax.servlet.request.ssl_session_id", idStr); + request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite); + request.setAttribute("javax.servlet.request.key_size", keySize); + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION, e); + } + } + + /* ------------------------------------------------------------ */ + public String[] getExcludeCipherSuites() { + return _excludeCipherSuites; + } + + /* ------------------------------------------------------------ */ + public String getKeystore() + { + return _keystore; + } + + /* ------------------------------------------------------------ */ + public String getKeystoreType() + { + return (_keystoreType); + } + + /* ------------------------------------------------------------ */ + public boolean getNeedClientAuth() + { + return _needClientAuth; + } + + /* ------------------------------------------------------------ */ + public String getProtocol() + { + return _protocol; + } + + /* ------------------------------------------------------------ */ + public String getProvider() { + return _provider; + } + + /* ------------------------------------------------------------ */ + public String getSecureRandomAlgorithm() + { + return (this._secureRandomAlgorithm); + } + + /* ------------------------------------------------------------ */ + public String getSslKeyManagerFactoryAlgorithm() + { + return (this._sslKeyManagerFactoryAlgorithm); + } + + /* ------------------------------------------------------------ */ + public String getSslTrustManagerFactoryAlgorithm() + { + return (this._sslTrustManagerFactoryAlgorithm); + } + + /* ------------------------------------------------------------ */ + public String getTruststore() + { + return _truststore; + } + + /* ------------------------------------------------------------ */ + public String getTruststoreType() + { + return _truststoreType; + } + + /* ------------------------------------------------------------ */ + public boolean getWantClientAuth() + { + return _wantClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * By default, we're confidential, given we speak SSL. But, if we've been told about an + * confidential port, and said port is not our port, then we're not. This allows separation of + * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener + * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not + * requiring client certs providing mere INTEGRAL constraints. + */ + public boolean isConfidential(Request request) + { + final int confidentialPort = getConfidentialPort(); + return confidentialPort == 0 || confidentialPort == request.getServerPort(); + } + + /* ------------------------------------------------------------ */ + /** + * By default, we're integral, given we speak SSL. But, if we've been told about an integral + * port, and said port is not our port, then we're not. This allows separation of listeners + * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to + * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring + * client certs providing mere INTEGRAL constraints. + */ + public boolean isIntegral(Request request) + { + final int integralPort = getIntegralPort(); + return integralPort == 0 || integralPort == request.getServerPort(); + } + + /* ------------------------------------------------------------ */ + /** + * @param addr The {@link SocketAddress address} that this server should listen on + * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)} + * @return A new {@link ServerSocket socket object} bound to the supplied address with all other + * settings as per the current configuration of this connector. + * @see #setWantClientAuth + * @see #setNeedClientAuth + * @see #setCipherSuites + * @exception IOException + */ + + /* ------------------------------------------------------------ */ + protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException + { + SSLServerSocketFactory factory = null; + SSLServerSocket socket = null; + + try + { + factory = createFactory(); + + socket = (SSLServerSocket) (host==null? + factory.createServerSocket(port,backlog): + factory.createServerSocket(port,backlog,InetAddress.getByName(host))); + + if (_wantClientAuth) + socket.setWantClientAuth(_wantClientAuth); + if (_needClientAuth) + socket.setNeedClientAuth(_needClientAuth); + + if (_excludeCipherSuites != null && _excludeCipherSuites.length >0) + { + List excludedCSList = Arrays.asList(_excludeCipherSuites); + String[] enabledCipherSuites = socket.getEnabledCipherSuites(); + List enabledCSList = new ArrayList(Arrays.asList(enabledCipherSuites)); + Iterator exIter = excludedCSList.iterator(); + + while (exIter.hasNext()) + { + String cipherName = (String)exIter.next(); + if (enabledCSList.contains(cipherName)) + { + enabledCSList.remove(cipherName); + } + } + enabledCipherSuites = (String[])enabledCSList.toArray(new String[enabledCSList.size()]); + + socket.setEnabledCipherSuites(enabledCipherSuites); + } + + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION, e); + throw new IOException("Could not create JsseListener: " + e.toString()); + } + return socket; + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public void setExcludeCipherSuites(String[] cipherSuites) { + this._excludeCipherSuites = cipherSuites; + } + + /* ------------------------------------------------------------ */ + public void setKeyPassword(String password) + { + _keyPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + /** + * @param keystore The resource path to the keystore, or null for built in keystores. + */ + public void setKeystore(String keystore) + { + _keystore = keystore; + } + + /* ------------------------------------------------------------ */ + public void setKeystoreType(String keystoreType) + { + _keystoreType = keystoreType; + } + + /* ------------------------------------------------------------ */ + /** + * Set the value of the needClientAuth property + * + * @param needClientAuth true iff we require client certificate authentication. + */ + public void setNeedClientAuth(boolean needClientAuth) + { + _needClientAuth = needClientAuth; + } + + /* ------------------------------------------------------------ */ + public void setPassword(String password) + { + _password = Password.getPassword(PASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + public void setTrustPassword(String password) + { + _trustPassword = Password.getPassword(PASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + public void setProtocol(String protocol) + { + _protocol = protocol; + } + + /* ------------------------------------------------------------ */ + public void setProvider(String _provider) { + this._provider = _provider; + } + + /* ------------------------------------------------------------ */ + public void setSecureRandomAlgorithm(String algorithm) + { + this._secureRandomAlgorithm = algorithm; + } + + /* ------------------------------------------------------------ */ + public void setSslKeyManagerFactoryAlgorithm(String algorithm) + { + this._sslKeyManagerFactoryAlgorithm = algorithm; + } + + /* ------------------------------------------------------------ */ + public void setSslTrustManagerFactoryAlgorithm(String algorithm) + { + this._sslTrustManagerFactoryAlgorithm = algorithm; + } + + + public void setTruststore(String truststore) + { + _truststore = truststore; + } + + + public void setTruststoreType(String truststoreType) + { + _truststoreType = truststoreType; + } + + public void setSslContext(SSLContext sslContext) + { + _context = sslContext; + } + + /* ------------------------------------------------------------ */ + /** + * Set the value of the _wantClientAuth property. This property is used when + * {@link #newServerSocket(SocketAddress, int) opening server sockets}. + * + * @param wantClientAuth true iff we want client certificate authentication. + * @see SSLServerSocket#setWantClientAuth + */ + public void setWantClientAuth(boolean wantClientAuth) + { + _wantClientAuth = wantClientAuth; + } + + /** + * Set the time in milliseconds for so_timeout during ssl handshaking + * @param msec a non-zero value will be used to set so_timeout during + * ssl handshakes. A zero value means the maxIdleTime is used instead. + */ + public void setHandshakeTimeout (int msec) + { + _handshakeTimeout = msec; + } + + + public int getHandshakeTimeout () + { + return _handshakeTimeout; + } + /** + * Simple bundle of information that is cached in the SSLSession. Stores the effective keySize + * and the client certificate chain. + */ + private class CachedInfo + { + private X509Certificate[] _certs; + private Integer _keySize; + private String _idStr; + + + CachedInfo(Integer keySize, X509Certificate[] certs,String id) + { + this._keySize = keySize; + this._certs = certs; + this._idStr = id; + } + + X509Certificate[] getCerts() + { + return _certs; + } + + Integer getKeySize() + { + return _keySize; + } + + String getIdStr () + { + return _idStr; + } + } + + + public class SslConnection extends Connection + { + public SslConnection(Socket socket) throws IOException + { + super(socket); + } + + public void run() + { + try + { + int handshakeTimeout = getHandshakeTimeout(); + int oldTimeout = _socket.getSoTimeout(); + if (handshakeTimeout > 0) + _socket.setSoTimeout(handshakeTimeout); + + ((SSLSocket)_socket).startHandshake(); + + if (handshakeTimeout>0) + _socket.setSoTimeout(oldTimeout); + + super.run(); + } + catch (SSLException e) + { + Log.debug(e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + catch (IOException e) + { + Log.debug(e); + try{close();} + catch(IOException e2){Log.ignore(e2);} + } + } + } + +} diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/favicon.ico b/jetty-server/src/main/resources/org/eclipse/jetty/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ea9e174b48b0130d4294813a6b5d487f35b436cc GIT binary patch literal 1150 zcmbtT*-leY6um|h48jdk3$&%Q(B4i^j0|O{SS`gNpioe>$}E$@lZJ1QNB+PF^2Rq1 z-~0si(Ki_^w7s|RV6t5MRE#FRd7IU<_sLm%SZ5ziJI7yDmBxRswtPX;YBWs?5!~WV zX`jnKzth8Su;vVy6F!(zey)BLp7>CB>W4KOr0qw3w28if>`f;Q+U+p8Z%!Bx?#XHx z){FsbAqZ=}v|o5k{<>)7Pyike?g%;$(&VSpzKYFT`wR zDrX^M;CRr3qk($tMJ~gfG?=p)=1f4>OZ>eMmFKZ z{#6_645KLgIrf(Ep}0){MOd9rM%JLm7=wuWGDy2FVWh5HVfe~LjL`pd&kbuajQk^N z@{D>BMfSbkW<%5whu%d#k%;ZURdyb!Ya7ZI-%;ttJJuxJLz0cWW_*%td~zN%2=2D-ky` z(BtG2iSC_JOXHW6$^Qk_6M1L8#NWE%;LLH} zWIj0`Qp53(2ibm?;-@;O`5*Ztr|<+(CpGA+Q$34j#g!J$e~9zt#$j(AUOLK=Y~>vE zu?9lyH@sWXP4aHWYq5K$p7{f?rUNjigPh|g$c%mOb6~yGu6q2P&lSgd%&yLxlcl$% z^3Jn=&e49e4f|0W4m)dTSK~8hVtc3o?+0Ai>UUzBcl=?*!x|+Q-zjIsoMPQ_f7DOj qU^eMe?`tnuMcawfS5dwZz6;KEaVb>dF}Ls}x8hTO;ZooK`hEgZQ79q+ literal 0 HcmV?d00001 diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContextTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContextTest.java new file mode 100644 index 00000000000..16b93ee966a --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContextTest.java @@ -0,0 +1,294 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.handler.HandlerWrapper; + +public class AsyncContextTest extends TestCase +{ + protected Server _server = new Server(); + protected SuspendHandler _handler = new SuspendHandler(); + protected LocalConnector _connector; + + protected void setUp() throws Exception + { + _connector = new LocalConnector(); + _server.setConnectors(new Connector[]{ _connector }); + _server.setHandler(_handler); + _server.start(); + } + + protected void tearDown() throws Exception + { + _server.stop(); + } + + public void testSuspendResume() throws Exception + { + String response; + + _handler.setRead(0); + _handler.setSuspendFor(1000); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(-1); + check("TIMEOUT",process(null)); + + _handler.setSuspendFor(10000); + + _handler.setResumeAfter(0); + _handler.setCompleteAfter(-1); + check("RESUMED",process(null)); + + _handler.setResumeAfter(100); + _handler.setCompleteAfter(-1); + check("RESUMED",process(null)); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(0); + check("COMPLETED",process(null)); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(200); + check("COMPLETED",process(null)); + + _handler.setRead(-1); + + _handler.setResumeAfter(0); + _handler.setCompleteAfter(-1); + check("RESUMED",process("wibble")); + + _handler.setResumeAfter(100); + _handler.setCompleteAfter(-1); + check("RESUMED",process("wibble")); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(0); + check("COMPLETED",process("wibble")); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(100); + check("COMPLETED",process("wibble")); + + + _handler.setRead(6); + + _handler.setResumeAfter(0); + _handler.setCompleteAfter(-1); + check("RESUMED",process("wibble")); + + _handler.setResumeAfter(100); + _handler.setCompleteAfter(-1); + check("RESUMED",process("wibble")); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(0); + check("COMPLETED",process("wibble")); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(100); + check("COMPLETED",process("wibble")); + } + + protected void check(String content,String response) + { + assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + assertTrue(response.contains(content)); + } + + public synchronized String process(String content) throws Exception + { + String request = "GET / HTTP/1.1\r\n" + "Host: localhost\r\n"; + + if (content==null) + request+="\r\n"; + else + request+="Content-Length: "+content.length()+"\r\n" + "\r\n" + content; + + _connector.reopen(); + String response = _connector.getResponses(request); + return response; + } + + private static class SuspendHandler extends HandlerWrapper + { + private int _read; + private long _suspendFor=-1; + private long _resumeAfter=-1; + private long _completeAfter=-1; + + public SuspendHandler() + {} + + + public int getRead() + { + return _read; + } + + public void setRead(int read) + { + _read = read; + } + + public long getSuspendFor() + { + return _suspendFor; + } + + public void setSuspendFor(long suspendFor) + { + _suspendFor = suspendFor; + } + + public long getResumeAfter() + { + return _resumeAfter; + } + + public void setResumeAfter(long resumeAfter) + { + _resumeAfter = resumeAfter; + } + + public long getCompleteAfter() + { + return _completeAfter; + } + + public void setCompleteAfter(long completeAfter) + { + _completeAfter = completeAfter; + } + + + + public void handle(String target, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException + { + final Request base_request = (request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + + if (DispatcherType.REQUEST.equals(request.getDispatcherType())) + { + if (_read>0) + { + byte[] buf=new byte[_read]; + request.getInputStream().read(buf); + } + else if (_read<0) + { + InputStream in = request.getInputStream(); + int b=in.read(); + while(b!=-1) + b=in.read(); + } + + if (_suspendFor>0) + request.setAsyncTimeout(_suspendFor); + request.addAsyncListener(__asyncListener); + final AsyncContext asyncContext = request.startAsync(); + + if (_completeAfter>0) + { + new Thread() { + public void run() + { + try + { + Thread.sleep(_completeAfter); + response.getOutputStream().print("COMPLETED"); + response.setStatus(200); + base_request.setHandled(true); + asyncContext.complete(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }.start(); + } + else if (_completeAfter==0) + { + response.getOutputStream().print("COMPLETED"); + response.setStatus(200); + base_request.setHandled(true); + asyncContext.complete(); + } + + if (_resumeAfter>0) + { + new Thread() { + public void run() + { + try + { + Thread.sleep(_resumeAfter); + asyncContext.dispatch(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }.start(); + } + else if (_resumeAfter==0) + { + asyncContext.dispatch(); + } + } + else if (request.getAttribute("TIMEOUT")!=null) + { + response.setStatus(200); + response.getOutputStream().print("TIMEOUT"); + base_request.setHandled(true); + } + else + { + response.setStatus(200); + response.getOutputStream().print("RESUMED"); + base_request.setHandled(true); + } + } + } + + + private static AsyncListener __asyncListener = + new AsyncListener() + { + public void onComplete(AsyncEvent event) throws IOException + { + } + + public void onTimeout(AsyncEvent event) throws IOException + { + event.getRequest().setAttribute("TIMEOUT",Boolean.TRUE); + event.getRequest().getAsyncContext().dispatch(); + } + + }; +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelServerTest.java new file mode 100644 index 00000000000..d05fdc0ffda --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelServerTest.java @@ -0,0 +1,26 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; +import org.eclipse.jetty.server.nio.BlockingChannelConnector; + +/** + * HttpServer Tester. + */ +public class BlockingChannelServerTest extends HttpServerTestBase +{ + public BlockingChannelServerTest() + { + super(new BlockingChannelConnector()); + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java new file mode 100644 index 00000000000..038718664da --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java @@ -0,0 +1,139 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.NIOBuffer; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +/** + * HttpServer Tester. + */ +public class BusySelectChannelServerTest extends HttpServerTestBase +{ + + public BusySelectChannelServerTest() + { + super(new SelectChannelConnector() + { + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.jetty.server.server.nio.SelectChannelConnector#newEndPoint(java.nio.channels.SocketChannel, org.eclipse.io.nio.SelectorManager.SelectSet, java.nio.channels.SelectionKey) + */ + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException + { + return new SelectChannelEndPoint(channel,selectSet,key) + { + int write; + int read; + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.io.nio.SelectChannelEndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int x=write++&0xff; + if (x<8) + return 0; + if (x<32) + return flush(header); + return super.flush(header,buffer,trailer); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.io.nio.SelectChannelEndPoint#flush(org.eclipse.io.Buffer) + */ + public int flush(Buffer buffer) throws IOException + { + int x=write++&0xff; + if (x<8) + return 0; + if (x<32) + { + View v = new View(buffer); + v.setPutIndex(v.getIndex()+1); + int l=super.flush(v); + if (l>0) + buffer.skip(l); + return l; + } + return super.flush(buffer); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.io.nio.ChannelEndPoint#fill(org.eclipse.io.Buffer) + */ + public int fill(Buffer buffer) throws IOException + { + int x=read++&0xff; + if (x<8) + return 0; + + if (x<16 & buffer.space()>=1) + { + NIOBuffer one = new IndirectNIOBuffer(1); + int l=super.fill(one); + if (l>0) + buffer.put(one.peek(0)); + return l; + } + + if (x<24 & buffer.space()>=2) + { + NIOBuffer two = new IndirectNIOBuffer(2); + int l=super.fill(two); + if (l>0) + buffer.put(two.peek(0)); + if (l>1) + buffer.put(two.peek(1)); + return l; + } + + if (x<64 & buffer.space()>=3) + { + NIOBuffer three = new IndirectNIOBuffer(3); + int l=super.fill(three); + if (l>0) + buffer.put(three.peek(0)); + if (l>1) + buffer.put(three.peek(1)); + if (l>2) + buffer.put(three.peek(2)); + return l; + } + + return super.fill(buffer); + } + }; + } + }); + } + + + protected void configServer(Server server) + { + server.setThreadPool(new QueuedThreadPool()); + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java new file mode 100644 index 00000000000..1590c7494d4 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java @@ -0,0 +1,177 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.handler.AbstractHandler; + +/** + * Test {@link AbstractConnector#checkForwardedHeaders(org.eclipse.io.EndPoint, Request)}. + */ +public class CheckReverseProxyHeadersTest extends TestCase +{ + Server server=new Server(); + LocalConnector connector=new LocalConnector(); + + /** + * Constructor for CheckReverseProxyHeadersTest. + * @param name test case name. + */ + public CheckReverseProxyHeadersTest(String name) + { + super(name); + } + + public void testCheckReverseProxyHeaders() throws Exception + { + // Classic ProxyPass from example.com:80 to localhost:8080 + testRequest("Host: localhost:8080\n" + + "X-Forwarded-For: 10.20.30.40\n" + + "X-Forwarded-Host: example.com", new RequestValidator() + { + public void validate(HttpServletRequest request) + { + assertEquals("example.com", request.getServerName()); + assertEquals(80, request.getServerPort()); + assertEquals("10.20.30.40", request.getRemoteAddr()); + assertEquals("10.20.30.40", request.getRemoteHost()); + assertEquals("example.com", request.getHeader("Host")); + } + }); + + // ProxyPass from example.com:81 to localhost:8080 + testRequest("Host: localhost:8080\n" + + "X-Forwarded-For: 10.20.30.40\n" + + "X-Forwarded-Host: example.com:81\n" + + "X-Forwarded-Server: example.com", new RequestValidator() + { + public void validate(HttpServletRequest request) + { + assertEquals("example.com", request.getServerName()); + assertEquals(81, request.getServerPort()); + assertEquals("10.20.30.40", request.getRemoteAddr()); + assertEquals("10.20.30.40", request.getRemoteHost()); + assertEquals("example.com:81", request.getHeader("Host")); + } + }); + + // Multiple ProxyPass from example.com:80 to rp.example.com:82 to localhost:8080 + testRequest("Host: localhost:8080\n" + + "X-Forwarded-For: 10.20.30.40, 10.0.0.1\n" + + "X-Forwarded-Host: example.com, rp.example.com:82\n" + + "X-Forwarded-Server: example.com, rp.example.com", new RequestValidator() + { + public void validate(HttpServletRequest request) + { + assertEquals("example.com", request.getServerName()); + assertEquals(80, request.getServerPort()); + assertEquals("10.20.30.40", request.getRemoteAddr()); + assertEquals("10.20.30.40", request.getRemoteHost()); + assertEquals("example.com", request.getHeader("Host")); + } + }); + } + + private void testRequest(String headers, RequestValidator requestValidator) throws Exception + { + Server server = new Server(); + LocalConnector connector = new LocalConnector(); + + // Activate reverse proxy headers checking + connector.setForwarded(true); + + server.setConnectors(new Connector[] {connector}); + ValidationHandler validationHandler = new ValidationHandler(requestValidator); + server.setHandler(validationHandler); + + try + { + server.start(); + connector.getResponses("GET / HTTP/1.1\n" + headers + "\n\n"); + + Error error = validationHandler.getError(); + + if (error != null) + { + throw error; + } + } + finally + { + server.stop(); + } + } + + /** + * Interface for validate a wrapped request. + */ + private static interface RequestValidator + { + /** + * Validate the current request. + * @param request the request. + */ + void validate(HttpServletRequest request); + } + + /** + * Handler for validation. + */ + private static class ValidationHandler extends AbstractHandler + { + private RequestValidator _requestValidator; + private Error _error; + + /** + * Create the validation handler with a request validator. + * @param requestValidator the request validator. + */ + public ValidationHandler(RequestValidator requestValidator) + { + _requestValidator = requestValidator; + } + + /** + * Retrieve the validation error. + * @return the validation error or null if there was no error. + */ + public Error getError() + { + return _error; + } + + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + _requestValidator.validate(request); + } + catch (Error e) + { + _error = e; + } + catch (Throwable e) + { + _error = new Error(e); + } + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java new file mode 100644 index 00000000000..758215872ec --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java @@ -0,0 +1,217 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Enumeration; + +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.ajax.Continuation; +import org.eclipse.jetty.util.ajax.ContinuationSupport; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Dump request handler. + * Dumps GET and POST requests. + * Useful for testing and debugging. + * + * @version $Id: DumpHandler.java,v 1.14 2005/08/13 00:01:26 gregwilkins Exp $ + * + */ +public class DumpHandler extends AbstractHandler +{ + String label="Dump HttpHandler"; + + public DumpHandler() + { + } + + public DumpHandler(String label) + { + this.label=label; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Request base_request = (request instanceof Request) ? (Request)request:HttpConnection.getCurrentConnection().getRequest(); + + if (!isStarted()) + return; + + if (request.getParameter("continue")!=null) + { + Continuation continuation = ContinuationSupport.getContinuation(request, null); + continuation.suspend(Long.parseLong(request.getParameter("continue"))); + } + + base_request.setHandled(true); + response.setHeader(HttpHeaders.CONTENT_TYPE,MimeTypes.TEXT_HTML); + + OutputStream out = response.getOutputStream(); + ByteArrayOutputStream buf = new ByteArrayOutputStream(2048); + Writer writer = new OutputStreamWriter(buf,StringUtil.__ISO_8859_1); + writer.write("

      "+label+"

      "); + writer.write("
      \npathInfo="+request.getPathInfo()+"\n
      \n"); + writer.write("
      \ncontentType="+request.getContentType()+"\n
      \n"); + writer.write("
      \nencoding="+request.getCharacterEncoding()+"\n
      \n"); + writer.write("

      Header:

      ");
      +        writer.write(request.getMethod()+" "+request.getRequestURI()+" "+request.getProtocol()+"\n");
      +        Enumeration headers = request.getHeaderNames();
      +        while(headers.hasMoreElements())
      +        {
      +            String name=(String)headers.nextElement();
      +            writer.write(name);
      +            writer.write(": ");
      +            writer.write(request.getHeader(name));
      +            writer.write("\n");
      +        }
      +        writer.write("
      \n

      Parameters:

      \n
      ");
      +        Enumeration names=request.getParameterNames();
      +        while(names.hasMoreElements())
      +        {
      +            String name=names.nextElement().toString();
      +            String[] values=request.getParameterValues(name);
      +            if (values==null || values.length==0)
      +            {
      +                writer.write(name);
      +                writer.write("=\n");
      +            }
      +            else if (values.length==1)
      +            {
      +                writer.write(name);
      +                writer.write("=");
      +                writer.write(values[0]);
      +                writer.write("\n");
      +            }
      +            else
      +            {
      +                for (int i=0; i0)
      +        {
      +            String cookie_action=request.getParameter("Button");
      +            try{
      +                Cookie cookie=
      +                    new Cookie(cookie_name.trim(),
      +                                    request.getParameter("CookieVal"));
      +                if ("Clear Cookie".equals(cookie_action))
      +                    cookie.setMaxAge(0);
      +                response.addCookie(cookie);
      +            }
      +            catch(IllegalArgumentException e)
      +            {
      +                writer.write("
      \n

      BAD Set-Cookie:

      \n
      ");
      +                writer.write(e.toString());
      +            }
      +        }
      +        
      +        writer.write("
      \n

      Cookies:

      \n
      ");
      +        Cookie[] cookies=request.getCookies();
      +        if (cookies!=null && cookies.length>0)
      +        {
      +            for(int c=0;c\n

      Attributes:

      \n
      ");
      +        Enumeration attributes=request.getAttributeNames();
      +        if (attributes!=null && attributes.hasMoreElements())
      +        {
      +            while(attributes.hasMoreElements())
      +            {
      +                String attr=attributes.nextElement().toString();
      +                writer.write(attr);
      +                writer.write("=");
      +                writer.write(request.getAttribute(attr).toString());
      +                writer.write("\n");
      +            }
      +        }
      +        
      +        writer.write("
      \n

      Content:

      \n
      ");
      +        char[] content= new char[4096];
      +        int len;
      +        try{
      +            request.setCharacterEncoding(StringUtil.__UTF8);
      +            Reader in=request.getReader();
      +            String charset=request.getCharacterEncoding();
      +            if (charset==null)
      +                charset=StringUtil.__ISO_8859_1;
      +            while((len=in.read(content))>=0)
      +                writer.write(new String(content,0,len));
      +        }
      +        catch(IOException e)
      +        {   
      +            Log.warn(e);
      +            writer.write(e.toString());
      +        }
      +        
      +        writer.write("
      "); + writer.write(""); + + // commit now + writer.flush(); + response.setContentLength(buf.size()+1000); + + try + { + buf.writeTo(out); + + buf.reset(); + writer.flush(); + for (int pad=998-buf.size();pad-->0;) + writer.write(" "); + writer.write("\015\012"); + writer.flush(); + buf.writeTo(out); + + response.setHeader("IgnoreMe","ignored"); + } + catch(Exception e) + { + Log.ignore(e); + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java new file mode 100644 index 00000000000..0d75b485f6d --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java @@ -0,0 +1,45 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.net.URLDecoder; +import java.net.URLEncoder; + +import junit.framework.TestCase; + +import org.eclipse.jetty.http.EncodedHttpURI; + +public class EncodedHttpURITest extends TestCase +{ + + public void testNonURIAscii () + throws Exception + { + String url = "http://www.foo.com/ma\u00F1ana"; + byte[] asISO = url.getBytes("ISO-8859-1"); + String str = new String(asISO, "ISO-8859-1"); + + //use a non UTF-8 charset as the encoding and url-escape as per + //http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars + String s = URLEncoder.encode(url, "ISO-8859-1"); + EncodedHttpURI uri = new EncodedHttpURI("ISO-8859-1"); + + //parse it, using the same encoding + uri.parse(s); + + //decode the url encoding + String d = URLDecoder.decode(uri.getCompletePath(), "ISO-8859-1"); + assertEquals(url, d); + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java new file mode 100644 index 00000000000..1036e19f7df --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java @@ -0,0 +1,354 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +/* + * Created on 9/01/2004 + * + * To change the template for this generated file go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ +package org.eclipse.jetty.server; + +import junit.framework.TestCase; + +/** + * + * + */ +public class HttpConnectionTest extends TestCase +{ + Server server = new Server(); + LocalConnector connector = new LocalConnector(); + + /** + * Constructor + * @param arg0 + */ + public HttpConnectionTest(String arg0) + { + super(arg0); + server.setConnectors(new Connector[]{connector}); + server.setHandler(new DumpHandler()); + } + + /* + * @see TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + connector.setHeaderBufferSize(1024); + server.start(); + } + + /* + * @see TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + server.stop(); + } + + + + /* --------------------------------------------------------------- */ + public void testFragmentedChunk() + { + + String response=null; + try + { + int offset=0; + + // Chunk last + offset=0; connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"/R1"); + offset = checkContains(response,offset,"12345"); + + + response=connector.getResponses("GET /R2 HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain\n"+ + "\015\012"+ + "5;\015\012",true); + response=connector.getResponses("ABCDE\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"/R2"); + offset = checkContains(response,offset,"ABCDE"); + + + } + catch(Exception e) + { + e.printStackTrace(); + assertTrue(false); + if (response!=null) + System.err.println(response); + } + } + + /* --------------------------------------------------------------- */ + public void testEmpty() throws Exception + { + String response=connector.getResponses("GET /R1 HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain\n"+ + "\015\012"+ + "0\015\012\015\012"); + + int offset=0; + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"/R1"); + } + + /* --------------------------------------------------------------- */ + public void testAutoFlush() throws Exception + { + + String response=null; + int offset=0; + + offset=0; connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 200"); + checkNotContained(response,offset,"IgnoreMe"); + offset = checkContains(response,offset,"/R1"); + offset = checkContains(response,offset,"12345"); + } + + /* --------------------------------------------------------------- */ + public void testCharset() + { + + String response=null; + try + { + int offset=0; + + offset=0; connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain; charset=utf-8\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"/R1"); + offset = checkContains(response,offset,"encoding=UTF-8"); + offset = checkContains(response,offset,"12345"); + + offset=0; connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain; charset = iso-8859-1 ; other=value\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"encoding=iso-8859-1"); + offset = checkContains(response,offset,"/R1"); + offset = checkContains(response,offset,"12345"); + + offset=0; connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain; charset=unknown\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"encoding=unknown"); + offset = checkContains(response,offset,"/R1"); + offset = checkContains(response,offset,"12345"); + + + } + catch(Exception e) + { + e.printStackTrace(); + assertTrue(false); + if (response!=null) + System.err.println(response); + } + } + + + public void testConnection () + { + String response=null; + try + { + int offset=0; + + offset=0; connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+ + "Host: localhost\n"+ + "Connection: TE, close"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain; charset=utf-8\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"Connection: TE"); + offset = checkContains(response,offset,"Connection: close"); + } + catch (Exception e) + { + e.printStackTrace(); + assertTrue(false); + if (response!=null) + System.err.println(response); + } + } + + public void testOversizedBuffer() + { + String response = null; + connector.reopen(); + try + { + int offset = 0; + String cookie = "thisisastringthatshouldreachover1kbytes"; + for (int i=0;i<100;i++) + cookie+="xxxxxxxxxxxx"; + response = connector.getResponses("GET / HTTP/1.1\n"+ + "Host: localhost\n" + + "Cookie: "+cookie+"\n"+ + "\015\012" + ); + offset = checkContains(response, offset, "HTTP/1.1 413"); + } + catch(Exception e) + { + e.printStackTrace(); + assertTrue(false); + if(response != null) + System.err.println(response); + + } + } + + public void testAsterisk() + { + String response = null; + + try + { + int offset=0; + + offset=0; connector.reopen(); + response=connector.getResponses("OPTIONS * HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain; charset=utf-8\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"*"); + + // to prevent the DumpHandler from picking this up and returning 200 OK + server.setHandler(null); + offset=0; connector.reopen(); + response=connector.getResponses("GET * HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain; charset=utf-8\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 404 Not Found"); + + offset=0; connector.reopen(); + response=connector.getResponses("GET ** HTTP/1.1\n"+ + "Host: localhost\n"+ + "Transfer-Encoding: chunked\n"+ + "Content-Type: text/plain; charset=utf-8\n"+ + "\015\012"+ + "5;\015\012"+ + "12345\015\012"+ + "0;\015\012\015\012"); + offset = checkContains(response,offset,"HTTP/1.1 400 Bad Request"); + } + catch (Exception e) + { + e.printStackTrace(); + assertTrue(false); + if (response!=null) + System.err.println(response); + } + + } + + private int checkContains(String s,int offset,String c) + { + int o=s.indexOf(c,offset); + if (o=offset) + { + System.err.println("FAILED"); + System.err.println("'"+c+"' IS in:"); + System.err.println(s.substring(offset)); + System.err.flush(); + System.out.println("--\n"+s); + System.out.flush(); + assertTrue(false); + } + } +} + + diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java new file mode 100644 index 00000000000..35947ff9624 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java @@ -0,0 +1,918 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.net.Socket; +import java.net.URL; +import java.util.Arrays; +import java.util.Random; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +/** + * HttpServer Tester. + */ +public class HttpServerTestBase extends TestCase +{ + private static boolean stress=Boolean.getBoolean("STRESS"); + + // ~ Static fields/initializers + // --------------------------------------------- + + /** The request. */ + private static final String REQUEST1_HEADER="POST / HTTP/1.0\n"+"Host: localhost\n"+"Content-Type: text/xml; charset=utf-8\n"+"Connection: close\n"+"Content-Length: "; + private static final String REQUEST1_CONTENT="\n" + +"\n" + +""; + private static final String REQUEST1=REQUEST1_HEADER+REQUEST1_CONTENT.getBytes().length+"\n\n"+REQUEST1_CONTENT; + + /** The expected response. */ + private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Connection: close\n"+"Server: Jetty(7.0.x)\n"+"\n"+"Hello world\n"; + + // Break the request up into three pieces, splitting the header. + private static final String FRAGMENT1=REQUEST1.substring(0,16); + private static final String FRAGMENT2=REQUEST1.substring(16,34); + private static final String FRAGMENT3=REQUEST1.substring(34); + + /** Second test request. */ + private static final String REQUEST2_HEADER= + "POST / HTTP/1.0\n"+ + "Host: localhost\n"+ + "Content-Type: text/xml\n"+ + "Content-Length: "; + + private static final String REQUEST2_CONTENT= + "\n"+ + "\n"+ + " \n"+ + " \n"+ + " 73\n"+ + " \n"+ + " \n"+ + ""; + + private static final String REQUEST2=REQUEST2_HEADER+REQUEST2_CONTENT.getBytes().length+"\n\n"+REQUEST2_CONTENT; + + private static final String RESPONSE2_CONTENT= + "\n"+ + "\n"+ + " \n"+ + " \n"+ + " 73\n"+ + " \n"+ + " \n" + +"\n"; + private static final String RESPONSE2= + "HTTP/1.1 200 OK\n"+ + "Content-Type: text/xml;charset=ISO-8859-1\n"+ + "Content-Length: "+RESPONSE2_CONTENT.getBytes().length+"\n"+ + "Server: Jetty(7.0.x)\n"+ + "\n"+ + RESPONSE2_CONTENT; + + // Useful constants + private static final long PAUSE=10L; + private static final int LOOPS=stress?250:25; + private static final String HOST="localhost"; + + private Connector _connector; + private int port=0; + + protected void tearDown() throws Exception + { + super.tearDown(); + Thread.sleep(100); + } + + // ~ Methods + // ---------------------------------------------------------------- + + /** + * Feed the server the entire request at once. + * + * @throws Exception + * @throws InterruptedException + */ + public void testRequest1_jetty() throws Exception, InterruptedException + { + Server server=startServer(new HelloWorldHandler()); + Socket client=new Socket(HOST,port); + OutputStream os=client.getOutputStream(); + + os.write(REQUEST1.getBytes()); + os.flush(); + + // Read the response. + String response=readResponse(client); + + // Shut down + client.close(); + server.stop(); + + // Check the response + assertEquals("response",RESPONSE1,response); + } + + /* --------------------------------------------------------------- */ + public void testFragmentedChunk() throws Exception + { + + Server server=startServer(new EchoHandler()); + Socket client=new Socket(HOST,port); + OutputStream os=client.getOutputStream(); + + os.write(("GET /R2 HTTP/1.1\015\012"+"Host: localhost\015\012"+"Transfer-Encoding: chunked\015\012"+"Content-Type: text/plain\015\012" + +"Connection: close\015\012"+"\015\012").getBytes()); + os.flush(); + Thread.sleep(PAUSE); + os.write(("5\015\012").getBytes()); + os.flush(); + Thread.sleep(PAUSE); + os.write(("ABCDE\015\012"+"0;\015\012\015\012").getBytes()); + os.flush(); + + // Read the response. + String response=readResponse(client); + + // Shut down + client.close(); + server.stop(); + + assertTrue(true); // nothing checked yet. + + } + + /** + * Feed the server fragmentary headers and see how it copes with it. + * + * @throws Exception + * @throws InterruptedException + */ + public void testRequest1Fragments_jetty() throws Exception, InterruptedException + { + Server server=startServer(new HelloWorldHandler()); + String response; + + try + { + Socket client=new Socket(HOST,port); + OutputStream os=client.getOutputStream(); + + // Write a fragment, flush, sleep, write the next fragment, etc. + os.write(FRAGMENT1.getBytes()); + os.flush(); + Thread.sleep(PAUSE); + os.write(FRAGMENT2.getBytes()); + os.flush(); + Thread.sleep(PAUSE); + os.write(FRAGMENT3.getBytes()); + os.flush(); + + // Read the response + response=readResponse(client); + + // Shut down + client.close(); + } + finally + { + server.stop(); + } + + // Check the response + assertEquals("response",RESPONSE1,response); + } + + public void testRequest2_jetty() throws Exception + { + byte[] bytes=REQUEST2.getBytes(); + Server server=startServer(new EchoHandler()); + + try + { + for (int i=0; i=0) + { + Thread.sleep(500); + len=is.read(buf); + if (len>0) + total+=len; + } + + assertTrue(total>(1024*256)); + assertTrue(30000L>(System.currentTimeMillis()-start)); + } + finally + { + // Shut down + server.stop(); + Thread.yield(); + } + + } + + + /** + * After several iterations, I generated some known bad fragment points. + * + * @throws Exception + */ + public void testPipeline() throws Exception + { + Server server=startServer(new HelloWorldHandler()); + + try + { + //for (int pipeline=1;pipeline<32;pipeline++) + for (int pipeline=1;pipeline<32;pipeline++) + { + Socket client=new Socket(HOST,port); + client.setSoTimeout(5000); + OutputStream os=client.getOutputStream(); + + String request=""; + + for (int i=1;i=0); + assertTrue(in.indexOf("abcdefghZ")>=0); + assertTrue(in.indexOf("Wibble")<0); + + in = new String(b,i,b.length-i,"utf-16"); + assertEquals("Wibble\n",in); + + } + finally + { + // Shut down + server.stop(); + Thread.yield(); + } + } + + /** + */ + public void testRecycledReaders() throws Exception + { + Server server=startServer(new EchoHandler()); + + try + { + long start=System.currentTimeMillis(); + Socket client=new Socket(HOST,port); + OutputStream os=client.getOutputStream(); + InputStream is=client.getInputStream(); + + os.write(( + "POST /echo?charset=utf-8 HTTP/1.1\r\n"+ + "host: "+HOST+":"+port+"\r\n"+ + "content-type: text/plain; charset=utf-8\r\n"+ + "content-length: 10\r\n"+ + "\r\n").getBytes("iso-8859-1")); + + os.write(( + "123456789\n" + ).getBytes("utf-8")); + + os.write(( + "POST /echo?charset=utf-8 HTTP/1.1\r\n"+ + "host: "+HOST+":"+port+"\r\n"+ + "content-type: text/plain; charset=utf-8\r\n"+ + "content-length: 10\r\n"+ + "\r\n" + ).getBytes("iso-8859-1")); + + os.write(( + "abcdefghi\n" + ).getBytes("utf-8")); + + String content="Wibble"; + byte[] contentB=content.getBytes("utf-16"); + os.write(( + "POST /echo?charset=utf-8 HTTP/1.1\r\n"+ + "host: "+HOST+":"+port+"\r\n"+ + "content-type: text/plain; charset=utf-16\r\n"+ + "content-length: "+contentB.length+"\r\n"+ + "connection: close\r\n"+ + "\r\n" + ).getBytes("iso-8859-1")); + os.write(contentB); + + os.flush(); + + String in = IO.toString(is); + assertTrue(in.indexOf("123456789")>=0); + assertTrue(in.indexOf("abcdefghi")>=0); + assertTrue(in.indexOf("Wibble")>=0); + + } + finally + { + // Shut down + server.stop(); + Thread.yield(); + } + } + + /** + * Read entire response from the client. Close the output. + * + * @param client + * Open client socket. + * + * @return The response string. + * + * @throws IOException + */ + private static String readResponse(Socket client) throws IOException + { + BufferedReader br=null; + + try + { + br=new BufferedReader(new InputStreamReader(client.getInputStream())); + + StringBuilder sb=new StringBuilder(); + String line; + + while ((line=br.readLine())!=null) + { + sb.append(line); + sb.append('\n'); + } + + return sb.toString(); + } + finally + { + if (br!=null) + { + br.close(); + } + } + } + + protected HttpServerTestBase(Connector connector) + { + _connector=connector; + } + + /** + * Create the server. + * + * @param handler + * + * @return Newly created server, ready to start. + * + * @throws Exception + */ + protected Server startServer(Handler handler) throws Exception + { + Server server=new Server(); + + _connector.setPort(0); + server.setConnectors(new Connector[] + { _connector }); + server.setHandler(handler); + + configServer(server); + + server.start(); + port=_connector.getLocalPort(); + return server; + } + + protected void configServer(Server server) + { + + } + + private void writeFragments(byte[] bytes, int[] points, StringBuilder message, OutputStream os) throws IOException, InterruptedException + { + int last=0; + + // Write out the fragments + for (int j=0; j=0) + throw new IllegalStateException("Not closed"); + } + } + + // ---------------------------------------------------------- + private static class HelloWorldHandler extends AbstractHandler + { + // ------------------------------------------------------------ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + base_request.setHandled(true); + response.setStatus(200); + response.getOutputStream().print("Hello world\r\n"); + } + } + + // ---------------------------------------------------------- + private static class DataHandler extends AbstractHandler + { + // ------------------------------------------------------------ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + base_request.setHandled(true); + response.setStatus(200); + + InputStream in = request.getInputStream(); + String input=IO.toString(in); + + String tmp = request.getParameter("writes"); + int writes=Integer.parseInt(tmp==null?"10":tmp); + tmp = request.getParameter("block"); + int block=Integer.parseInt(tmp==null?"10":tmp); + String encoding=request.getParameter("encoding"); + String chars=request.getParameter("chars"); + + String chunk = (input+"\u0a870123456789A\u0a87CDEFGHIJKLMNOPQRSTUVWXYZ\u0250bcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") + .substring(0,block); + response.setContentType("text/plain"); + if (encoding==null) + { + byte[] bytes=chunk.getBytes("ISO-8859-1"); + OutputStream out=response.getOutputStream(); + for (int i=0;i","GET body"); + + connector.reopen(); + String head=connector.getResponses("HEAD /R1 HTTP/1.0\n"+"Host: localhost\n"+"\n"); + checkContains(head,0,"HTTP/1.1 200","HEAD"); + checkContains(head,0,"Content-Type: text/html","HEAD _content"); + assertEquals("HEAD no body",-1,head.indexOf("")); + } + catch (Exception e) + { + e.printStackTrace(); + assertTrue(false); + } + } + + /* --------------------------------------------------------------- */ + public void test9_8() + { + // TODO + /* + * try { TestListener listener = new TestListener(); String response; + * int offset=0; connector.reopen(); + * // Default Host offset=0; connector.reopen(); + * response=connector.getResponses("TRACE /path HTTP/1.1\n"+ "Host: + * localhost\n"+ "Connection: close\n"+ "\n"); + * offset=checkContains(response,offset, "HTTP/1.1 200","200")+1; + * offset=checkContains(response,offset, "Content-Type: message/http", + * "message/http")+1; offset=checkContains(response,offset, "TRACE /path + * HTTP/1.1\r\n"+ "Host: localhost\r\n", "Request"); } catch(Exception + * e) { e.printStackTrace(); assertTrue(false); } + */ + } + + /* --------------------------------------------------------------- */ + public void test10_2_7() + { + // TODO + /* + * + * try { TestListener listener = new TestListener(); String response; + * int offset=0; connector.reopen(); + * // check to see if corresponging GET w/o range would return // a) + * ETag // b) Content-Location // these same headers will be required + * for corresponding // sub range requests + * + * response=connector.getResponses("GET /" + + * TestRFC2616.testFiles[0].name + " HTTP/1.1\n"+ "Host: localhost\n"+ + * "Connection: close\n"+ "\n"); + * + * boolean noRangeHasContentLocation = + * (response.indexOf("\r\nContent-Location: ") != -1); + * + * // now try again for the same resource but this time WITH range + * header + * + * response=connector.getResponses("GET /" + + * TestRFC2616.testFiles[0].name + " HTTP/1.1\n"+ "Host: localhost\n"+ + * "Connection: close\n"+ "Range: bytes=1-3\n"+ "\n"); + * + * offset=0; connector.reopen(); offset=checkContains(response,offset, + * "HTTP/1.1 206 Partial Content\r\n", "1. proper 206 status code"); + * offset=checkContains(response,offset, "Content-Type: text/plain", "2. + * _content type") + 2; offset=checkContains(response,offset, + * "Last-Modified: " + TestRFC2616.testFiles[0].modDate + "\r\n", "3. + * correct resource mod date"); + * // if GET w/o range had Content-Location, then the corresponding // + * response for the a GET w/ range must also have that same header + * + * offset=checkContains(response,offset, "Content-Range: bytes 1-3/26", + * "4. _content range") + 2; + * + * if (noRangeHasContentLocation) { + * offset=checkContains(response,offset, "Content-Location: ", "5. + * Content-Location header as with 200"); } else { // no need to check + * for Conten-Location header in 206 response // spec does not require + * existence or absence if these want any // header for the get w/o + * range } + * + * String expectedData = TestRFC2616.testFiles[0].data.substring(1, + * 3+1); offset=checkContains(response,offset, expectedData, "6. + * subrange data: \"" + expectedData + "\""); } catch(Exception e) { + * e.printStackTrace(); assertTrue(false); } + * + */ + } + + /* --------------------------------------------------------------- */ + public void test10_3() + { + // TODO + /* + * try { TestListener listener = new TestListener(); String response; + * int offset=0; connector.reopen(); + * // HTTP/1.0 offset=0; connector.reopen(); + * response=connector.getResponses("GET /redirect HTTP/1.0\n"+ + * "Connection: Keep-Alive\n"+ "\n" ); + * offset=checkContains(response,offset, "HTTP/1.1 302","302")+1; + * checkContains(response,offset, "Location: /dump", "redirected"); + * checkContains(response,offset, "Connection: close", "Connection: + * close"); + * + * // HTTP/1.1 offset=0; connector.reopen(); + * response=connector.getResponses("GET /redirect HTTP/1.1\n"+ "Host: + * localhost\n"+ "\n"+ "GET /redirect HTTP/1.1\n"+ "Host: localhost\n"+ + * "Connection: close\n"+ "\n" ); offset=checkContains(response,offset, + * "HTTP/1.1 302","302")+1; checkContains(response,offset, "Location: + * /dump", "redirected"); checkContains(response,offset, + * "Transfer-Encoding: chunked", "Transfer-Encoding: chunked"); + * + * offset=checkContains(response,offset, "HTTP/1.1 302","302")+1; + * checkContains(response,offset, "Location: /dump", "redirected"); + * checkContains(response,offset, "Connection: close", "closed"); + * // HTTP/1.0 _content offset=0; connector.reopen(); + * response=connector.getResponses("GET /redirect/_content HTTP/1.0\n"+ + * "Connection: Keep-Alive\n"+ "\n"+ "GET /redirect/_content + * HTTP/1.0\n"+ "\n" ); offset=checkContains(response,offset, "HTTP/1.1 + * 302","302")+1; checkContains(response,offset, "Location: /dump", + * "redirected"); checkContains(response,offset, "Connection: close", + * "close no _content length"); + * // HTTP/1.1 _content offset=0; connector.reopen(); + * response=connector.getResponses("GET /redirect/_content HTTP/1.1\n"+ + * "Host: localhost\n"+ "\n"+ "GET /redirect/_content HTTP/1.1\n"+ + * "Host: localhost\n"+ "Connection: close\n"+ "\n" ); + * offset=checkContains(response,offset, "HTTP/1.1 302","302")+1; + * checkContains(response,offset, "Location: /dump", "redirected"); + * checkContains(response,offset, "Transfer-Encoding: chunked", "chunked + * _content length"); + * + * offset=checkContains(response,offset, "HTTP/1.1 302","302")+1; + * checkContains(response,offset, "Location: /dump", "redirected"); + * checkContains(response,offset, "Connection: close", "closed"); + * } catch(Exception e) { e.printStackTrace(); assertTrue(false); } + * + */ + } + + /* --------------------------------------------------------------- */ + public void checkContentRange(LocalConnector listener, String tname, String path, String reqRanges, int expectedStatus, String expectedRange, + String expectedData) + { + try + { + String response; + int offset=0; + connector.reopen(); + + String byteRangeHeader=""; + if (reqRanges!=null) + { + byteRangeHeader="Range: "+reqRanges+"\n"; + } + + response=connector.getResponses("GET /"+path+" HTTP/1.1\n"+"Host: localhost\n"+byteRangeHeader+"Connection: close\n"+"\n"); + + switch (expectedStatus) + { + case 200: + { + offset=checkContains(response,offset,"HTTP/1.1 200 OK\r\n",tname+".1. proper 200 OK status code"); + break; + } + case 206: + { + offset=checkContains(response,offset,"HTTP/1.1 206 Partial Content\r\n",tname+".1. proper 206 Partial Content status code"); + break; + } + case 416: + { + offset=checkContains(response,offset,"HTTP/1.1 416 Requested Range Not Satisfiable\r\n",tname + +".1. proper 416 Requested Range not Satisfiable status code"); + break; + } + } + + if (expectedRange!=null) + { + String expectedContentRange="Content-Range: bytes "+expectedRange+"\r\n"; + offset=checkContains(response,offset,expectedContentRange,tname+".2. _content range "+expectedRange); + } + + if (expectedStatus==200||expectedStatus==206) + { + offset=checkContains(response,offset,expectedData,tname+".3. subrange data: \""+expectedData+"\""); + } + } + catch (Exception e) + { + e.printStackTrace(); + assertTrue(false); + } + } + + public void test14_16() + { + // TODO + /* + * try { TestListener listener = new TestListener(); + * + * int id = 0; + * + * // // calibrate with normal request (no ranges); if this doesnt // + * work, dont expect ranges to work either // + * connector.checkContentRange( t, Integer.toString(id++), + * TestRFC2616.testFiles[0].name, null, 200, null, + * TestRFC2616.testFiles[0].data ); + * + * // // server should ignore all range headers which include // at + * least one syntactically invalid range // String [] totallyBadRanges = { + * "bytes=a-b", "bytes=-1-2", "bytes=-1-2,2-3", "bytes=-", "bytes=-1-", + * "bytes=a-b,-1-1-1", "doublehalfwords=1-2", }; + * + * for (int i = 0; i < totallyBadRanges.length; i++) { + * connector.checkContentRange( t, "BadRange"+i, + * TestRFC2616.testFiles[0].name, totallyBadRanges[i], 416, null, + * TestRFC2616.testFiles[0].data ); } + * + * // // should test for combinations of good and syntactically // + * invalid ranges here, but I am not certain what the right // behavior + * is abymore // // a) Range: bytes=a-b,5-8 // // b) Range: + * bytes=a-b,bytes=5-8 // // c) Range: bytes=a-b // Range: bytes=5-8 // + * + * // // return data for valid ranges while ignoring unsatisfiable // + * ranges // + * + * connector.checkContentRange( t, "bytes=5-8", + * TestRFC2616.testFiles[0].name, "bytes=5-8", 206, "5-8/26", + * TestRFC2616.testFiles[0].data.substring(5,8+1) ); + * // // server should not return a 416 if at least syntactically valid + * ranges // are is satisfiable // connector.checkContentRange( t, + * "bytes=5-8,50-60", TestRFC2616.testFiles[0].name, "bytes=5-8,50-60", + * 206, "5-8/26", TestRFC2616.testFiles[0].data.substring(5,8+1) ); + * connector.checkContentRange( t, "bytes=50-60,5-8", + * TestRFC2616.testFiles[0].name, "bytes=50-60,5-8", 206, "5-8/26", + * TestRFC2616.testFiles[0].data.substring(5,8+1) ); + * // 416 as none are satisfiable connector.checkContentRange( t, + * "bytes=50-60", TestRFC2616.testFiles[0].name, "bytes=50-60", 416, + * "*REMOVE_THIS/26", null ); + * + * } + * + * catch(Exception e) { e.printStackTrace(); assertTrue(false); } + */ + } + + /* --------------------------------------------------------------- */ + public void test14_23() + { + try + { + + String response; + int offset=0; + connector.reopen(); + + // HTTP/1.0 OK with no host + offset=0; + connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.0\n"+"\n"); + offset=checkContains(response,offset,"HTTP/1.1 200","200")+1; + + offset=0; + connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+"\n"); + offset=checkContains(response,offset,"HTTP/1.1 400","400")+1; + + offset=0; + connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"\n"); + offset=checkContains(response,offset,"HTTP/1.1 200","200")+1; + + offset=0; + connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host:\n"+"\n"); + offset=checkContains(response,offset,"HTTP/1.1 200","200")+1; + + } + catch (Exception e) + { + e.printStackTrace(); + assertTrue(false); + } + } + + /* --------------------------------------------------------------- */ + public void test14_35() + { + // TODO + /* + * try { TestListener listener = new TestListener(); + * // // test various valid range specs that have not been // tested + * yet // + * + * connector.checkContentRange( t, "bytes=0-2", + * TestRFC2616.testFiles[0].name, "bytes=0-2", 206, "0-2/26", + * TestRFC2616.testFiles[0].data.substring(0,2+1) ); + * + * connector.checkContentRange( t, "bytes=23-", + * TestRFC2616.testFiles[0].name, "bytes=23-", 206, "23-25/26", + * TestRFC2616.testFiles[0].data.substring(23,25+1) ); + * + * connector.checkContentRange( t, "bytes=23-42", + * TestRFC2616.testFiles[0].name, "bytes=23-42", 206, "23-25/26", + * TestRFC2616.testFiles[0].data.substring(23,25+1) ); + * + * connector.checkContentRange( t, "bytes=-3", + * TestRFC2616.testFiles[0].name, "bytes=-3", 206, "23-25/26", + * TestRFC2616.testFiles[0].data.substring(23,25+1) ); + * + * connector.checkContentRange( t, "bytes=23-23,-2", + * TestRFC2616.testFiles[0].name, "bytes=23-23,-2", 206, "23-23/26", + * TestRFC2616.testFiles[0].data.substring(23,23+1) ); + * + * connector.checkContentRange( t, "bytes=-1,-2,-3", + * TestRFC2616.testFiles[0].name, "bytes=-1,-2,-3", 206, "25-25/26", + * TestRFC2616.testFiles[0].data.substring(25,25+1) ); + * } + * + * catch(Exception e) { e.printStackTrace(); assertTrue(false); } + */} + + /* --------------------------------------------------------------- */ + public void test14_39() + { + // TODO + /* + * try { TestListener listener = new TestListener(); String response; + * int offset=0; connector.reopen(); + * // Gzip accepted offset=0; connector.reopen(); + * response=connector.getResponses("GET /R1?gzip HTTP/1.1\n"+ "Host: + * localhost\n"+ "TE: gzip;q=0.5\n"+ "Connection: close\n"+ "\n"); + * offset=checkContains(response,offset, "HTTP/1.1 200","TE: coding")+1; + * offset=checkContains(response,offset, "Transfer-Encoding: gzip","TE: + * coding")+1; + * // Gzip not accepted offset=0; connector.reopen(); + * response=connector.getResponses("GET /R1?gzip HTTP/1.1\n"+ "Host: + * localhost\n"+ "TE: deflate\n"+ "Connection: close\n"+ "\n"); + * offset=checkContains(response,offset, "HTTP/1.1 501","TE: coding not + * accepted")+1; + * } catch(Exception e) { e.printStackTrace(); assertTrue(false); } + */ + } + + /* --------------------------------------------------------------- */ + public void test19_6() + { + try + { + + String response; + int offset=0; + connector.reopen(); + + offset=0; + connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.0\n"+"\n"); + offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 default close")+10; + checkNotContained(response,offset,"Connection: close","19.6.2 not assumed"); + + offset=0; + connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"\n"+ + + "GET /R2 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+ + + "GET /R3 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"); + + offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1; + offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1; + + offset=checkContains(response,offset,"","19.6.2 Keep-alive 1")+1; + + offset=checkContains(response,offset,"/R1","19.6.2 Keep-alive 1")+1; + + offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 2")+11; + offset=checkContains(response,offset,"Connection: close","19.6.2 Keep-alive close")+1; + offset=checkContains(response,offset,"/R2","19.6.2 Keep-alive close")+3; + + assertEquals("19.6.2 closed",-1,response.indexOf("/R3")); + + offset=0; + connector.reopen(); + response=connector.getResponses("GET /R1 HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"Content-Length: 10\n"+"\n"+"1234567890\n"+ + + "GET /RA HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"Content-Length: 10\n"+"\n"+"ABCDEFGHIJ\n"+ + + "GET /R2 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+ + + "GET /R3 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"); + offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1; + offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1; + offset=checkContains(response,offset,"","19.6.2 Keep-alive 1")+1; + offset=checkContains(response,offset,"1234567890","19.6.2 Keep-alive 1")+1; + + offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1; + offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1; + offset=checkContains(response,offset,"","19.6.2 Keep-alive 1")+1; + offset=checkContains(response,offset,"ABCDEFGHIJ","19.6.2 Keep-alive 1")+1; + + offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 2")+11; + offset=checkContains(response,offset,"Connection: close","19.6.2 Keep-alive close")+1; + offset=checkContains(response,offset,"/R2","19.6.2 Keep-alive close")+3; + + assertEquals("19.6.2 closed",-1,response.indexOf("/R3")); + + } + catch (Exception e) + { + e.printStackTrace(); + assertTrue(false); + } + } + + private int checkContains(String s, int offset, String c, String test) + { + int o=s.indexOf(c,offset); + if (o0) + assertEquals(l,_handler._content.length()); + content+="x"; + } + } + + public void testConnectionClose() + throws Exception + { + String response; + + _handler._checker = new RequestTester() + { + public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException + { + response.getOutputStream().println("Hello World"); + return true; + } + }; + + _connector.reopen(); + response=_connector.getResponses( + "GET / HTTP/1.1\n"+ + "Host: whatever\n"+ + "\n" + ); + assertTrue(response.indexOf("200")>0); + assertFalse(response.indexOf("Connection: close")>0); + assertTrue(response.indexOf("Hello World")>0); + + _connector.reopen(); + response=_connector.getResponses( + "GET / HTTP/1.1\n"+ + "Host: whatever\n"+ + "Connection: close\n"+ + "\n" + ); + assertTrue(response.indexOf("200")>0); + assertTrue(response.indexOf("Connection: close")>0); + assertTrue(response.indexOf("Hello World")>0); + + _connector.reopen(); + response=_connector.getResponses( + "GET / HTTP/1.1\n"+ + "Host: whatever\n"+ + "Connection: Other, close\n"+ + "\n" + ); + + assertTrue(response.indexOf("200")>0); + assertTrue(response.indexOf("Connection: close")>0); + assertTrue(response.indexOf("Hello World")>0); + + + + _connector.reopen(); + response=_connector.getResponses( + "GET / HTTP/1.0\n"+ + "Host: whatever\n"+ + "\n" + ); + assertTrue(response.indexOf("200")>0); + assertFalse(response.indexOf("Connection: close")>0); + assertTrue(response.indexOf("Hello World")>0); + + _connector.reopen(); + response=_connector.getResponses( + "GET / HTTP/1.0\n"+ + "Host: whatever\n"+ + "Connection: Other, close\n"+ + "\n" + ); + assertTrue(response.indexOf("200")>0); + assertTrue(response.indexOf("Connection: close")>0); + assertTrue(response.indexOf("Hello World")>0); + + _connector.reopen(); + response=_connector.getResponses( + "GET / HTTP/1.0\n"+ + "Host: whatever\n"+ + "Connection: Other, keep-alive\n"+ + "\n" + ); + assertTrue(response.indexOf("200")>0); + assertTrue(response.indexOf("Connection: keep-alive")>0); + assertTrue(response.indexOf("Hello World")>0); + + + + + _handler._checker = new RequestTester() + { + public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException + { + response.setHeader("Connection","TE"); + response.addHeader("Connection","Other"); + response.getOutputStream().println("Hello World"); + return true; + } + }; + + _connector.reopen(); + response=_connector.getResponses( + "GET / HTTP/1.1\n"+ + "Host: whatever\n"+ + "\n" + ); + assertTrue(response.indexOf("200")>0); + assertTrue(response.indexOf("Connection: TE,Other")>0); + assertTrue(response.indexOf("Hello World")>0); + + _connector.reopen(); + response=_connector.getResponses( + "GET / HTTP/1.1\n"+ + "Host: whatever\n"+ + "Connection: close\n"+ + "\n" + ); + assertTrue(response.indexOf("200")>0); + assertTrue(response.indexOf("Connection: close")>0); + assertTrue(response.indexOf("Hello World")>0); + + + + + } + + + public void testCookies() throws Exception + { + final ArrayList cookies = new ArrayList(); + + _handler._checker = new RequestTester() + { + public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException + { + javax.servlet.http.Cookie[] ca = request.getCookies(); + if (ca!=null) + cookies.addAll(Arrays.asList(ca)); + response.getOutputStream().println("Hello World"); + return true; + } + }; + + String response; + _connector.reopen(); + + cookies.clear(); + response=_connector.getResponses( + "GET / HTTP/1.1\n"+ + "Host: whatever\n"+ + "\n" + ); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + assertEquals(0,cookies.size()); + + + cookies.clear(); + response=_connector.getResponses( + "GET / HTTP/1.1\n"+ + "Host: whatever\n"+ + "Cookie: name=value\n" + + "\n" + ); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + assertEquals(1,cookies.size()); + assertEquals("name",((Cookie)cookies.get(0)).getName()); + assertEquals("value",((Cookie)cookies.get(0)).getValue()); + + cookies.clear(); + response=_connector.getResponses( + "GET / HTTP/1.1\n"+ + "Host: whatever\n"+ + "Cookie: name=value; other=\"quoted=;value\"\n" + + "\n" + ); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + assertEquals(2,cookies.size()); + assertEquals("name",((Cookie)cookies.get(0)).getName()); + assertEquals("value",((Cookie)cookies.get(0)).getValue()); + assertEquals("other",((Cookie)cookies.get(1)).getName()); + assertEquals("quoted=;value",((Cookie)cookies.get(1)).getValue()); + + + cookies.clear(); + response=_connector.getResponses( + "GET /other HTTP/1.1\n"+ + "Host: whatever\n"+ + "Other: header\n"+ + "Cookie: name=value; other=\"quoted=;value\"\n" + + "\n"+ + "GET /other HTTP/1.1\n"+ + "Host: whatever\n"+ + "Other: header\n"+ + "Cookie: name=value; other=\"quoted=;value\"\n" + + "\n" + ); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + assertEquals(4,cookies.size()); + assertEquals("name",((Cookie)cookies.get(0)).getName()); + assertEquals("value",((Cookie)cookies.get(0)).getValue()); + assertEquals("other",((Cookie)cookies.get(1)).getName()); + assertEquals("quoted=;value",((Cookie)cookies.get(1)).getValue()); + + assertTrue((Cookie)cookies.get(0)==(Cookie)cookies.get(2)); + assertTrue((Cookie)cookies.get(1)==(Cookie)cookies.get(3)); + + + cookies.clear(); + response=_connector.getResponses( + "GET /other HTTP/1.1\n"+ + "Host: whatever\n"+ + "Other: header\n"+ + "Cookie: name=value; other=\"quoted=;value\"\n" + + "\n"+ + "GET /other HTTP/1.1\n"+ + "Host: whatever\n"+ + "Other: header\n"+ + "Cookie: name=value; other=\"othervalue\"\n" + + "\n" + ); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + assertEquals(4,cookies.size()); + assertEquals("name",((Cookie)cookies.get(0)).getName()); + assertEquals("value",((Cookie)cookies.get(0)).getValue()); + assertEquals("other",((Cookie)cookies.get(1)).getName()); + assertEquals("quoted=;value",((Cookie)cookies.get(1)).getValue()); + + assertTrue((Cookie)cookies.get(0)!=(Cookie)cookies.get(2)); + assertTrue((Cookie)cookies.get(1)!=(Cookie)cookies.get(3)); + + + + } + + + interface RequestTester + { + boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException; + } + + class RequestHandler extends AbstractHandler + { + RequestTester _checker; + String _content; + + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + ((Request)request).setHandled(true); + + if (request.getContentLength()>0) + _content=IO.toString(request.getInputStream()); + + if (_checker!=null && _checker.check(request,response)) + response.setStatus(200); + else + response.sendError(500); + + + } + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java new file mode 100644 index 00000000000..e61355eb3f0 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java @@ -0,0 +1,155 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.File; +import java.io.FileOutputStream; + +import junit.framework.TestCase; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.ResourceCache.Content; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; + +public class ResourceCacheTest extends TestCase +{ + Resource directory; + File[] files=new File[10]; + String[] names=new String[files.length]; + ResourceCache cache = new ResourceCache(new MimeTypes()); + ResourceFactory factory; + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + for (int i=0;i0); + } + + public void testContentTypeCharacterEncoding() + throws Exception + { + HttpConnection connection = new HttpConnection(connector,connector._endp,connector._server); + + Request request = connection.getRequest(); + Response response = connection.getResponse(); + + + response.setContentType("foo/bar"); + response.setCharacterEncoding("utf-8"); + assertEquals("foo/bar;charset=utf-8",response.getContentType()); + response.getWriter(); + assertEquals("foo/bar;charset=utf-8",response.getContentType()); + response.setContentType("foo2/bar2"); + assertEquals("foo2/bar2;charset=utf-8",response.getContentType()); + response.setCharacterEncoding("ISO-8859-1"); + assertEquals("foo2/bar2;charset=utf-8",response.getContentType()); + + response.recycle(); + + response.setContentType("text/html"); + response.setCharacterEncoding("utf-8"); + assertEquals("text/html;charset=UTF-8",response.getContentType()); + response.getWriter(); + assertEquals("text/html;charset=UTF-8",response.getContentType()); + response.setContentType("text/xml"); + assertEquals("text/xml;charset=UTF-8",response.getContentType()); + response.setCharacterEncoding("ISO-8859-1"); + assertEquals("text/xml;charset=UTF-8",response.getContentType()); + + } + + public void testCharacterEncodingContentType() + throws Exception + { + Response response = new Response(new HttpConnection(connector,connector._endp,connector._server)); + + response.setCharacterEncoding("utf-8"); + response.setContentType("foo/bar"); + assertEquals("foo/bar;charset=utf-8",response.getContentType()); + response.getWriter(); + assertEquals("foo/bar;charset=utf-8",response.getContentType()); + response.setContentType("foo2/bar2"); + assertEquals("foo2/bar2;charset=utf-8",response.getContentType()); + response.setCharacterEncoding("ISO-8859-1"); + assertEquals("foo2/bar2;charset=utf-8",response.getContentType()); + + response.recycle(); + + response.setCharacterEncoding("utf-8"); + response.setContentType("text/html"); + assertEquals("text/html;charset=UTF-8",response.getContentType()); + response.getWriter(); + assertEquals("text/html;charset=UTF-8",response.getContentType()); + response.setContentType("text/xml"); + assertEquals("text/xml;charset=UTF-8",response.getContentType()); + response.setCharacterEncoding("iso-8859-1"); + assertEquals("text/xml;charset=UTF-8",response.getContentType()); + + } + + public void testContentTypeWithCharacterEncoding() + throws Exception + { + Response response = new Response(new HttpConnection(connector,connector._endp,connector._server)); + + response.setCharacterEncoding("utf16"); + response.setContentType("foo/bar; charset=utf-8"); + assertEquals("foo/bar; charset=utf-8",response.getContentType()); + response.getWriter(); + assertEquals("foo/bar; charset=utf-8",response.getContentType()); + response.setContentType("foo2/bar2"); + assertEquals("foo2/bar2;charset=utf-8",response.getContentType()); + response.setCharacterEncoding("ISO-8859-1"); + assertEquals("foo2/bar2;charset=utf-8",response.getContentType()); + + response.recycle(); + + response.setCharacterEncoding("utf16"); + response.setContentType("text/html; charset=utf-8"); + assertEquals("text/html;charset=UTF-8",response.getContentType()); + response.getWriter(); + assertEquals("text/html;charset=UTF-8",response.getContentType()); + response.setContentType("text/xml"); + assertEquals("text/xml;charset=UTF-8",response.getContentType()); + response.setCharacterEncoding("iso-8859-1"); + assertEquals("text/xml;charset=UTF-8",response.getContentType()); + + } + + public void testContentTypeWithOther() + throws Exception + { + Response response = new Response(new HttpConnection(connector,connector._endp,connector._server)); + + response.setContentType("foo/bar; other=xyz"); + assertEquals("foo/bar; other=xyz",response.getContentType()); + response.getWriter(); + assertEquals("foo/bar; other=xyz charset=ISO-8859-1",response.getContentType()); + response.setContentType("foo2/bar2"); + assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType()); + + response.recycle(); + + response.setCharacterEncoding("utf-8"); + response.setContentType("text/html; other=xyz"); + assertEquals("text/html; other=xyz charset=utf-8",response.getContentType()); + response.getWriter(); + assertEquals("text/html; other=xyz charset=utf-8",response.getContentType()); + response.setContentType("text/xml"); + assertEquals("text/xml;charset=UTF-8",response.getContentType()); + } + + + public void testContentTypeWithCharacterEncodingAndOther() + throws Exception + { + Response response = new Response(new HttpConnection(connector,connector._endp,connector._server)); + + response.setCharacterEncoding("utf16"); + response.setContentType("foo/bar; charset=utf-8 other=xyz"); + assertEquals("foo/bar; charset=utf-8 other=xyz",response.getContentType()); + response.getWriter(); + assertEquals("foo/bar; charset=utf-8 other=xyz",response.getContentType()); + + response.recycle(); + + response.setCharacterEncoding("utf16"); + response.setContentType("text/html; other=xyz charset=utf-8"); + assertEquals("text/html; other=xyz charset=utf-8",response.getContentType()); + response.getWriter(); + assertEquals("text/html; other=xyz charset=utf-8",response.getContentType()); + + response.recycle(); + + response.setCharacterEncoding("utf16"); + response.setContentType("foo/bar; other=pq charset=utf-8 other=xyz"); + assertEquals("foo/bar; other=pq charset=utf-8 other=xyz",response.getContentType()); + response.getWriter(); + assertEquals("foo/bar; other=pq charset=utf-8 other=xyz",response.getContentType()); + + } + + public void testStatusCodes() throws Exception + { + Response response=newResponse(); + + response.sendError(404); + assertEquals(404, response.getStatus()); + assertEquals(null, response.getReason()); + + response=newResponse(); + + response.sendError(500, "Database Error"); + assertEquals(500, response.getStatus()); + assertEquals("Database Error", response.getReason()); + assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeaders.CACHE_CONTROL)); + + response=newResponse(); + + response.setStatus(200); + assertEquals(200, response.getStatus()); + assertEquals(null, response.getReason()); + + response=newResponse(); + + response.sendError(406, "Super Nanny"); + assertEquals(406, response.getStatus()); + assertEquals("Super Nanny", response.getReason()); + assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeaders.CACHE_CONTROL)); + } + + public void testEncodeRedirect() + throws Exception + { + HttpConnection connection=new HttpConnection(connector,connector._endp,connector._server); + Response response = new Response(connection); + Request request = connection.getRequest(); + + assertEquals("http://host:port/path/info;param?query=0&more=1#target",response.encodeRedirectUrl("http://host:port/path/info;param?query=0&more=1#target")); + + request.setRequestedSessionId("12345"); + request.setRequestedSessionIdFromCookie(false); + AbstractSessionManager manager=new HashSessionManager(); + manager.setIdManager(new HashSessionIdManager()); + request.setSessionManager(manager); + request.setSession(new TestSession(manager,"12345")); + + assertEquals("http://host:port/path/info;param;jsessionid=12345?query=0&more=1#target",response.encodeRedirectUrl("http://host:port/path/info;param?query=0&more=1#target")); + + } + + public void testSetBufferSize () + throws Exception + { + Response response = new Response(new HttpConnection(connector,connector._endp,connector._server)); + response.setBufferSize(20*1024); + response.getWriter().print("hello"); + try + { + response.setBufferSize(21*1024); + fail("Expected IllegalStateException on Request.setBufferSize"); + } + catch (Exception e) + { + assertTrue(e instanceof IllegalStateException); + } + } + + public void testHead() throws Exception + { + Server server = new Server(); + try + { + SocketConnector socketConnector = new SocketConnector(); + socketConnector.setPort(0); + server.addConnector(socketConnector); + server.addHandler(new AbstractHandler() + { + public void handle(String string, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setStatus(200); + response.setContentType("text/plain"); + PrintWriter w = response.getWriter(); + w.flush(); + w.println("Geht"); + w.flush(); + w.println("Doch"); + ((Request) request).setHandled(true); + } + }); + server.start(); + + Socket socket = new Socket("localhost",socketConnector.getLocalPort()); + socket.getOutputStream().write("HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n".getBytes()); + socket.getOutputStream().write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes()); + socket.getOutputStream().flush(); + + LineNumberReader reader = new LineNumberReader(new InputStreamReader(socket.getInputStream())); + String line = reader.readLine(); + while (line!=null && line.length()>0) + line = reader.readLine(); + + while (line!=null && line.length()==0) + line = reader.readLine(); + + assertTrue(line.startsWith("HTTP/1.1 200 OK")); + + } + finally + { + server.stop(); + } + } + + private Response newResponse() + { + HttpConnection connection=new HttpConnection(connector,connector._endp,connector._server); + connection.getGenerator().reset(false); + HttpConnection.setCurrentConnection(connection); + Response response = connection.getResponse(); + connection.getRequest().setRequestURI("/test"); + return response; + } + + class TestSession extends AbstractSessionManager.Session + { + public TestSession(AbstractSessionManager abstractSessionManager, String id) + { + abstractSessionManager.super(System.currentTimeMillis(), id); + } + + public Object getAttribute(String name) + { + return null; + } + + public Enumeration getAttributeNames() + { + + return null; + } + + public long getCreationTime() + { + + return 0; + } + + public String getId() + { + return "12345"; + } + + public long getLastAccessedTime() + { + return 0; + } + + public int getMaxInactiveInterval() + { + return 0; + } + + public ServletContext getServletContext() + { + return null; + } + + public HttpSessionContext getSessionContext() + { + return null; + } + + public Object getValue(String name) + { + return null; + } + + public String[] getValueNames() + { + return null; + } + + public void invalidate() + { + } + + public boolean isNew() + { + return false; + } + + public void putValue(String name, Object value) + { + } + + public void removeAttribute(String name) + { + } + + public void removeValue(String name) + { + } + + public void setAttribute(String name, Object value) + { + } + + public void setMaxInactiveInterval(int interval) + { + } + + protected Map newAttributeMap() + { + // TODO Auto-generated method stub + return null; + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java new file mode 100644 index 00000000000..b7dadeda086 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java @@ -0,0 +1,26 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; +import org.eclipse.jetty.server.nio.SelectChannelConnector; + +/** + * HttpServer Tester. + */ +public class SelectChannelServerTest extends HttpServerTestBase +{ + public SelectChannelServerTest() + { + super(new SelectChannelConnector()); + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java new file mode 100644 index 00000000000..45454bf5fba --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java @@ -0,0 +1,51 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.util.Random; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.handler.DefaultHandler; + +/** + * @version $Revision$ + */ +public class ServerTest extends TestCase +{ + /** + * JETTY-87, adding a handler to a server without any handlers should not + * throw an exception + */ + public void testAddHandlerToEmptyServer() + { + Server server=new Server(); + DefaultHandler handler=new DefaultHandler(); + try + { + server.addHandler(handler); + } + catch (Exception e) + { + fail("Adding handler "+handler+" to server "+server+" threw exception "+e); + } + } + + public void testServerWithPort() + { + int port=new Random().nextInt(20000)+10000; + Server server=new Server(port); + assertEquals(port,server.getConnectors()[0].getPort()); + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java new file mode 100644 index 00000000000..d644616c003 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java @@ -0,0 +1,26 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; +import org.eclipse.jetty.server.bio.SocketConnector; + +/** + * HttpServer Tester. + */ +public class SocketServerTest extends HttpServerTestBase +{ + public SocketServerTest() + { + super(new SocketConnector()); + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/UnreadInputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/UnreadInputTest.java new file mode 100644 index 00000000000..70a4464a1c4 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/UnreadInputTest.java @@ -0,0 +1,119 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; + +public class UnreadInputTest extends TestCase +{ + public static final String __OK_RESPONSE = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nServer: Jetty(7.0.x)\r\n\r\n"; + protected Server _server = new Server(); + protected SocketConnector _connector; + protected int _port; + protected Socket _socket; + protected OutputStream _outputStream; + protected InputStream _inputStream; + + public class NoopHandler extends AbstractHandler + { + public void handle(String target, HttpServletRequest request, + HttpServletResponse response) throws IOException, + ServletException + { + //don't read the input, just send something back + ((Request)request).setHandled(true); + response.setStatus(200); + } + } + + + + protected void setUp() throws Exception + { + //server side + _connector = new SocketConnector(); + _server.setConnectors(new Connector[]{ _connector }); + _server.setHandler(new NoopHandler()); + _server.start(); + _port = _connector.getLocalPort(); + + //client side + _socket = new Socket((String)null, _port); + _outputStream = _socket.getOutputStream(); + _inputStream = _socket.getInputStream(); + } + + protected void tearDown() throws Exception + { + _server.stop(); + } + + + + public void testUnreadInput () + throws Exception + { + for (int i=0; i<2; i++) + { + String content = "This is a loooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooooooooooooooooooooooooo"+ + "ooooooooooooooooooooooonnnnnnnnnnnnnnnnggggggggg content"; + byte[] bytes = content.getBytes(); + + _outputStream.write("GET / HTTP/1.1\r\nHost: localhost\r\n".getBytes()); + Thread.currentThread().sleep(500L); + + String str = "Content-Length: "+bytes.length+"\r\n" + "\r\n"; + _outputStream.write(str.getBytes()); + Thread.currentThread().sleep(500L); + + //write some bytes of the content + _outputStream.write(bytes, 0, (bytes.length/2)); + Thread.currentThread().sleep(1000L); + + //write the rest + _outputStream.write(bytes, bytes.length/2, (bytes.length - bytes.length/2)); + } + + + byte[] inbuf = new byte[__OK_RESPONSE.getBytes().length*2]; + int x = _inputStream.read(inbuf); + System.err.println(new String(inbuf, 0, x)); + + _inputStream.close(); + _outputStream.close(); + _socket.close(); + } + + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java new file mode 100644 index 00000000000..b42f4418e76 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java @@ -0,0 +1,176 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; + +public class ContextHandlerCollectionTest extends TestCase +{ + public void testVirtualHostNormalization() throws Exception + { + Server server = new Server(); + LocalConnector connector = new LocalConnector(); + server.setConnectors(new Connector[] + { connector }); + + ContextHandler contextA = new ContextHandler("/"); + contextA.setVirtualHosts(new String[] + { "www.example.com" }); + IsHandledHandler handlerA = new IsHandledHandler(); + contextA.setHandler(handlerA); + + ContextHandler contextB = new ContextHandler("/"); + IsHandledHandler handlerB = new IsHandledHandler(); + contextB.setHandler(handlerB); + contextB.setVirtualHosts(new String[] + { "www.example2.com." }); + + ContextHandler contextC = new ContextHandler("/"); + IsHandledHandler handlerC = new IsHandledHandler(); + contextC.setHandler(handlerC); + + ContextHandlerCollection c = new ContextHandlerCollection(); + + c.addHandler(contextA); + c.addHandler(contextB); + c.addHandler(contextC); + + server.setHandler(c); + + try + { + server.start(); + connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example.com.\n\n"); + + assertTrue(handlerA.isHandled()); + assertFalse(handlerB.isHandled()); + assertFalse(handlerC.isHandled()); + + handlerA.reset(); + handlerB.reset(); + handlerC.reset(); + + connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example2.com\n\n"); + + assertFalse(handlerA.isHandled()); + assertTrue(handlerB.isHandled()); + assertFalse(handlerC.isHandled()); + + } + finally + { + server.stop(); + } + + } + + public void testVirtualHostWildcard() throws Exception + { + Server server = new Server(); + LocalConnector connector = new LocalConnector(); + server.setConnectors(new Connector[] { connector }); + + ContextHandler context = new ContextHandler("/"); + + IsHandledHandler handler = new IsHandledHandler(); + context.setHandler(handler); + + ContextHandlerCollection c = new ContextHandlerCollection(); + c.addHandler(context); + + server.setHandler(c); + + try + { + server.start(); + checkWildcardHost(true,server,null,new String[] {"example.com", ".example.com", "vhost.example.com"}); + checkWildcardHost(false,server,new String[] {null},new String[] {"example.com", ".example.com", "vhost.example.com"}); + + checkWildcardHost(true,server,new String[] {"example.com", "*.example.com"}, new String[] {"example.com", ".example.com", "vhost.example.com"}); + checkWildcardHost(false,server,new String[] {"example.com", "*.example.com"}, new String[] {"badexample.com", ".badexample.com", "vhost.badexample.com"}); + + checkWildcardHost(false,server,new String[] {"*."}, new String[] {"anything.anything"}); + + checkWildcardHost(true,server,new String[] {"*.example.com"}, new String[] {"vhost.example.com", ".example.com"}); + checkWildcardHost(false,server,new String[] {"*.example.com"}, new String[] {"vhost.www.example.com", "example.com", "www.vhost.example.com"}); + + checkWildcardHost(true,server,new String[] {"*.sub.example.com"}, new String[] {"vhost.sub.example.com", ".sub.example.com"}); + checkWildcardHost(false,server,new String[] {"*.sub.example.com"}, new String[] {".example.com", "sub.example.com", "vhost.example.com"}); + + checkWildcardHost(false,server,new String[] {"example.*.com","example.com.*"}, new String[] {"example.vhost.com", "example.com.vhost", "example.com"}); + } + finally + { + server.stop(); + } + } + + private void checkWildcardHost(boolean succeed, Server server, String[] contextHosts, String[] requestHosts) throws Exception + { + LocalConnector connector = (LocalConnector)server.getConnectors()[0]; + ContextHandlerCollection handlerCollection = (ContextHandlerCollection)server.getHandler(); + ContextHandler context = (ContextHandler)handlerCollection.getHandlers()[0]; + IsHandledHandler handler = (IsHandledHandler)context.getHandler(); + + context.setVirtualHosts(contextHosts); + // trigger this manually; it's supposed to be called when adding the handler + handlerCollection.mapContexts(); + + for(String host : requestHosts) + { + connector.getResponses("GET / HTTP/1.1\n" + "Host: "+host+"\n\n"); + if(succeed) + assertTrue("'"+host+"' should have been handled.",handler.isHandled()); + else + assertFalse("'"+host + "' should not have been handled.", handler.isHandled()); + handler.reset(); + } + + } + + public static final class IsHandledHandler extends AbstractHandler + { + private boolean handled; + + public boolean isHandled() + { + return handled; + } + + public void handle(String s, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Request base_request = (request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + base_request.setHandled(true); + this.handled = true; + } + + public void reset() + { + handled = false; + } + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java new file mode 100644 index 00000000000..cb384eaeb99 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java @@ -0,0 +1,259 @@ +// ======================================================================== +// $Id$ +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.resource.Resource; + +/** + * @version $Revision$ + */ +public class ContextHandlerTest extends TestCase +{ + public void testGetResourcePathsWhenSuppliedPathEndsInSlash() throws Exception + { + checkResourcePathsForExampleWebApp("/WEB-INF/"); + } + + public void testGetResourcePathsWhenSuppliedPathDoesNotEndInSlash() throws Exception + { + checkResourcePathsForExampleWebApp("/WEB-INF"); + } + + private void checkResourcePathsForExampleWebApp(String root) throws IOException, MalformedURLException + { + File testDirectory = setupTestDirectory(); + + ContextHandler handler = new ContextHandler(); + + assertTrue("Not a directory " + testDirectory,testDirectory.isDirectory()); + handler.setBaseResource(Resource.newResource(testDirectory.toURL())); + + List paths = new ArrayList(handler.getResourcePaths(root)); + assertEquals(2,paths.size()); + + Collections.sort(paths); + assertEquals("/WEB-INF/jsp/",paths.get(0)); + assertEquals("/WEB-INF/web.xml",paths.get(1)); + } + + private File setupTestDirectory() throws IOException + { + File tmpDir = new File( System.getProperty( "basedir" ) + "/target/tmp/ContextHandlerTest" ); + tmpDir.mkdirs(); + File tmp = File.createTempFile("cht",null, tmpDir ); + tmp.delete(); + tmp.mkdir(); + tmp.deleteOnExit(); + File root = new File(tmp,getClass().getName()); + root.mkdir(); + + File webInf = new File(root,"WEB-INF"); + webInf.mkdir(); + + new File(webInf,"jsp").mkdir(); + new File(webInf,"web.xml").createNewFile(); + + return root; + } + + public void testVirtualHostNormalization() throws Exception + { + Server server = new Server(); + LocalConnector connector = new LocalConnector(); + server.setConnectors(new Connector[] + { connector }); + + ContextHandler contextA = new ContextHandler("/"); + contextA.setVirtualHosts(new String[] + { "www.example.com" }); + IsHandledHandler handlerA = new IsHandledHandler(); + contextA.setHandler(handlerA); + + ContextHandler contextB = new ContextHandler("/"); + IsHandledHandler handlerB = new IsHandledHandler(); + contextB.setHandler(handlerB); + contextB.setVirtualHosts(new String[] + { "www.example2.com." }); + + ContextHandler contextC = new ContextHandler("/"); + IsHandledHandler handlerC = new IsHandledHandler(); + contextC.setHandler(handlerC); + + HandlerCollection c = new HandlerCollection(); + + c.addHandler(contextA); + c.addHandler(contextB); + c.addHandler(contextC); + + server.setHandler(c); + + try + { + server.start(); + connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example.com.\n\n"); + + assertTrue(handlerA.isHandled()); + assertFalse(handlerB.isHandled()); + assertFalse(handlerC.isHandled()); + + handlerA.reset(); + handlerB.reset(); + handlerC.reset(); + + connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example2.com\n\n"); + + assertFalse(handlerA.isHandled()); + assertTrue(handlerB.isHandled()); + assertFalse(handlerC.isHandled()); + + } + finally + { + server.stop(); + } + + } + + public void testVirtualHostWildcard() throws Exception + { + Server server = new Server(); + LocalConnector connector = new LocalConnector(); + server.setConnectors(new Connector[] { connector }); + + ContextHandler context = new ContextHandler("/"); + + IsHandledHandler handler = new IsHandledHandler(); + context.setHandler(handler); + + server.addHandler(context); + + try + { + server.start(); + checkWildcardHost(true,server,null,new String[] {"example.com", ".example.com", "vhost.example.com"}); + checkWildcardHost(false,server,new String[] {null},new String[] {"example.com", ".example.com", "vhost.example.com"}); + + checkWildcardHost(true,server,new String[] {"example.com", "*.example.com"}, new String[] {"example.com", ".example.com", "vhost.example.com"}); + checkWildcardHost(false,server,new String[] {"example.com", "*.example.com"}, new String[] {"badexample.com", ".badexample.com", "vhost.badexample.com"}); + + checkWildcardHost(false,server,new String[] {"*."}, new String[] {"anything.anything"}); + + checkWildcardHost(true,server,new String[] {"*.example.com"}, new String[] {"vhost.example.com", ".example.com"}); + checkWildcardHost(false,server,new String[] {"*.example.com"}, new String[] {"vhost.www.example.com", "example.com", "www.vhost.example.com"}); + + checkWildcardHost(true,server,new String[] {"*.sub.example.com"}, new String[] {"vhost.sub.example.com", ".sub.example.com"}); + checkWildcardHost(false,server,new String[] {"*.sub.example.com"}, new String[] {".example.com", "sub.example.com", "vhost.example.com"}); + + checkWildcardHost(false,server,new String[] {"example.*.com","example.com.*"}, new String[] {"example.vhost.com", "example.com.vhost", "example.com"}); + } + finally + { + server.stop(); + } + } + + private void checkWildcardHost(boolean succeed, Server server, String[] contextHosts, String[] requestHosts) throws Exception + { + LocalConnector connector = (LocalConnector)server.getConnectors()[0]; + ContextHandler context = (ContextHandler)server.getHandler(); + context.setVirtualHosts(contextHosts); + + IsHandledHandler handler = (IsHandledHandler)context.getHandler(); + for(String host : requestHosts) + { + connector.getResponses("GET / HTTP/1.1\n" + "Host: "+host+"\n\n"); + if(succeed) + assertTrue("'"+host+"' should have been handled.",handler.isHandled()); + else + assertFalse("'"+host + "' should not have been handled.", handler.isHandled()); + handler.reset(); + } + + } + + public static final class IsHandledHandler extends AbstractHandler + { + private boolean handled; + + public boolean isHandled() + { + return handled; + } + + public void handle(String s, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Request base_request = (request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + base_request.setHandled(true); + this.handled = true; + } + + public void reset() + { + handled = false; + } + } + + public void testAttributes() throws Exception + { + ContextHandler handler = new ContextHandler(); + handler.setAttribute("aaa","111"); + handler.getServletContext().setAttribute("bbb","222"); + assertEquals("111",handler.getServletContext().getAttribute("aaa")); + assertEquals("222",handler.getAttribute("bbb")); + + handler.start(); + + handler.getServletContext().setAttribute("aaa","000"); + handler.setAttribute("ccc","333"); + handler.getServletContext().setAttribute("ddd","444"); + assertEquals("111",handler.getServletContext().getAttribute("aaa")); + assertEquals("222",handler.getServletContext().getAttribute("bbb")); + assertEquals("333",handler.getServletContext().getAttribute("ccc")); + assertEquals("444",handler.getServletContext().getAttribute("ddd")); + + assertEquals("111",handler.getAttribute("aaa")); + assertEquals("222",handler.getAttribute("bbb")); + assertEquals("333",handler.getAttribute("ccc")); + assertEquals(null,handler.getAttribute("ddd")); + + + handler.stop(); + + assertEquals("111",handler.getServletContext().getAttribute("aaa")); + assertEquals("222",handler.getServletContext().getAttribute("bbb")); + assertEquals("333",handler.getServletContext().getAttribute("ccc")); + assertEquals(null,handler.getServletContext().getAttribute("ddd")); + + + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java new file mode 100644 index 00000000000..43a658bfd56 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java @@ -0,0 +1,383 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; + +public class StatisticsHandlerTest extends TestCase +{ + protected Server _server = new Server(); + protected LocalConnector _connector; + + private StatisticsHandler _statsHandler; + + protected void setUp() throws Exception + { + _statsHandler = new StatisticsHandler(); + _server.setHandler(_statsHandler); + + _connector = new LocalConnector(); + _server.setConnectors(new Connector[]{ _connector }); + _server.start(); + + } + + protected void tearDown() throws Exception + { + _server.stop(); + } + + /* TODO fix this + public void testSuspendedStats() throws Exception + { + process(new ResumeHandler()); + process(new SuspendHandler()); + process(); + + assertEquals(3,_statsHandler.getRequests()); + assertEquals(1,_statsHandler.getRequestsTimedout()); + assertEquals(1,_statsHandler.getRequestsResumed()); + } + */ + + // TODO: keep it active without blocking + // public void testActiveStats() throws Exception + // { + // process(new ActiveHandler(_lock)); + // process(new ActiveHandler(_lock)); + // + // assertEquals(2, _statsHandler.getRequests()); + // assertEquals(2, _statsHandler.getRequestsActive()); + // assertEquals(2, _statsHandler.getRequestsActiveMax()); + // assertEquals(0, _statsHandler.getRequestsActiveMin()); + // + // _statsHandler.statsReset(); + // assertEquals(2, _statsHandler.getRequestsActive()); + // assertEquals(2, _statsHandler.getRequestsActiveMax()); + // assertEquals(2, _statsHandler.getRequestsActiveMin()); + // + // process(); + // assertEquals(1, _statsHandler.getRequests()); + // assertEquals(2, _statsHandler.getRequestsActive()); + // assertEquals(3, _statsHandler.getRequestsActiveMax()); + // assertEquals(2, _statsHandler.getRequestsActiveMin()); + // } + + public void testDurationStats() throws Exception + { + process(new DurationHandler(200)); + process(new DurationHandler(500)); + + isApproximately(200,_statsHandler.getRequestsDurationMin()); + isApproximately(500,_statsHandler.getRequestsDurationMax()); + isApproximately(350,_statsHandler.getRequestsDurationAve()); + isApproximately(700,_statsHandler.getRequestsDurationTotal()); + + isApproximately(200,_statsHandler.getRequestsActiveDurationMin()); + isApproximately(500,_statsHandler.getRequestsActiveDurationMax()); + isApproximately(350,_statsHandler.getRequestsActiveDurationAve()); + isApproximately(700,_statsHandler.getRequestsActiveDurationTotal()); + + _statsHandler.statsReset(); + assertEquals(0,_statsHandler.getRequestsDurationMin()); + assertEquals(0,_statsHandler.getRequestsDurationMax()); + assertEquals(0,_statsHandler.getRequestsDurationAve()); + assertEquals(0,_statsHandler.getRequestsDurationTotal()); + assertEquals(0,_statsHandler.getRequestsActiveDurationMin()); + assertEquals(0,_statsHandler.getRequestsActiveDurationMax()); + assertEquals(0,_statsHandler.getRequestsActiveDurationAve()); + assertEquals(0,_statsHandler.getRequestsActiveDurationTotal()); + } + + /* + public void testDurationWithSuspend() throws Exception + { + int processDuration = 100; + long[] suspendFor = new long[] + { 200, 400, 600 }; + int suspendDuration = 0; + for (long i : suspendFor) + suspendDuration += i; + + process(new DurationSuspendHandler(processDuration,suspendFor)); + + isApproximately(processDuration,_statsHandler.getRequestsActiveDurationTotal()); + isApproximately(processDuration + suspendDuration,_statsHandler.getRequestsDurationTotal()); + + } + */ + + /* TODO fix + public void testResponses() throws Exception + { + // all return 200 + process(); + assertEquals(1,_statsHandler.getResponses2xx()); + + // don't count the suspend. + process(new ResumeHandler()); + assertEquals(2,_statsHandler.getResponses2xx()); + + process(new SuspendHandler(1)); + assertEquals(3,_statsHandler.getResponses2xx()); + + } + */ + + /* TODO fix + public void testComplete() throws Exception + { + int initialDelay = 200; + int completeDuration = 500; + + + synchronized(_server) + { + process(new SuspendCompleteHandler(initialDelay, completeDuration, _server)); + + try + { + _server.wait(); + } + catch(InterruptedException e) + { + } + } + + isApproximately(initialDelay,_statsHandler.getRequestsActiveDurationTotal()); + // fails; twice the expected value + //TODO failed in jaspi branch +// isApproximately(initialDelay + completeDuration,_statsHandler.getRequestsDurationTotal()); + } + */ + + public void process() throws Exception + { + process(null); + } + + public synchronized void process(HandlerWrapper customHandler) throws Exception + { + _statsHandler.setHandler(customHandler); + + String request = "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Length: 6\r\n" + "\r\n" + "test\r\n"; + + _connector.reopen(); + _connector.getResponses(request); + _statsHandler.setHandler(null); + + } + + private void isApproximately(long expected, long actual) + { + assertTrue("expected " + expected + "; got " + actual,actual > expected / 2); + assertTrue("expected " + expected + "; got " + actual,actual < (expected * 3) / 2); + } + + private static class ActiveHandler extends HandlerWrapper + { + private Object _lock; + + public ActiveHandler(Object lock) + { + _lock = lock; + } + + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!request.isAsyncStarted()) + { + try + { + synchronized (_lock) + { + _lock.wait(); + } + } + catch (InterruptedException e) + { + } + } + } + + } + + private static class SuspendHandler extends HandlerWrapper + { + private int _suspendFor; + + public SuspendHandler() + { + _suspendFor = 10; + } + + public SuspendHandler(int suspendFor) + { + _suspendFor = suspendFor; + } + + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!request.isAsyncStarted()) + { + request.setAsyncTimeout(_suspendFor); + request.startAsync(); + } + } + + } + + private static class ResumeHandler extends HandlerWrapper + { + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!request.isAsyncStarted()) + { + request.setAsyncTimeout(100000); + request.startAsync().dispatch(); + } + } + + } + + private static class DurationHandler extends HandlerWrapper + { + private int _duration; + + public DurationHandler(int duration) + { + _duration = duration; + } + + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!request.isAsyncStarted()) + { + try + { + Thread.sleep(_duration); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + } + + private static class DurationSuspendHandler extends HandlerWrapper + { + private int _duration; + private long[] _suspendFor; + + public DurationSuspendHandler(int duration, long[] suspendFor) + { + _duration = duration; + _suspendFor = suspendFor; + } + + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + + Integer i = (Integer)request.getAttribute("i"); + if (i == null) + i = 0; + + if (i < _suspendFor.length) + { + request.setAsyncTimeout(_suspendFor[i]); + request.startAsync(); + request.setAttribute("i",i + 1); + return; + } + else + { + try + { + Thread.sleep(_duration); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + } + + } + + private class SuspendCompleteHandler extends HandlerWrapper + { + private long _initialDuration; + private long _completeDuration; + private Object _lock; + public SuspendCompleteHandler(int initialDuration, int completeDuration, Object lock) + { + _initialDuration = initialDuration; + _completeDuration = completeDuration; + _lock = lock; + } + + public void handle(String target, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + + if(!request.isAsyncStarted()) + { + try + { + Thread.sleep(_initialDuration); + } catch (InterruptedException e1) + { + } + + request.setAsyncTimeout(_completeDuration*10); + request.startAsync(); + + (new Thread() { + public void run() + { + try + { + Thread.sleep(_completeDuration); + request.getAsyncContext().complete(); + + synchronized(_lock) + { + _lock.notify(); + } + } + catch(InterruptedException e) + { + } + } + }).start(); + } + } + + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java new file mode 100644 index 00000000000..81b9dde683e --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java @@ -0,0 +1,266 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +// JettyTest.java -- +// +// Junit test that shows the Jetty SSL bug. +// + +package org.eclipse.jetty.server.ssl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.net.Socket; +import java.net.SocketTimeoutException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; + +/** + * HttpServer Tester. + */ +public class SSLEngineTest extends TestCase +{ + // ~ Static fields/initializers + // --------------------------------------------- + + // Useful constants + private static final String HELLO_WORLD="Hello world. The quick brown fox jumped over the lazy dog. How now brown cow. The rain in spain falls mainly on the plain.\n"; + private static final String JETTY_VERSION=Server.getVersion(); + private static final String PROTOCOL_VERSION="2.0"; + + /** The request. */ + private static final String REQUEST0_HEADER="POST /r0 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Content-Length: "; + private static final String REQUEST1_HEADER="POST /r1 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Connection: close\n"+"Content-Length: "; + private static final String REQUEST_CONTENT="\n" + +"\n"+""; + + private static final String REQUEST0=REQUEST0_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT; + private static final String REQUEST1=REQUEST1_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT; + + /** The expected response. */ + private static final String RESPONSE0="HTTP/1.1 200 OK\n"+"Content-Length: "+HELLO_WORLD.length()+"\n"+"Server: Jetty("+JETTY_VERSION+")\n"+'\n'+HELLO_WORLD; + private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Connection: close\n"+"Server: Jetty("+JETTY_VERSION+")\n"+'\n'+HELLO_WORLD; + + private static final TrustManager[] s_dummyTrustManagers=new TrustManager[] + { new X509TrustManager() + { + public java.security.cert.X509Certificate[] getAcceptedIssuers() + { + return null; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) + + { + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) + + { + } + } }; + + Server server; + SslSelectChannelConnector connector; + + // ~ Methods + // ---------------------------------------------------------------- + + public void setUp() throws Exception + { + super.setUp(); + + server=new Server(); + connector=new SslSelectChannelConnector(); + + String keystore = System.getProperty("user.dir")+File.separator+"src"+File.separator+"test"+File.separator+"resources"+File.separator+"keystore"; + + connector.setPort(0); + connector.setKeystore(keystore); + connector.setPassword("storepwd"); + connector.setKeyPassword("keypwd"); + + server.setConnectors(new Connector[] + { connector }); + server.setHandler(new HelloWorldHandler()); + server.start(); + Thread.sleep(100); + } + + + public void tearDown() throws Exception + { + Thread.sleep(2000); + server.stop(); + super.tearDown(); + } + + public void testNothing() throws Exception + { + + } + + /** + * Feed the server the entire request at once. + * + * @throws Exception + */ + public void /*test*/RequestJettyHttps() throws Exception + { + final int loops=100; + final int numConns=100; + + Socket[] client=new Socket[numConns]; + + SSLContext ctx=SSLContext.getInstance("SSLv3"); + ctx.init(null,s_dummyTrustManagers,new java.security.SecureRandom()); + + int port=connector.getLocalPort(); + + try + { + for (int l=0;lX_7T1LX-9cnmv_92vyZY^nksPD zdgGz_%YMiA%<+G`?zc{_^OTmKsam}GdFt7neK!{P|Bco8Yq|9PvTGT0HqQC!@x9l1 z+kfrLjk$U4PfrzX4>?lEY_X)NU`c_upzVKwLzZV_Z=OB7;%H;~#q=-kB7C15mGAK^ zvQDWpwV1Q=%|)J*vS+h8eD_X&KBLa;y!Ym4fd?y?u6<@yiQRcgW4YSWJ1qI-Q@4qE za4m1Vxao>O&#^E1)!s>&+m1~AFk#ErRgE9c$R|1RHZn>ST)F2PCX`h;GftH|P3C*s zWzB^ZGW^U2Io6G9jl> z6zzz+mrM40Funf&SL@T49sAWnZvUNcc-}kaM`;+(`I*O}e=OTwz2vdB%C^t0KV2dN z8|5bLIJC=m`3p1S>EdUM&gly9o_)k?62R=&u_P~(KYzPEv+V7u$GBF7Yt*targsEg zTQzgVqnKmgxtO%H)!0fto_uWX^v1c;QdM?#toT{0*tgvCKBx=r`VraapZ2`+cgggB z-Pvqw9={ifyv(DLz=%jkh_cpEkNBn#fQtx!K-V#22ApgPb-K(wNrdO3Et=(zk zH%r#4Uj9|=lZlRA5;d`xvpUaban7~tb)L?vqO(?31t6P_KXXUmI|NTdEW^*XD%W$kc6J6C`ZJL_TRc(ux#yKj=Ue_0-Rb!XA^BcIEU@^esCUfRc7B*q_(7f!t{PH}Q2nU7; zJ3>UpKnkRbOIX-9zos$!bwcyY3l3?6uy+&YLB9AjrXwMJ9=TVxnh^gTTRrgQDAx z_HixoIbPSC|G?I^w5omSz6+C2WR&Q742Af!tZ{F_B0c(^#Lh& zza|JWF*7nSB0CQl;mkmH*$SFDWnY}}>VU(o3#<5V+huGG`Dn2??1_qdd2)==i--F? zs#KSqOx?Wrm7jRA(!0Yuu5HVhrWZZce5Lb}5Bq8!uvR=V-JM%7`INM|?JE9h{&PR9 zPS5OYv=7|qeAeX{<0}8%Th2N96wN*x(D1*r!0!3JEkDAd&tKmoUZ_-IpH*z9SS + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-servlet-tester + jar + Jetty :: Servlet Tester + + + org.eclipse.jetty + jetty-webapp + ${project.version} + + + junit + junit + test + + + diff --git a/jetty-servlet-tester/src/main/java/org/eclipse/jetty/testing/HttpTester.java b/jetty-servlet-tester/src/main/java/org/eclipse/jetty/testing/HttpTester.java new file mode 100644 index 00000000000..94fbd9c1805 --- /dev/null +++ b/jetty-servlet-tester/src/main/java/org/eclipse/jetty/testing/HttpTester.java @@ -0,0 +1,504 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.testing; + +import java.io.IOException; +import java.util.Enumeration; + +import javax.servlet.http.Cookie; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.SimpleBuffers; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.io.bio.StringEndPoint; +import org.eclipse.jetty.util.ByteArrayOutputStream2; + +/* ------------------------------------------------------------ */ +/** Test support class. + * Assist with parsing and generating HTTP requests and responses. + * + *
      + *      HttpTester tester = new HttpTester();
      + *      
      + *      tester.parse(
      + *          "GET /uri HTTP/1.1\r\n"+
      + *          "Host: fakehost\r\n"+
      + *          "Content-Length: 10\r\n" +
      + *          "\r\n");
      + *     
      + *      System.err.println(tester.getMethod());
      + *      System.err.println(tester.getURI());
      + *      System.err.println(tester.getVersion());
      + *      System.err.println(tester.getHeader("Host"));
      + *      System.err.println(tester.getContent());
      + * 
      + * + * + * @see org.eclipse.jetty.testing.ServletTester + */ +public class HttpTester +{ + protected HttpFields _fields=new HttpFields(); + protected String _method; + protected String _uri; + protected String _version; + protected int _status; + protected String _reason; + protected ByteArrayOutputStream2 _parsedContent; + protected byte[] _genContent; + + private String _charset, _defaultCharset; + private Buffer _contentType; + + public HttpTester() + { + this("UTF-8"); + } + + public HttpTester(String charset) + { + _defaultCharset = charset; + } + + public void reset() + { + _fields.clear(); + _method=null; + _uri=null; + _version=null; + _status=0; + _reason=null; + _parsedContent=null; + _genContent=null; + } + + private String getString(Buffer buffer) + { + return getString(buffer.asArray()); + } + + private String getString(byte[] b) + { + if(_charset==null) + return new String(b); + try + { + return new String(b, _charset); + } + catch(Exception e) + { + return new String(b); + } + } + + private byte[] getByteArray(String str) + { + if(_charset==null) + return str.getBytes(); + try + { + return str.getBytes(_charset); + } + catch(Exception e) + { + return str.getBytes(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Parse one HTTP request or response + * @param rawHTTP Raw HTTP to parse + * @return Any unparsed data in the rawHTTP (eg pipelined requests) + * @throws IOException + */ + public String parse(String rawHTTP) throws IOException + { + _charset = _defaultCharset; + ByteArrayBuffer buf = new ByteArrayBuffer(getByteArray(rawHTTP)); + View view = new View(buf); + HttpParser parser = new HttpParser(view,new PH()); + parser.parse(); + return getString(view.asArray()); + } + + /* ------------------------------------------------------------ */ + public String generate() throws IOException + { + _charset = _defaultCharset; + _contentType = _fields.get(HttpHeaders.CONTENT_TYPE_BUFFER); + if(_contentType!=null) + { + String charset = MimeTypes.getCharsetFromContentType(_contentType); + if(charset!=null) + _charset = charset; + } + Buffer bb=new ByteArrayBuffer(32*1024 + (_genContent!=null?_genContent.length:0)); + Buffer sb=new ByteArrayBuffer(4*1024); + StringEndPoint endp = new StringEndPoint(_charset); + HttpGenerator generator = new HttpGenerator(new SimpleBuffers(new Buffer[]{sb,bb}),endp, sb.capacity(), bb.capacity()); + + if (_method!=null) + { + generator.setRequest(getMethod(),getURI()); + if (_version==null) + generator.setVersion(HttpVersions.HTTP_1_1_ORDINAL); + else + generator.setVersion(HttpVersions.CACHE.getOrdinal(HttpVersions.CACHE.lookup(_version))); + generator.completeHeader(_fields,false); + if (_genContent!=null) + generator.addContent(new View(new ByteArrayBuffer(_genContent)),false); + else if (_parsedContent!=null) + generator.addContent(new ByteArrayBuffer(_parsedContent.toByteArray()),false); + } + + generator.complete(); + generator.flushBuffer(); + return endp.getOutput(); + } + + /* ------------------------------------------------------------ */ + /** + * @return the method + */ + public String getMethod() + { + return _method; + } + + /* ------------------------------------------------------------ */ + /** + * @param method the method to set + */ + public void setMethod(String method) + { + _method=method; + } + + /* ------------------------------------------------------------ */ + /** + * @return the reason + */ + public String getReason() + { + return _reason; + } + + /* ------------------------------------------------------------ */ + /** + * @param reason the reason to set + */ + public void setReason(String reason) + { + _reason=reason; + } + + /* ------------------------------------------------------------ */ + /** + * @return the status + */ + public int getStatus() + { + return _status; + } + + /* ------------------------------------------------------------ */ + /** + * @param status the status to set + */ + public void setStatus(int status) + { + _status=status; + } + + /* ------------------------------------------------------------ */ + /** + * @return the uri + */ + public String getURI() + { + return _uri; + } + + /* ------------------------------------------------------------ */ + /** + * @param uri the uri to set + */ + public void setURI(String uri) + { + _uri=uri; + } + + /* ------------------------------------------------------------ */ + /** + * @return the version + */ + public String getVersion() + { + return _version; + } + + /* ------------------------------------------------------------ */ + /** + * @param version the version to set + */ + public void setVersion(String version) + { + _version=version; + } + + /* ------------------------------------------------------------ */ + public String getContentType() + { + return getString(_contentType); + } + + /* ------------------------------------------------------------ */ + public String getCharacterEncoding() + { + return _charset; + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + * @throws IllegalArgumentException + * @see org.eclipse.jetty.http.HttpFields#add(java.lang.String, java.lang.String) + */ + public void addHeader(String name, String value) throws IllegalArgumentException + { + _fields.add(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param date + * @see org.eclipse.jetty.http.HttpFields#addDateField(java.lang.String, long) + */ + public void addDateHeader(String name, long date) + { + _fields.addDateField(name,date); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + * @see org.eclipse.jetty.http.HttpFields#addLongField(java.lang.String, long) + */ + public void addLongHeader(String name, long value) + { + _fields.addLongField(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param cookie + * @see org.eclipse.jetty.http.HttpFields#addSetCookie(javax.servlet.http.Cookie) + */ + public void addSetCookie(Cookie cookie) + { + _fields.addSetCookie( + cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.getComment(), + cookie.getSecure(), + cookie.isHttpOnly(), + cookie.getVersion()); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @return + * @see org.eclipse.jetty.http.HttpFields#getDateField(java.lang.String) + */ + public long getDateHeader(String name) + { + return _fields.getDateField(name); + } + + /* ------------------------------------------------------------ */ + /** + * @return + * @see org.eclipse.jetty.http.HttpFields#getFieldNames() + */ + public Enumeration getHeaderNames() + { + return _fields.getFieldNames(); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @return + * @throws NumberFormatException + * @see org.eclipse.jetty.http.HttpFields#getLongField(java.lang.String) + */ + public long getLongHeader(String name) throws NumberFormatException + { + return _fields.getLongField(name); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @return + * @see org.eclipse.jetty.http.HttpFields#getStringField(java.lang.String) + */ + public String getHeader(String name) + { + return _fields.getStringField(name); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @return + * @see org.eclipse.jetty.http.HttpFields#getValues(java.lang.String) + */ + public Enumeration getHeaderValues(String name) + { + return _fields.getValues(name); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + * @see org.eclipse.jetty.http.HttpFields#put(java.lang.String, java.lang.String) + */ + public void setHeader(String name, String value) + { + _fields.put(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param date + * @see org.eclipse.jetty.http.HttpFields#putDateField(java.lang.String, long) + */ + public void setDateHeader(String name, long date) + { + _fields.putDateField(name,date); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + * @see org.eclipse.jetty.http.HttpFields#putLongField(java.lang.String, long) + */ + public void setLongHeader(String name, long value) + { + _fields.putLongField(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @see org.eclipse.jetty.http.HttpFields#remove(java.lang.String) + */ + public void removeHeader(String name) + { + _fields.remove(name); + } + + /* ------------------------------------------------------------ */ + public String getContent() + { + if (_parsedContent!=null) + return getString(_parsedContent.toByteArray()); + if (_genContent!=null) + return getString(_genContent); + return null; + } + + /* ------------------------------------------------------------ */ + public void setContent(String content) + { + _parsedContent=null; + if (content!=null) + { + _genContent=getByteArray(content); + setLongHeader(HttpHeaders.CONTENT_LENGTH,_genContent.length); + } + else + { + removeHeader(HttpHeaders.CONTENT_LENGTH); + _genContent=null; + } + } + + /* ------------------------------------------------------------ */ + private class PH extends HttpParser.EventHandler + { + public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException + { + reset(); + _method=getString(method); + _uri=getString(url); + _version=getString(version); + } + + public void startResponse(Buffer version, int status, Buffer reason) throws IOException + { + reset(); + _version=getString(version); + _status=status; + _reason=getString(reason); + } + + public void parsedHeader(Buffer name, Buffer value) throws IOException + { + _fields.add(name,value); + } + + public void headerComplete() throws IOException + { + _contentType = _fields.get(HttpHeaders.CONTENT_TYPE_BUFFER); + if(_contentType!=null) + { + String charset = MimeTypes.getCharsetFromContentType(_contentType); + if(charset!=null) + _charset = charset; + } + } + + public void messageComplete(long contextLength) throws IOException + { + } + + public void content(Buffer ref) throws IOException + { + if (_parsedContent==null) + _parsedContent=new ByteArrayOutputStream2(); + _parsedContent.write(ref.asArray()); + } + } + +} diff --git a/jetty-servlet-tester/src/main/java/org/eclipse/jetty/testing/ServletTester.java b/jetty-servlet-tester/src/main/java/org/eclipse/jetty/testing/ServletTester.java new file mode 100644 index 00000000000..4cc262f90ab --- /dev/null +++ b/jetty-servlet-tester/src/main/java/org/eclipse/jetty/testing/ServletTester.java @@ -0,0 +1,358 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.testing; + +import java.util.Enumeration; +import java.util.EventListener; + +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.Attributes; + + + +/* ------------------------------------------------------------ */ +/** Testing support for servlets and filters. + * + * Allows a programatic setup of a context with servlets and filters for + * testing. Raw HTTP requests may be sent to the context and responses received. + * To avoid handling raw HTTP see {@link org.eclipse.jetty.testing.HttpTester}. + *
      + *      ServletTester tester=new ServletTester();
      + *      tester.setContextPath("/context");
      + *      tester.addServlet(TestServlet.class, "/servlet/*");
      + *      tester.addServlet("org.eclipse.jetty.servlet.DefaultServlet", "/");
      + *      tester.start();
      + *      String response = tester.getResponses("GET /context/servlet/info HTTP/1.0\r\n\r\n");
      + * 
      + * + * @see org.eclipse.jetty.testing.HttpTester + * + * + */ +public class ServletTester +{ + Server _server = new Server(); + LocalConnector _connector = new LocalConnector(); +// Context _context = new Context(Context.SESSIONS|Context.SECURITY); + //jaspi why security if it is not set up? + ServletContextHandler _context = new ServletContextHandler(ServletContextHandler.SESSIONS); + + public ServletTester() + { + try + { + _server.setSendServerVersion(false); + _server.addConnector(_connector); + _server.addHandler(_context); + } + catch (Error e) + { + throw e; + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + public void start() throws Exception + { + _server.start(); + } + + /* ------------------------------------------------------------ */ + public void stop() throws Exception + { + _server.stop(); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler getContext() + { + return _context; + } + + /* ------------------------------------------------------------ */ + /** Get raw HTTP responses from raw HTTP requests. + * Multiple requests and responses may be handled, but only if + * persistent connections conditions apply. + * @param rawRequests String of raw HTTP requests + * @return String of raw HTTP responses + * @throws Exception + */ + public String getResponses(String rawRequests) throws Exception + { + _connector.reopen(); + String responses = _connector.getResponses(rawRequests); + return responses; + } + + /* ------------------------------------------------------------ */ + /** Get raw HTTP responses from raw HTTP requests. + * Multiple requests and responses may be handled, but only if + * persistent connections conditions apply. + * @param rawRequests String of raw HTTP requests + * @param connector The connector to handle the responses + * @return String of raw HTTP responses + * @throws Exception + */ + public String getResponses(String rawRequests, LocalConnector connector) throws Exception + { + connector.reopen(); + String responses = connector.getResponses(rawRequests); + return responses; + } + + /* ------------------------------------------------------------ */ + /** Get raw HTTP responses from raw HTTP requests. + * Multiple requests and responses may be handled, but only if + * persistent connections conditions apply. + * @param rawRequests String of raw HTTP requests + * @return String of raw HTTP responses + * @throws Exception + */ + public ByteArrayBuffer getResponses(ByteArrayBuffer rawRequests) throws Exception + { + _connector.reopen(); + ByteArrayBuffer responses = _connector.getResponses(rawRequests,false); + return responses; + } + + /* ------------------------------------------------------------ */ + /** Get raw HTTP responses from raw HTTP requests. + * Multiple requests and responses may be handled, but only if + * persistent connections conditions apply. + * @param rawRequests String of raw HTTP requests + * @param connector The connector to handle the responses + * @return String of raw HTTP responses + * @throws Exception + */ + public ByteArrayBuffer getResponses(ByteArrayBuffer rawRequests, LocalConnector connector) throws Exception + { + connector.reopen(); + ByteArrayBuffer responses = connector.getResponses(rawRequests,false); + return responses; + } + + /* ------------------------------------------------------------ */ + /** Create a Socket connector. + * This methods adds a socket connector to the server + * @param locahost if true, only listen on local host, else listen on all interfaces. + * @return A URL to access the server via the socket connector. + * @throws Exception + */ + public String createSocketConnector(boolean localhost) + throws Exception + { + synchronized (this) + { + SocketConnector connector = new SocketConnector(); + if (localhost) + connector.setHost("127.0.0.1"); + _server.addConnector(connector); + if (_server.isStarted()) + connector.start(); + else + connector.open(); + + return "http://127.0.0.1:"+connector.getLocalPort(); + } + } + + /* ------------------------------------------------------------ */ + /** Create a Socket connector. + * This methods adds a socket connector to the server + * @param locahost if true, only listen on local host, else listen on all interfaces. + * @return A URL to access the server via the socket connector. + * @throws Exception + */ + public LocalConnector createLocalConnector() + throws Exception + { + synchronized (this) + { + LocalConnector connector = new LocalConnector(); + _server.addConnector(connector); + + if (_server.isStarted()) + connector.start(); + + return connector; + } + } + + /* ------------------------------------------------------------ */ + /** + * @param listener + * @see org.eclipse.jetty.handler.ContextHandler#addEventListener(java.util.EventListener) + */ + public void addEventListener(EventListener listener) + { + _context.addEventListener(listener); + } + + /* ------------------------------------------------------------ */ + /** + * @param filterClass + * @param pathSpec + * @param dispatches + * @return + * @see org.eclipse.jetty.servlet.Scope#addFilter(java.lang.Class, java.lang.String, int) + */ + public FilterHolder addFilter(Class filterClass, String pathSpec, int dispatches) + { + return _context.addFilter(filterClass,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** + * @param filterClass + * @param pathSpec + * @param dispatches + * @return + * @see org.eclipse.jetty.servlet.Scope#addFilter(java.lang.String, java.lang.String, int) + */ + public FilterHolder addFilter(String filterClass, String pathSpec, int dispatches) + { + return _context.addFilter(filterClass,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** + * @param servlet + * @param pathSpec + * @return + * @see org.eclipse.jetty.servlet.Scope#addServlet(java.lang.Class, java.lang.String) + */ + public ServletHolder addServlet(Class servlet, String pathSpec) + { + return _context.addServlet(servlet,pathSpec); + } + + /* ------------------------------------------------------------ */ + /** + * @param className + * @param pathSpec + * @return + * @see org.eclipse.jetty.servlet.Scope#addServlet(java.lang.String, java.lang.String) + */ + public ServletHolder addServlet(String className, String pathSpec) + { + return _context.addServlet(className,pathSpec); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @return + * @see org.eclipse.jetty.handler.ContextHandler#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _context.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @return + * @see org.eclipse.jetty.handler.ContextHandler#getAttributeNames() + */ + public Enumeration getAttributeNames() + { + return _context.getAttributeNames(); + } + + /* ------------------------------------------------------------ */ + /** + * @return + * @see org.eclipse.jetty.handler.ContextHandler#getAttributes() + */ + public Attributes getAttributes() + { + return _context.getAttributes(); + } + + /* ------------------------------------------------------------ */ + /** + * @return + * @see org.eclipse.jetty.handler.ContextHandler#getResourceBase() + */ + public String getResourceBase() + { + return _context.getResourceBase(); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param value + * @see org.eclipse.jetty.handler.ContextHandler#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object value) + { + _context.setAttribute(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param classLoader + * @see org.eclipse.jetty.handler.ContextHandler#setClassLoader(java.lang.ClassLoader) + */ + public void setClassLoader(ClassLoader classLoader) + { + _context.setClassLoader(classLoader); + } + + /* ------------------------------------------------------------ */ + /** + * @param contextPath + * @see org.eclipse.jetty.handler.ContextHandler#setContextPath(java.lang.String) + */ + public void setContextPath(String contextPath) + { + _context.setContextPath(contextPath); + } + + /* ------------------------------------------------------------ */ + /** + * @param eventListeners + * @see org.eclipse.jetty.handler.ContextHandler#setEventListeners(java.util.EventListener[]) + */ + public void setEventListeners(EventListener[] eventListeners) + { + _context.setEventListeners(eventListeners); + } + + /* ------------------------------------------------------------ */ + /** + * @param resourceBase + * @see org.eclipse.jetty.handler.ContextHandler#setResourceBase(java.lang.String) + */ + public void setResourceBase(String resourceBase) + { + _context.setResourceBase(resourceBase); + } + +} diff --git a/jetty-servlet-tester/src/test/java/org/eclipse/jetty/testing/HttpTesterTest.java b/jetty-servlet-tester/src/test/java/org/eclipse/jetty/testing/HttpTesterTest.java new file mode 100644 index 00000000000..4ae2f291301 --- /dev/null +++ b/jetty-servlet-tester/src/test/java/org/eclipse/jetty/testing/HttpTesterTest.java @@ -0,0 +1,42 @@ +// ======================================================================== +// Copyright (c) 2007-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.testing; + +import junit.framework.TestCase; + +public class HttpTesterTest extends TestCase +{ + + public void testCharset() throws Exception + { + HttpTester tester = new HttpTester(); + tester.parse( + "POST /uri© HTTP/1.1\r\n"+ + "Host: fakehost\r\n"+ + "Content-Length: 11\r\n" + + "Content-Type: text/plain; charset=utf-8\r\n" + + "\r\n" + + "123456789©"); + System.err.println(tester.getMethod()); + System.err.println(tester.getURI()); + System.err.println(tester.getVersion()); + System.err.println(tester.getHeader("Host")); + System.err.println(tester.getContentType()); + System.err.println(tester.getCharacterEncoding()); + System.err.println(tester.getContent()); + assertEquals(tester.getContent(), "123456789©"); + System.err.println(tester.generate()); + } + +} diff --git a/jetty-servlet-tester/src/test/java/org/eclipse/jetty/testing/ServletTest.java b/jetty-servlet-tester/src/test/java/org/eclipse/jetty/testing/ServletTest.java new file mode 100644 index 00000000000..6446ec12e3e --- /dev/null +++ b/jetty-servlet-tester/src/test/java/org/eclipse/jetty/testing/ServletTest.java @@ -0,0 +1,288 @@ +package org.eclipse.jetty.testing; +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + + +import java.io.IOException; +import java.net.URL; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.IO; + +public class ServletTest extends TestCase +{ + ServletTester tester; + + /* ------------------------------------------------------------ */ + protected void setUp() throws Exception + { + super.setUp(); + tester=new ServletTester(); + tester.setContextPath("/context"); + tester.addServlet(TestServlet.class, "/servlet/*"); + tester.addServlet(HelloServlet.class, "/hello/*"); + tester.addServlet("org.eclipse.jetty.servlet.DefaultServlet", "/"); + tester.start(); + } + + /* ------------------------------------------------------------ */ + protected void tearDown() throws Exception + { + tester.stop(); + tester=null; + super.tearDown(); + } + + /* ------------------------------------------------------------ */ + public void testServletTesterRaw() throws Exception + { + // Raw HTTP test requests + String requests= + "GET /context/servlet/info?query=foo HTTP/1.1\r\n"+ + "Host: tester\r\n"+ + "\r\n"+ + + "GET /context/hello HTTP/1.1\r\n"+ + "Host: tester\r\n"+ + "\r\n"; + + String responses = tester.getResponses(requests); + + String expected= + "HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/html;charset=ISO-8859-1\r\n"+ + "Content-Length: 21\r\n"+ + "\r\n"+ + "

      Test Servlet

      " + + + "HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/html;charset=ISO-8859-1\r\n"+ + "Content-Length: 22\r\n"+ + "\r\n"+ + "

      Hello Servlet

      "; + + assertEquals(expected,responses); + } + + /* ------------------------------------------------------------ */ + public void testServletTesterClient() throws Exception + { + String base_url=tester.createSocketConnector(true); + + URL url = new URL(base_url+"/context/hello/info"); + String result = IO.toString(url.openStream()); + assertEquals("

      Hello Servlet

      ",result); + } + + /* ------------------------------------------------------------ */ + public void testHttpTester() throws Exception + { + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + + // test GET + request.setMethod("GET"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/hello/info"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(200,response.getStatus()); + assertEquals("

      Hello Servlet

      ",response.getContent()); + + // test GET with content + request.setMethod("POST"); + request.setContent("
      Some Test Content
      "); + request.setHeader("Content-Type","text/html"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(200,response.getStatus()); + assertEquals("

      Hello Servlet

      Some Test Content
      ",response.getContent()); + + // test redirection + request.setMethod("GET"); + request.setURI("/context"); + request.setContent(null); + response.parse(tester.getResponses(request.generate())); + assertEquals(302,response.getStatus()); + assertEquals("http://tester/context/",response.getHeader("location")); + + // test not found + request.setURI("/context/xxxx"); + response.parse(tester.getResponses(request.generate())); + assertEquals(404,response.getStatus()); + + } + + + /* ------------------------------------------------------------ */ + public void testBigPost() throws Exception + { + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + + String content = "0123456789abcdef"; + content+=content; + content+=content; + content+=content; + content+=content; + content+=content; + content+=content; + content+=content; + content+=content; + content+=content; + content+=content; + content+=content; + content+=content; + content+="!"; + + request.setMethod("POST"); + request.setVersion("HTTP/1.1"); + request.setURI("/context/hello/info"); + request.setHeader("Host","tester"); + request.setHeader("Content-Type","text/plain"); + request.setContent(content); + String r=request.generate(); + r = tester.getResponses(r); + response.parse(r); + assertTrue(response.getMethod()==null); + assertEquals(200,response.getStatus()); + assertEquals("

      Hello Servlet

      "+content,response.getContent()); + + + } + + + /* ------------------------------------------------------------ */ + public void testCharset() + throws Exception + { + byte[] content_iso_8859_1="abcd=1234&AAA=xxx".getBytes("iso8859-1"); + byte[] content_utf_8="abcd=1234&AAA=xxx".getBytes("utf-8"); + byte[] content_utf_16="abcd=1234&AAA=xxx".getBytes("utf-16"); + + String request_iso_8859_1= + "POST /context/servlet/post HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: application/x-www-form-urlencoded\r\n"+ + "Content-Length: "+content_iso_8859_1.length+"\r\n"+ + "\r\n"; + + String request_utf_8= + "POST /context/servlet/post HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n"+ + "Content-Length: "+content_utf_8.length+"\r\n"+ + "\r\n"; + + String request_utf_16= + "POST /context/servlet/post HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: application/x-www-form-urlencoded; charset=utf-16\r\n"+ + "Content-Length: "+content_utf_16.length+"\r\n"+ + "Connection: close\r\n"+ + "\r\n"; + + ByteArrayBuffer out = new ByteArrayBuffer(4096); + out.put(request_iso_8859_1.getBytes("iso8859-1")); + out.put(content_iso_8859_1); + out.put(request_utf_8.getBytes("iso8859-1")); + out.put(content_utf_8); + out.put(request_utf_16.getBytes("iso8859-1")); + out.put(content_utf_16); + + ByteArrayBuffer responses = tester.getResponses(out); + + String expected= + "HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/html;charset=ISO-8859-1\r\n"+ + "Content-Length: 21\r\n"+ + "\r\n"+ + "

      Test Servlet

      "+ + "HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/html;charset=ISO-8859-1\r\n"+ + "Content-Length: 21\r\n"+ + "\r\n"+ + "

      Test Servlet

      "+ + "HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/html;charset=ISO-8859-1\r\n"+ + "Connection: close\r\n"+ + "\r\n"+ + "

      Test Servlet

      "; + + assertEquals(expected,responses.toString()); + } + + + /* ------------------------------------------------------------ */ + public static class HelloServlet extends HttpServlet + { + private static final long serialVersionUID=2779906630657190712L; + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + doGet(request,response); + } + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("text/html"); + response.getWriter().print("

      Hello Servlet

      "); + if (request.getContentLength()>0) + response.getWriter().write(IO.toString(request.getInputStream())); + } + } + + public static class TestServlet extends HttpServlet + { + private static final long serialVersionUID=2779906630657190712L; + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + assertEquals("/context",request.getContextPath()); + assertEquals("/servlet",request.getServletPath()); + assertEquals("/post",request.getPathInfo()); + assertEquals(2,request.getParameterMap().size()); + assertEquals("1234",request.getParameter("abcd")); + assertEquals("xxx",request.getParameter("AAA")); + + response.setContentType("text/html"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().print("

      Test Servlet

      "); + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + assertEquals("/context",request.getContextPath()); + assertEquals("/servlet",request.getServletPath()); + assertEquals("/info",request.getPathInfo()); + assertEquals("query=foo",request.getQueryString()); + assertEquals(1,request.getParameterMap().size()); + assertEquals(1,request.getParameterValues("query").length); + assertEquals("foo",request.getParameter("query")); + + response.setContentType("text/html"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().print("

      Test Servlet

      "); + } + } +} diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml new file mode 100644 index 00000000000..ab2baca08b7 --- /dev/null +++ b/jetty-servlet/pom.xml @@ -0,0 +1,61 @@ + + + + jetty-project + org.eclipse.jetty + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-servlet + Jetty :: Servlet Handling + Jetty Servlet Container + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.servlet + J2SE-1.5 + org.eclipse.jetty.servlet;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + + + junit + junit + test + + + org.eclipse.jetty + jetty-security + ${project.version} + + + diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java new file mode 100644 index 00000000000..a75e1572eef --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -0,0 +1,905 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.List; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.WriterOutputStream; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.InclusiveByteRange; +import org.eclipse.jetty.server.ResourceCache; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.nio.NIOConnector; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.MultiPartOutputStream; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; + + + +/* ------------------------------------------------------------ */ +/** The default servlet. + * This servlet, normally mapped to /, provides the handling for static + * content, OPTION and TRACE methods for the context. + * The following initParameters are supported, these can be set either + * on the servlet itself or as ServletContext initParameters with a prefix + * of org.eclipse.jetty.servlet.Default. : + *
                                                                            
      + *   acceptRanges     If true, range requests and responses are         
      + *                    supported                                         
      + *                                                                      
      + *   dirAllowed       If true, directory listings are returned if no    
      + *                    welcome file is found. Else 403 Forbidden.        
      + *
      + *   redirectWelcome  If true, welcome files are redirected rather than
      + *                    forwarded to.
      + *
      + *   gzip             If set to true, then static content will be served as 
      + *                    gzip content encoded if a matching resource is 
      + *                    found ending with ".gz"
      + *
      + *  resourceBase      Set to replace the context resource base
      + *
      + *  relativeResourceBase    
      + *                    Set with a pathname relative to the base of the
      + *                    servlet context root. Useful for only serving static content out
      + *                    of only specific subdirectories.
      + * 
      + *  aliases           If True, aliases of resources are allowed (eg. symbolic
      + *                    links and caps variations). May bypass security constraints.
      + *                    
      + *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
      + *  maxCachedFileSize The maximum size of a file to cache
      + *  maxCachedFiles    The maximum number of files to cache
      + *  cacheType         Set to "bio", "nio" or "both" to determine the type resource cache. 
      + *                    A bio cached buffer may be used by nio but is not as efficient as an
      + *                    nio buffer.  An nio cached buffer may not be used by bio.    
      + *  
      + *  useFileMappedBuffer 
      + *                    If set to true, it will use mapped file buffer to serve static content
      + *                    when using NIO connector. Setting this value to false means that
      + *                    a direct buffer will be used instead of a mapped file buffer. 
      + *                    By default, this is set to true.
      + *                    
      + *  cacheControl      If set, all static content will have this value set as the cache-control
      + *                    header.
      + *                    
      + * 
      + * 
      + * + * + * + * + */ +public class DefaultServlet extends HttpServlet implements ResourceFactory +{ + private ServletContext _servletContext; + private ContextHandler _contextHandler; + + private boolean _acceptRanges=true; + private boolean _dirAllowed=true; + private boolean _redirectWelcome=false; + private boolean _gzip=true; + + private Resource _resourceBase; + private NIOResourceCache _nioCache; + private ResourceCache _bioCache; + + private MimeTypes _mimeTypes; + private String[] _welcomes; + private boolean _useFileMappedBuffer=false; + private ByteArrayBuffer _cacheControl; + private String _relativeResourceBase; + + + /* ------------------------------------------------------------ */ + public void init() + throws UnavailableException + { + _servletContext=getServletContext(); + ContextHandler.Context scontext=ContextHandler.getCurrentContext(); + if (scontext==null) + _contextHandler=((ContextHandler.Context)_servletContext).getContextHandler(); + else + _contextHandler = ContextHandler.getCurrentContext().getContextHandler(); + + _mimeTypes = _contextHandler.getMimeTypes(); + + _welcomes = _contextHandler.getWelcomeFiles(); + if (_welcomes==null) + _welcomes=new String[] {"index.jsp","index.html"}; + + _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges); + _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed); + _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome); + _gzip=getInitBoolean("gzip",_gzip); + + String aliases=_servletContext.getInitParameter("aliases"); + if (aliases!=null) + _contextHandler.setAliases(Boolean.parseBoolean(aliases)); + + _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer); + + _relativeResourceBase = getInitParameter("relativeResourceBase"); + + String rb=getInitParameter("resourceBase"); + if (rb!=null) + { + if (_relativeResourceBase!=null) + throw new UnavailableException("resourceBase & relativeResourceBase"); + try{_resourceBase=_contextHandler.newResource(rb);} + catch (Exception e) + { + Log.warn(Log.EXCEPTION,e); + throw new UnavailableException(e.toString()); + } + } + + String t=getInitParameter("cacheControl"); + if (t!=null) + _cacheControl=new ByteArrayBuffer(t); + + try + { + String cache_type =getInitParameter("cacheType"); + int max_cache_size=getInitInt("maxCacheSize", -2); + int max_cached_file_size=getInitInt("maxCachedFileSize", -2); + int max_cached_files=getInitInt("maxCachedFiles", -2); + + if (cache_type==null || "nio".equals(cache_type)|| "both".equals(cache_type)) + { + if (max_cache_size==-2 || max_cache_size>0) + { + _nioCache=new NIOResourceCache(_mimeTypes); + _nioCache.setUseFileMappedBuffer(_useFileMappedBuffer); + if (max_cache_size>0) + _nioCache.setMaxCacheSize(max_cache_size); + if (max_cached_file_size>=-1) + _nioCache.setMaxCachedFileSize(max_cached_file_size); + if (max_cached_files>=-1) + _nioCache.setMaxCachedFiles(max_cached_files); + _nioCache.start(); + } + } + if ("bio".equals(cache_type)|| "both".equals(cache_type)) + { + if (max_cache_size==-2 || max_cache_size>0) + { + _bioCache=new ResourceCache(_mimeTypes); + if (max_cache_size>0) + _bioCache.setMaxCacheSize(max_cache_size); + if (max_cached_file_size>=-1) + _bioCache.setMaxCachedFileSize(max_cached_file_size); + if (max_cached_files>=-1) + _bioCache.setMaxCachedFiles(max_cached_files); + _bioCache.start(); + } + } + if (_nioCache==null) + _bioCache=null; + + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION,e); + throw new UnavailableException(e.toString()); + } + + if (Log.isDebugEnabled()) Log.debug("resource base = "+_resourceBase); + } + + /* ------------------------------------------------------------ */ + public String getInitParameter(String name) + { + String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name); + if (value==null) + value=super.getInitParameter(name); + return value; + } + + /* ------------------------------------------------------------ */ + private boolean getInitBoolean(String name, boolean dft) + { + String value=getInitParameter(name); + if (value==null || value.length()==0) + return dft; + return (value.startsWith("t")|| + value.startsWith("T")|| + value.startsWith("y")|| + value.startsWith("Y")|| + value.startsWith("1")); + } + + /* ------------------------------------------------------------ */ + private int getInitInt(String name, int dft) + { + String value=getInitParameter(name); + if (value==null) + value=getInitParameter(name); + if (value!=null && value.length()>0) + return Integer.parseInt(value); + return dft; + } + + /* ------------------------------------------------------------ */ + /** get Resource to serve. + * Map a path to a resource. The default implementation calls + * HttpContext.getResource but derived servlets may provide + * their own mapping. + * @param pathInContext The path to find a resource for. + * @return The resource to serve. + */ + public Resource getResource(String pathInContext) + { + Resource r=null; + if (_relativeResourceBase!=null) + pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext); + + try + { + if (_resourceBase!=null) + r = _resourceBase.addPath(pathInContext); + else + { + URL u = _servletContext.getResource(pathInContext); + r = _contextHandler.newResource(u); + } + + if (Log.isDebugEnabled()) Log.debug("RESOURCE="+r); + } + catch (IOException e) + { + Log.ignore(e); + } + return r; + } + + /* ------------------------------------------------------------ */ + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + String servletPath=null; + String pathInfo=null; + Enumeration reqRanges = null; + Boolean included =request.getAttribute(Dispatcher.__INCLUDE_REQUEST_URI)!=null; + if (included!=null && included.booleanValue()) + { + servletPath=(String)request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH); + pathInfo=(String)request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO); + if (servletPath==null) + { + servletPath=request.getServletPath(); + pathInfo=request.getPathInfo(); + } + } + else + { + included=Boolean.FALSE; + servletPath=request.getServletPath(); + pathInfo=request.getPathInfo(); + + // Is this a range request? + reqRanges = request.getHeaders(HttpHeaders.RANGE); + if (reqRanges!=null && !reqRanges.hasMoreElements()) + reqRanges=null; + } + + String pathInContext=URIUtil.addPaths(servletPath,pathInfo); + boolean endsWithSlash=pathInContext.endsWith(URIUtil.SLASH); + + // Can we gzip this request? + String pathInContextGz=null; + boolean gzip=false; + if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash ) + { + String accept=request.getHeader(HttpHeaders.ACCEPT_ENCODING); + if (accept!=null && accept.indexOf("gzip")>=0) + gzip=true; + } + + // Find the resource and content + Resource resource=null; + HttpContent content=null; + + Connector connector = HttpConnection.getCurrentConnection().getConnector(); + ResourceCache cache=(connector instanceof NIOConnector) ?_nioCache:_bioCache; + try + { + // Try gzipped content first + if (gzip) + { + pathInContextGz=pathInContext+".gz"; + + if (cache==null) + { + resource=getResource(pathInContextGz); + } + else + { + content=cache.lookup(pathInContextGz,this); + + if (content!=null) + resource=content.getResource(); + else + resource=getResource(pathInContextGz); + } + + if (resource==null || !resource.exists()|| resource.isDirectory()) + { + if (cache!=null && content==null) + { + String real_path=_servletContext.getRealPath(pathInContextGz); + if (real_path!=null) + cache.miss(pathInContextGz,_contextHandler.newResource(real_path)); + } + gzip=false; + pathInContextGz=null; + } + } + + // find resource + if (!gzip) + { + if (cache==null) + resource=getResource(pathInContext); + else + { + content=cache.lookup(pathInContext,this); + + if (content!=null) + resource=content.getResource(); + else + resource=getResource(pathInContext); + } + } + + if (Log.isDebugEnabled()) + Log.debug("resource="+resource+(content!=null?" content":"")); + + // Handle resource + if (resource==null || !resource.exists()) + response.sendError(HttpServletResponse.SC_NOT_FOUND); + else if (!resource.isDirectory()) + { + // ensure we have content + if (content==null) + content=new UnCachedContent(resource); + + if (included.booleanValue() || passConditionalHeaders(request,response, resource,content)) + { + if (gzip) + { + response.setHeader(HttpHeaders.CONTENT_ENCODING,"gzip"); + String mt=_servletContext.getMimeType(pathInContext); + if (mt!=null) + response.setContentType(mt); + } + sendData(request,response,included.booleanValue(),resource,content,reqRanges); + } + } + else + { + String welcome=null; + + if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null)) + { + StringBuffer buf=request.getRequestURL(); + synchronized(buf) + { + int param=buf.lastIndexOf(";"); + if (param<0) + buf.append('/'); + else + buf.insert(param,'/'); + String q=request.getQueryString(); + if (q!=null&&q.length()!=0) + { + buf.append('?'); + buf.append(q); + } + response.setContentLength(0); + response.sendRedirect(response.encodeRedirectURL(buf.toString())); + } + } + // else look for a welcome file + else if (null!=(welcome=getWelcomeFile(resource))) + { + String ipath=URIUtil.addPaths(pathInContext,welcome); + if (_redirectWelcome) + { + // Redirect to the index + response.setContentLength(0); + String q=request.getQueryString(); + if (q!=null&&q.length()!=0) + response.sendRedirect(URIUtil.addPaths( _servletContext.getContextPath(),ipath)+"?"+q); + else + response.sendRedirect(URIUtil.addPaths( _servletContext.getContextPath(),ipath)); + } + else + { + // Forward to the index + RequestDispatcher dispatcher=request.getRequestDispatcher(ipath); + if (dispatcher!=null) + { + if (included.booleanValue()) + dispatcher.include(request,response); + else + { + request.setAttribute("org.eclipse.jetty.server.welcome",ipath); + dispatcher.forward(request,response); + } + } + } + } + else + { + content=new UnCachedContent(resource); + if (included.booleanValue() || passConditionalHeaders(request,response, resource,content)) + sendDirectory(request,response,resource,pathInContext.length()>1); + } + } + } + catch(IllegalArgumentException e) + { + Log.warn(Log.EXCEPTION,e); + if(!response.isCommitted()) + response.sendError(500, e.getMessage()); + } + finally + { + if (content!=null) + content.release(); + else if (resource!=null) + resource.release(); + } + + } + + /* ------------------------------------------------------------ */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doGet(request,response); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + } + + /* ------------------------------------------------------------ */ + /** + * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of + * configured {@link #_welcomes welcome files} that existing within the directory referenced by the Resource. + * If the resource is not a directory, or no matching file is found, then null is returned. + * The list of welcome files is read from the {@link ContextHandler} for this servlet, or + * "index.jsp" , "index.html" if that is null. + * @param resource + * @return The name of the matching welcome file. + * @throws IOException + * @throws MalformedURLException + */ + private String getWelcomeFile(Resource resource) throws MalformedURLException, IOException + { + if (!resource.isDirectory() || _welcomes==null) + return null; + + for (int i=0;i<_welcomes.length;i++) + { + Resource welcome=resource.addPath(_welcomes[i]); + if (welcome.exists()) + return _welcomes[i]; + } + + return null; + } + + /* ------------------------------------------------------------ */ + /* Check modification date headers. + */ + protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content) + throws IOException + { + try + { + if (!request.getMethod().equals(HttpMethods.HEAD) ) + { + String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE); + if (ifms!=null) + { + if (content!=null) + { + Buffer mdlm=content.getLastModified(); + if (mdlm!=null) + { + if (ifms.equals(mdlm.toString())) + { + response.reset(); + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + response.flushBuffer(); + return false; + } + } + } + + long ifmsl=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE); + if (ifmsl!=-1) + { + if (resource.lastModified()/1000 <= ifmsl/1000) + { + response.reset(); + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + response.flushBuffer(); + return false; + } + } + } + + // Parse the if[un]modified dates and compare to resource + long date=request.getDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE); + + if (date!=-1) + { + if (resource.lastModified()/1000 > date/1000) + { + response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); + return false; + } + } + + } + } + catch(IllegalArgumentException iae) + { + if(!response.isCommitted()) + response.sendError(400, iae.getMessage()); + throw iae; + } + return true; + } + + + /* ------------------------------------------------------------------- */ + protected void sendDirectory(HttpServletRequest request, + HttpServletResponse response, + Resource resource, + boolean parent) + throws IOException + { + if (!_dirAllowed) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + byte[] data=null; + String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH); + String dir = resource.getListHTML(base,parent); + if (dir==null) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, + "No directory"); + return; + } + + data=dir.getBytes("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + response.setContentLength(data.length); + response.getOutputStream().write(data); + } + + /* ------------------------------------------------------------ */ + protected void sendData(HttpServletRequest request, + HttpServletResponse response, + boolean include, + Resource resource, + HttpContent content, + Enumeration reqRanges) + throws IOException + { + long content_length=resource.length(); + + // Get the output stream (or writer) + OutputStream out =null; + try{out = response.getOutputStream();} + catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());} + + if ( reqRanges == null || !reqRanges.hasMoreElements()) + { + // if there were no ranges, send entire entity + if (include) + { + resource.writeTo(out,0,content_length); + } + else + { + // See if a short direct method can be used? + if (out instanceof HttpConnection.Output) + { + if (_cacheControl!=null) + { + if (response instanceof Response) + ((Response)response).getHttpFields().put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl); + else + response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString()); + } + ((HttpConnection.Output)out).sendContent(content); + } + else + { + + // Write content normally + writeHeaders(response,content,content_length); + resource.writeTo(out,0,content_length); + } + } + } + else + { + // Parse the satisfiable ranges + List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length); + + // if there are no satisfiable ranges, send 416 response + if (ranges==null || ranges.size()==0) + { + writeHeaders(response, content, content_length); + response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); + response.setHeader(HttpHeaders.CONTENT_RANGE, + InclusiveByteRange.to416HeaderRangeString(content_length)); + resource.writeTo(out,0,content_length); + return; + } + + + // if there is only a single valid range (must be satisfiable + // since were here now), send that range with a 216 response + if ( ranges.size()== 1) + { + InclusiveByteRange singleSatisfiableRange = + (InclusiveByteRange)ranges.get(0); + long singleLength = singleSatisfiableRange.getSize(content_length); + writeHeaders(response,content,singleLength ); + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + response.setHeader(HttpHeaders.CONTENT_RANGE, + singleSatisfiableRange.toHeaderRangeString(content_length)); + resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength); + return; + } + + + // multiple non-overlapping valid ranges cause a multipart + // 216 response which does not require an overall + // content-length header + // + writeHeaders(response,content,-1); + String mimetype=content.getContentType().toString(); + MultiPartOutputStream multi = new MultiPartOutputStream(out); + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + + // If the request has a "Request-Range" header then we need to + // send an old style multipart/x-byteranges Content-Type. This + // keeps Netscape and acrobat happy. This is what Apache does. + String ctp; + if (request.getHeader(HttpHeaders.REQUEST_RANGE)!=null) + ctp = "multipart/x-byteranges; boundary="; + else + ctp = "multipart/byteranges; boundary="; + response.setContentType(ctp+multi.getBoundary()); + + InputStream in=resource.getInputStream(); + long pos=0; + + for (int i=0;i=0) + response.setDateHeader(HttpHeaders.LAST_MODIFIED,lml); + + if (count != -1) + { + if (count to) + throw new IllegalArgumentException("from>to"); + + _from = from; + _to = to; + _uri = uri; + } + + boolean isInRange(int value) + { + if ((value >= _from) && (value <= _to)) + { + return true; + } + + return false; + } + + String getUri() + { + return _uri; + } + + public String toString() + { + return "from: " + _from + ",to: " + _to + ",uri: " + _uri; + } + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java new file mode 100644 index 00000000000..b8557634102 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java @@ -0,0 +1,194 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.util.EnumSet; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.FilterRegistration; + +import org.eclipse.jetty.util.log.Log; + +/* --------------------------------------------------------------------- */ +/** + * + */ +public class FilterHolder extends Holder +{ + /* ------------------------------------------------------------ */ + private transient Filter _filter; + private transient Config _config; + + /* ---------------------------------------------------------------- */ + /** Constructor for Serialization. + */ + public FilterHolder() + { + } + + /* ---------------------------------------------------------------- */ + /** Constructor for Serialization. + */ + public FilterHolder(Class filter) + { + super (filter); + } + + /* ---------------------------------------------------------------- */ + /** Constructor for existing filter. + */ + public FilterHolder(Filter filter) + { + setFilter(filter); + } + + /* ------------------------------------------------------------ */ + public void doStart() + throws Exception + { + super.doStart(); + + if (!javax.servlet.Filter.class + .isAssignableFrom(_class)) + { + String msg = _class+" is not a javax.servlet.Filter"; + super.stop(); + throw new IllegalStateException(msg); + } + + if (_filter==null) + _filter=(Filter)newInstance(); + + _filter = getServletHandler().customizeFilter(_filter); + + _config=new Config(); + _filter.init(_config); + } + + /* ------------------------------------------------------------ */ + public void doStop() + { + if (_filter!=null) + { + try + { + destroyInstance(_filter); + } + catch (Exception e) + { + Log.warn(e); + } + } + if (!_extInstance) + _filter=null; + + _config=null; + super.doStop(); + } + + /* ------------------------------------------------------------ */ + public void destroyInstance (Object o) + throws Exception + { + if (o==null) + return; + Filter f = (Filter)o; + f.destroy(); + getServletHandler().customizeFilterDestroy(f); + } + + /* ------------------------------------------------------------ */ + public synchronized void setFilter(Filter filter) + { + _filter=filter; + _extInstance=true; + setHeldClass(filter.getClass()); + if (getName()==null) + setName(filter.getClass().getName()); + } + + /* ------------------------------------------------------------ */ + public Filter getFilter() + { + return _filter; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return getName(); + } + + + public FilterRegistration getRegistration() + { + return new Registration(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected class Registration extends HolderRegistration implements FilterRegistration + { + /* ------------------------------------------------------------ */ + public boolean addMappingForServletNames(EnumSet dispatcherTypes, boolean isMatchAfter, String... servletNames) + { + illegalStateIfContextStarted(); + FilterMapping mapping = new FilterMapping(); + mapping.setFilterHolder(FilterHolder.this); + mapping.setServletNames(servletNames); + mapping.setDispatcherTypes(dispatcherTypes); + if (isMatchAfter) + _servletHandler.addFilterMapping(mapping); + else + _servletHandler.prependFilterMapping(mapping); + + return true; + } + + public boolean addMappingForUrlPatterns(EnumSet dispatcherTypes, boolean isMatchAfter, String... urlPatterns) + { + illegalStateIfContextStarted(); + FilterMapping mapping = new FilterMapping(); + mapping.setFilterHolder(FilterHolder.this); + mapping.setPathSpecs(urlPatterns); + mapping.setDispatcherTypes(dispatcherTypes); + if (isMatchAfter) + _servletHandler.addFilterMapping(mapping); + else + _servletHandler.prependFilterMapping(mapping); + return true; + } + + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + class Config extends HolderConfig implements FilterConfig + { + /* ------------------------------------------------------------ */ + public String getFilterName() + { + return _name; + } + } +} + + + + + diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java new file mode 100644 index 00000000000..169067353dd --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java @@ -0,0 +1,280 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.util.Arrays; +import java.util.EnumSet; + +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.server.Handler; + + +public class FilterMapping +{ + /** Dispatch types */ + public static final int DEFAULT=0; + public static final int REQUEST=1; + public static final int FORWARD=2; + public static final int INCLUDE=4; + public static final int ERROR=8; + public static final int ASYNC=16; + public static final int ALL=31; + + + /* ------------------------------------------------------------ */ + /** Dispatch type from name + */ + public static int dispatch(String type) + { + if ("request".equalsIgnoreCase(type)) + return REQUEST; + if ("forward".equalsIgnoreCase(type)) + return FORWARD; + if ("include".equalsIgnoreCase(type)) + return INCLUDE; + if ("error".equalsIgnoreCase(type)) + return ERROR; + if ("async".equalsIgnoreCase(type)) + return ASYNC; + throw new IllegalArgumentException(type); + } + + /* ------------------------------------------------------------ */ + /** Dispatch type from name + */ + public static int dispatch(DispatcherType type) + { + switch(type) + { + case REQUEST: + return REQUEST; + case ASYNC: + return ASYNC; + case FORWARD: + return FORWARD; + case INCLUDE: + return INCLUDE; + case ERROR: + return ERROR; + } + throw new IllegalArgumentException(type.toString()); + } + + + /* ------------------------------------------------------------ */ + /** Dispatch type from name + */ + public static DispatcherType dispatch(int type) + { + switch(type) + { + case REQUEST: + return DispatcherType.REQUEST; + case ASYNC: + return DispatcherType.ASYNC; + case FORWARD: + return DispatcherType.FORWARD; + case INCLUDE: + return DispatcherType.INCLUDE; + case ERROR: + return DispatcherType.ERROR; + } + throw new IllegalArgumentException(""+type); + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + + + private int _dispatches=DEFAULT; + private String _filterName; + private transient FilterHolder _holder; + private String[] _pathSpecs; + private String[] _servletNames; + + /* ------------------------------------------------------------ */ + public FilterMapping() + {} + + /* ------------------------------------------------------------ */ + /** Check if this filter applies to a path. + * @param path The path to check or null to just check type + * @param type The type of request: __REQUEST,__FORWARD,__INCLUDE, __ASYNC or __ERROR. + * @return True if this filter applies + */ + boolean appliesTo(String path, int type) + { + if (appliesTo(type)) + { + for (int i=0;i<_pathSpecs.length;i++) + if (_pathSpecs[i]!=null && PathMap.match(_pathSpecs[i], path,true)) + return true; + } + + return false; + } + + /* ------------------------------------------------------------ */ + /** Check if this filter applies to a particular dispatch type. + * @param type The type of request: + * {@link Handler#REQUEST}, {@link Handler#FORWARD}, {@link Handler#INCLUDE} or {@link Handler#ERROR}. + * @return true if this filter applies + */ + boolean appliesTo(int type) + { + if (_dispatches==0) + return type==REQUEST || type==ASYNC && _holder.isAsyncSupported(); + return (_dispatches&type)!=0; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the dispatches. + */ + public int getDispatches() + { + return _dispatches; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the filterName. + */ + public String getFilterName() + { + return _filterName; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the holder. + */ + FilterHolder getFilterHolder() + { + return _holder; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the pathSpec. + */ + public String[] getPathSpecs() + { + return _pathSpecs; + } + + /* ------------------------------------------------------------ */ + public void setDispatcherTypes(EnumSet dispatcherTypes) + { + _dispatches=DEFAULT; + if (dispatcherTypes.contains(DispatcherType.ERROR)) + _dispatches|=ERROR; + if (dispatcherTypes.contains(DispatcherType.FORWARD)) + _dispatches|=FORWARD; + if (dispatcherTypes.contains(DispatcherType.INCLUDE)) + _dispatches|=INCLUDE; + if (dispatcherTypes.contains(DispatcherType.REQUEST)) + _dispatches|=REQUEST; + } + + /* ------------------------------------------------------------ */ + /** + * @param dispatches The dispatches to set. + * @see Handler#DEFAULT + * @see Handler#REQUEST + * @see Handler#ERROR + * @see Handler#FORWARD + * @see Handler#INCLUDE + */ + public void setDispatches(int dispatches) + { + _dispatches = dispatches; + } + + /* ------------------------------------------------------------ */ + /** + * @param filterName The filterName to set. + */ + public void setFilterName(String filterName) + { + _filterName = filterName; + } + + /* ------------------------------------------------------------ */ + /** + * @param holder The holder to set. + */ + void setFilterHolder(FilterHolder holder) + { + _holder = holder; + setFilterName(holder.getName()); + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpecs The Path specifications to which this filter should be mapped. + */ + public void setPathSpecs(String[] pathSpecs) + { + _pathSpecs = pathSpecs; + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec The pathSpec to set. + */ + public void setPathSpec(String pathSpec) + { + _pathSpecs = new String[]{pathSpec}; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletName. + */ + public String[] getServletNames() + { + return _servletNames; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletNames Maps the {@link #setFilterName(String) named filter} to multiple servlets + * @see #setServletName + */ + public void setServletNames(String[] servletNames) + { + _servletNames = servletNames; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletName Maps the {@link #setFilterName(String) named filter} to a single servlet + * @see #setServletNames + */ + public void setServletName(String servletName) + { + _servletNames = new String[]{servletName}; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return "(F="+_filterName+","+(_pathSpecs==null?"[]":Arrays.asList(_pathSpecs).toString())+","+(_servletNames==null?"[]":Arrays.asList(_servletNames).toString())+","+_dispatches+")"; + } + +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java new file mode 100644 index 00000000000..b63267baf8a --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java @@ -0,0 +1,379 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.UnavailableException; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + + +/* --------------------------------------------------------------------- */ +/** + * + */ +public class Holder extends AbstractLifeCycle implements Serializable +{ + protected transient Class _class; + protected String _className; + protected String _displayName; + protected Map _initParams; + protected boolean _extInstance; + protected boolean _asyncSupported; + protected AttributesMap _initAttributes; + + /* ---------------------------------------------------------------- */ + protected String _name; + protected ServletHandler _servletHandler; + + protected Holder() + {} + + /* ---------------------------------------------------------------- */ + protected Holder(Class held) + { + _class=held; + if (held!=null) + { + _className=held.getName(); + _name=held.getName()+"-"+this.hashCode(); + } + } + + /* ------------------------------------------------------------ */ + public void doStart() + throws Exception + { + //if no class already loaded and no classname, make servlet permanently unavailable + if (_class==null && (_className==null || _className.equals(""))) + throw new UnavailableException("No class for Servlet or Filter", -1); + + //try to load class + if (_class==null) + { + try + { + _class=Loader.loadClass(Holder.class, _className); + if(Log.isDebugEnabled())Log.debug("Holding {}",_class); + } + catch (Exception e) + { + Log.warn(e); + throw new UnavailableException(e.getMessage(), -1); + } + } + } + + /* ------------------------------------------------------------ */ + public void doStop() + { + if (!_extInstance) + _class=null; + } + + /* ------------------------------------------------------------ */ + public String getClassName() + { + return _className; + } + + /* ------------------------------------------------------------ */ + public Class getHeldClass() + { + return _class; + } + + /* ------------------------------------------------------------ */ + public String getDisplayName() + { + return _displayName; + } + + /* ---------------------------------------------------------------- */ + public String getInitParameter(String param) + { + if (_initParams==null) + return null; + return (String)_initParams.get(param); + } + + /* ------------------------------------------------------------ */ + public Enumeration getInitParameterNames() + { + if (_initParams==null) + return Collections.enumeration(Collections.EMPTY_LIST); + return Collections.enumeration(_initParams.keySet()); + } + + /* ---------------------------------------------------------------- */ + public Map getInitParameters() + { + return _initParams; + } + + /* ------------------------------------------------------------ */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletHandler. + */ + public ServletHandler getServletHandler() + { + return _servletHandler; + } + + /* ------------------------------------------------------------ */ + public synchronized Object newInstance() + throws InstantiationException, + IllegalAccessException + { + if (_class==null) + throw new InstantiationException("!"+_className); + return _class.newInstance(); + } + + /* ------------------------------------------------------------ */ + public void destroyInstance(Object instance) + throws Exception + { + } + + /* ------------------------------------------------------------ */ + /** + * @param className The className to set. + */ + public void setClassName(String className) + { + _className = className; + _class=null; + } + + /* ------------------------------------------------------------ */ + /** + * @param className The className to set. + */ + public void setHeldClass(Class held) + { + _class=held; + _className = held!=null?held.getName():null; + } + + /* ------------------------------------------------------------ */ + public void setDisplayName(String name) + { + _displayName=name; + } + + /* ------------------------------------------------------------ */ + public void setInitParameter(String param,String value) + { + if (_initParams==null) + _initParams=new HashMap(3); + _initParams.put(param,value); + } + + /* ---------------------------------------------------------------- */ + public void setInitParameters(Map map) + { + _initParams=map; + } + + /* ------------------------------------------------------------ */ + /** + * The name is a primary key for the held object. + * Ensure that the name is set BEFORE adding a Holder + * (eg ServletHolder or FilterHolder) to a ServletHandler. + * @param name The name to set. + */ + public void setName(String name) + { + _name = name; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletHandler The {@link ServletHandler} that will handle requests dispatched to this servlet. + */ + public void setServletHandler(ServletHandler servletHandler) + { + _servletHandler = servletHandler; + } + + /* ------------------------------------------------------------ */ + public void setAsyncSupported(boolean suspendable) + { + _asyncSupported=suspendable; + } + + /* ------------------------------------------------------------ */ + public boolean isAsyncSupported() + { + return _asyncSupported; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return _name; + } + + /* ------------------------------------------------------------ */ + protected void illegalStateIfContextStarted() + { + if (_servletHandler!=null) + { + ContextHandler.Context context=(ContextHandler.Context)_servletHandler.getServletContext(); + if (context!=null && context.getContextHandler().isStarted()) + throw new IllegalStateException("Started"); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected class HolderConfig + { + + /* -------------------------------------------------------- */ + public ServletContext getServletContext() + { + return _servletHandler.getServletContext(); + } + + /* -------------------------------------------------------- */ + public String getInitParameter(String param) + { + return Holder.this.getInitParameter(param); + } + + /* -------------------------------------------------------- */ + public Enumeration getInitParameterNames() + { + return Holder.this.getInitParameterNames(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletConfig#getInitAttribute(java.lang.String) + */ + public Object getInitAttribute(String name) + { + return (Holder.this._initAttributes==null)?null:Holder.this._initAttributes.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletConfig#getInitAttributeNames() + */ + public Iterable getInitAttributeNames() + { + if (Holder.this._initAttributes!=null) + return Holder.this._initAttributes.keySet(); + return Collections.emptySet(); + } + + } + + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + protected class HolderRegistration + { + public boolean setAsyncSupported(boolean isAsyncSupported) + { + illegalStateIfContextStarted(); + Holder.this.setAsyncSupported(isAsyncSupported); + return true; + } + + public boolean setDescription(String description) + { + return true; + } + + public boolean setInitParameter(String name, String value) + { + illegalStateIfContextStarted(); + if (Holder.this.getInitParameter(name)!=null) + return false; + Holder.this.setInitParameter(name,value); + return true; + } + + public boolean setInitParameters(Map initParameters) + { + illegalStateIfContextStarted(); + for (String name : initParameters.keySet()) + if (Holder.this.getInitParameter(name)!=null) + return false; + Holder.this.setInitParameters(initParameters); + return true; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletRegistration#setInitAttribute(java.lang.String, java.lang.Object) + */ + public boolean setInitAttribute(String name, Object value) + { + illegalStateIfContextStarted(); + if (_initAttributes==null) + _initAttributes=new AttributesMap(); + else if (_initAttributes.getAttribute(name)!=null) + return false; + _initAttributes.setAttribute(name,value); + return true; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletRegistration#setInitAttributes(java.util.Map) + */ + public boolean setInitAttributes(Map initAttributes) + { + illegalStateIfContextStarted(); + if (_initAttributes==null) + _initAttributes=new AttributesMap(); + else + { + for (String name : initAttributes.keySet()) + if (_initAttributes.getAttribute(name)!=null) + return false; + } + for (String name : initAttributes.keySet()) + _initAttributes.setAttribute(name,initAttributes.get(name)); + + return true; + }; + } +} + + + + + diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java new file mode 100644 index 00000000000..2b0175f8978 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java @@ -0,0 +1,303 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Dynamic Servlet Invoker. + * This servlet invokes anonymous servlets that have not been defined + * in the web.xml or by other means. The first element of the pathInfo + * of a request passed to the envoker is treated as a servlet name for + * an existing servlet, or as a class name of a new servlet. + * This servlet is normally mapped to /servlet/* + * This servlet support the following initParams: + *
                                                                           
      + *  nonContextServlets       If false, the invoker can only load        
      + *                           servlets from the contexts classloader.    
      + *                           This is false by default and setting this  
      + *                           to true may have security implications.    
      + *                                                                      
      + *  verbose                  If true, log dynamic loads                 
      + *                                                                      
      + *  *                        All other parameters are copied to the     
      + *                           each dynamic servlet as init parameters    
      + * 
      + * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $ + * + */ +public class Invoker extends HttpServlet +{ + + private ContextHandler _contextHandler; + private ServletHandler _servletHandler; + private Map.Entry _invokerEntry; + private Map _parameters; + private boolean _nonContextServlets; + private boolean _verbose; + + /* ------------------------------------------------------------ */ + public void init() + { + ServletContext config=getServletContext(); + _contextHandler=((ContextHandler.Context)config).getContextHandler(); + + Handler handler=_contextHandler.getHandler(); + while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper)) + handler=((HandlerWrapper)handler).getHandler(); + _servletHandler = (ServletHandler)handler; + Enumeration e = getInitParameterNames(); + while(e.hasMoreElements()) + { + String param=(String)e.nextElement(); + String value=getInitParameter(param); + String lvalue=value.toLowerCase(); + if ("nonContextServlets".equals(param)) + { + _nonContextServlets=value.length()>0 && lvalue.startsWith("t"); + } + if ("verbose".equals(param)) + { + _verbose=value.length()>0 && lvalue.startsWith("t"); + } + else + { + if (_parameters==null) + _parameters=new HashMap(); + _parameters.put(param,value); + } + } + } + + /* ------------------------------------------------------------ */ + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + // Get the requested path and info + boolean included=false; + String servlet_path=(String)request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH); + if (servlet_path==null) + servlet_path=request.getServletPath(); + else + included=true; + String path_info = (String)request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO); + if (path_info==null) + path_info=request.getPathInfo(); + + // Get the servlet class + String servlet = path_info; + if (servlet==null || servlet.length()<=1 ) + { + response.sendError(404); + return; + } + + + int i0=servlet.charAt(0)=='/'?1:0; + int i1=servlet.indexOf('/',i0); + servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1); + + // look for a named holder + ServletHolder[] holders = _servletHandler.getServlets(); + ServletHolder holder = getHolder (holders, servlet); + + if (holder!=null) + { + // Found a named servlet (from a user's web.xml file) so + // now we add a mapping for it + Log.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*"); + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(servlet); + mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*"); + _servletHandler.setServletMappings((ServletMapping[])LazyList.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class)); + } + else + { + // look for a class mapping + if (servlet.endsWith(".class")) + servlet=servlet.substring(0,servlet.length()-6); + if (servlet==null || servlet.length()==0) + { + response.sendError(404); + return; + } + + synchronized(_servletHandler) + { + // find the entry for the invoker (me) + _invokerEntry=_servletHandler.getHolderEntry(servlet_path); + + // Check for existing mapping (avoid threaded race). + String path=URIUtil.addPaths(servlet_path,servlet); + Map.Entry entry = _servletHandler.getHolderEntry(path); + + if (entry!=null && !entry.equals(_invokerEntry)) + { + // Use the holder + holder=(ServletHolder)entry.getValue(); + } + else + { + // Make a holder + Log.debug("Making new servlet="+servlet+" with path="+path+"/*"); + holder=_servletHandler.addServletWithMapping(servlet, path+"/*"); + + if (_parameters!=null) + holder.setInitParameters(_parameters); + + try {holder.start();} + catch (Exception e) + { + Log.debug(e); + throw new UnavailableException(e.toString()); + } + + // Check it is from an allowable classloader + if (!_nonContextServlets) + { + Object s=holder.getServlet(); + + if (_contextHandler.getClassLoader()!= + s.getClass().getClassLoader()) + { + try + { + holder.stop(); + } + catch (Exception e) + { + Log.ignore(e); + } + + Log.warn("Dynamic servlet "+s+ + " not loaded from context "+ + request.getContextPath()); + throw new UnavailableException("Not in context"); + } + } + + if (_verbose) + Log.debug("Dynamic load '"+servlet+"' at "+path); + } + } + } + + if (holder!=null) + { + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + holder.handle(base_request, + new InvokedRequest(request,included,servlet,servlet_path,path_info), + response); + } + else + { + Log.info("Can't find holder for servlet: "+servlet); + response.sendError(404); + } + + + } + + /* ------------------------------------------------------------ */ + class InvokedRequest extends HttpServletRequestWrapper + { + String _servletPath; + String _pathInfo; + boolean _included; + + /* ------------------------------------------------------------ */ + InvokedRequest(HttpServletRequest request, + boolean included, + String name, + String servletPath, + String pathInfo) + { + super(request); + _included=included; + _servletPath=URIUtil.addPaths(servletPath,name); + _pathInfo=pathInfo.substring(name.length()+1); + if (_pathInfo.length()==0) + _pathInfo=null; + } + + /* ------------------------------------------------------------ */ + public String getServletPath() + { + if (_included) + return super.getServletPath(); + return _servletPath; + } + + /* ------------------------------------------------------------ */ + public String getPathInfo() + { + if (_included) + return super.getPathInfo(); + return _pathInfo; + } + + /* ------------------------------------------------------------ */ + public Object getAttribute(String name) + { + if (_included) + { + if (name.equals(Dispatcher.__INCLUDE_REQUEST_URI)) + return URIUtil.addPaths(URIUtil.addPaths(getContextPath(),_servletPath),_pathInfo); + if (name.equals(Dispatcher.__INCLUDE_PATH_INFO)) + return _pathInfo; + if (name.equals(Dispatcher.__INCLUDE_SERVLET_PATH)) + return _servletPath; + } + return super.getAttribute(name); + } + } + + + private ServletHolder getHolder(ServletHolder[] holders, String servlet) + { + if (holders == null) + return null; + + ServletHolder holder = null; + for (int i=0; holder==null && i + * new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY); + *
      + *

      + * This class should have been called ServletContext, but this would have + * cause confusion with {@link ServletContext}. + */ +public class ServletContextHandler extends ContextHandler +{ + public final static int SESSIONS=1; + public final static int SECURITY=2; + public final static int NO_SESSIONS=0; + public final static int NO_SECURITY=0; + + protected Class _defaultSecurityHandlerClass=org.eclipse.jetty.security.ConstraintSecurityHandler.class; + protected SessionHandler _sessionHandler; + protected SecurityHandler _securityHandler; + protected ServletHandler _servletHandler; + protected int _options; + + /* ------------------------------------------------------------ */ + public ServletContextHandler() + { + this(null,null,null,null,null); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(int options) + { + this(null,null,options); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, String contextPath) + { + this(parent,contextPath,null,null,null,null); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, String contextPath, int options) + { + this(parent,contextPath,null,null,null,null); + _options=options; + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, String contextPath, boolean sessions, boolean security) + { + this(parent,contextPath,(sessions?SESSIONS:0)|(security?SECURITY:0)); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) + { + this(parent,null,sessionHandler,securityHandler,servletHandler,errorHandler); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) + { + super((ContextHandler.Context)null); + _scontext = new Context(); + _sessionHandler = sessionHandler; + _securityHandler = securityHandler; + _servletHandler = servletHandler; + + if (errorHandler!=null) + setErrorHandler(errorHandler); + + if (contextPath!=null) + setContextPath(contextPath); + + if (parent!=null) + parent.addHandler(this); + } + + + /* ------------------------------------------------------------ */ + /** Get the defaultSecurityHandlerClass. + * @return the defaultSecurityHandlerClass + */ + public Class getDefaultSecurityHandlerClass() + { + return _defaultSecurityHandlerClass; + } + + /* ------------------------------------------------------------ */ + /** Set the defaultSecurityHandlerClass. + * @param defaultSecurityHandlerClass the defaultSecurityHandlerClass to set + */ + public void setDefaultSecurityHandlerClass(Class defaultSecurityHandlerClass) + { + _defaultSecurityHandlerClass = defaultSecurityHandlerClass; + } + + /* ------------------------------------------------------------ */ + protected SessionHandler newSessionHandler() + { + return new SessionHandler(); + } + + /* ------------------------------------------------------------ */ + protected SecurityHandler newSecurityHandler() + { + try + { + return (SecurityHandler)_defaultSecurityHandlerClass.newInstance(); + } + catch(Exception e) + { + throw new IllegalStateException(e); + } + } + + /* ------------------------------------------------------------ */ + protected ServletHandler newServletHandler() + { + return new ServletHandler(); + } + + /* ------------------------------------------------------------ */ + /** + * Finish constructing handlers and link them together. + * + * @see org.eclipse.jetty.server.handler.ContextHandler#startContext() + */ + protected void startContext() throws Exception + { + // force creation of missing handlers. + getSessionHandler(); + getSecurityHandler(); + getServletHandler(); + + Handler handler = _servletHandler; + if (_securityHandler!=null) + { + _securityHandler.setHandler(handler); + handler=_securityHandler; + } + + if (_sessionHandler!=null) + { + _sessionHandler.setHandler(handler); + handler=_sessionHandler; + } + + setHandler(handler); + + super.startContext(); + + // OK to Initialize servlet handler now + if (_servletHandler != null && _servletHandler.isStarted()) + _servletHandler.initialize(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the securityHandler. + */ + public SecurityHandler getSecurityHandler() + { + if (_securityHandler==null && (_options&SECURITY)!=0 && !isStarted()) + _securityHandler=newSecurityHandler(); + + return _securityHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletHandler. + */ + public ServletHandler getServletHandler() + { + if (_servletHandler==null && !isStarted()) + _servletHandler=newServletHandler(); + return _servletHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionHandler. + */ + public SessionHandler getSessionHandler() + { + if (_sessionHandler==null && (_options&SESSIONS)!=0 && !isStarted()) + _sessionHandler=newSessionHandler(); + return _sessionHandler; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + */ + public ServletHolder addServlet(String className,String pathSpec) + { + return getServletHandler().addServletWithMapping(className, pathSpec); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + */ + public ServletHolder addServlet(Class servlet,String pathSpec) + { + return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + */ + public void addServlet(ServletHolder servlet,String pathSpec) + { + getServletHandler().addServletWithMapping(servlet, pathSpec); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a filter + */ + public void addFilter(FilterHolder holder,String pathSpec,int dispatches) + { + getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** convenience method to add a filter + */ + public FilterHolder addFilter(Class filterClass,String pathSpec,int dispatches) + { + return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** convenience method to add a filter + */ + public FilterHolder addFilter(String filterClass,String pathSpec,int dispatches) + { + return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** + * @param sessionHandler The sessionHandler to set. + */ + public void setSessionHandler(SessionHandler sessionHandler) + { + if (isStarted()) + throw new IllegalStateException("STARTED"); + + _sessionHandler = sessionHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @param securityHandler The {@link org.eclipse.jetty.server.handler.SecurityHandler} to set on this context. + */ + public void setSecurityHandler(SecurityHandler securityHandler) + { + if (isStarted()) + throw new IllegalStateException("STARTED"); + + _securityHandler = securityHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletHandler The servletHandler to set. + */ + public void setServletHandler(ServletHandler servletHandler) + { + if (isStarted()) + throw new IllegalStateException("STARTED"); + + _servletHandler = servletHandler; + } + + /* ------------------------------------------------------------ */ + public class Context extends ContextHandler.Context + { + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String) + */ + public RequestDispatcher getNamedDispatcher(String name) + { + ContextHandler context=org.eclipse.jetty.servlet.ServletContextHandler.this; + if (_servletHandler==null || _servletHandler.getServlet(name)==null) + return null; + return new Dispatcher(context, name); + } + + + /* ------------------------------------------------------------ */ + public void addFilterMappingForServletNames(String filterName, EnumSet dispatcherTypes, boolean isMatchAfter, String... servletNames) + { + if (isStarted()) + throw new IllegalStateException(); + ServletHandler handler = ServletContextHandler.this.getServletHandler(); + FilterMapping mapping = new FilterMapping(); + mapping.setFilterName(filterName); + mapping.setServletNames(servletNames); + mapping.setDispatcherTypes(dispatcherTypes); + handler.addFilterMapping(mapping); + } + + /* ------------------------------------------------------------ */ + public void addServletMapping(String servletName, String[] urlPatterns) + { + if (isStarted()) + throw new IllegalStateException(); + ServletHandler handler = ServletContextHandler.this.getServletHandler(); + ServletHolder holder= handler.newServletHolder(); + holder.setName(servletName); + handler.addServlet(holder); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addFilter(java.lang.String, java.lang.Class) + */ + public FilterRegistration addFilter(String filterName, Class filterClass) + { + if (isStarted()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final FilterHolder holder= handler.newFilterHolder(); + holder.setName(filterName); + holder.setHeldClass(filterClass); + handler.addFilter(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addFilter(java.lang.String, java.lang.String) + */ + public FilterRegistration addFilter(String filterName, String className) + { + if (isStarted()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final FilterHolder holder= handler.newFilterHolder(); + holder.setName(filterName); + holder.setClassName(className); + handler.addFilter(holder); + return holder.getRegistration(); + } + + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addFilter(java.lang.String, javax.servlet.Filter) + */ + public FilterRegistration addFilter(String filterName, Filter filter) + { + if (isStarted()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final FilterHolder holder= handler.newFilterHolder(); + holder.setName(filterName); + holder.setFilter(filter); + handler.addFilter(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.Class) + */ + public ServletRegistration addServlet(String servletName, Class servletClass) + { + if (!isStarting()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final ServletHolder holder= handler.newServletHolder(); + holder.setName(servletName); + holder.setHeldClass(servletClass); + handler.addServlet(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.String) + */ + public ServletRegistration addServlet(String servletName, String className) + { + if (!isStarting()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final ServletHolder holder= handler.newServletHolder(); + holder.setName(servletName); + holder.setClassName(className); + handler.addServlet(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, javax.servlet.Servlet) + */ + public ServletRegistration addServlet(String servletName, Servlet servlet) + { + if (!isStarting()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final ServletHolder holder= handler.newServletHolder(); + holder.setName(servletName); + holder.setServlet(servlet); + handler.addServlet(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#findFilterRegistration(java.lang.String) + */ + public FilterRegistration findFilterRegistration(String filterName) + { + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final FilterHolder holder=handler.getFilter(filterName); + return (holder==null)?null:holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#findServletRegistration(java.lang.String) + */ + public ServletRegistration findServletRegistration(String servletName) + { + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final ServletHolder holder=handler.getServlet(servletName); + return (holder==null)?null:holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#getDefaultSessionTrackingModes() + */ + public EnumSet getDefaultSessionTrackingModes() + { + return SessionHandler.DEFAULT_TRACKING; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#getEffectiveSessionTrackingModes() + */ + public EnumSet getEffectiveSessionTrackingModes() + { + Log.warn("Not Implemented"); + return null; + } + + + + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java new file mode 100644 index 00000000000..6945e29ada1 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -0,0 +1,1370 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletResponse; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RetryRequest; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiException; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + + +/* --------------------------------------------------------------------- */ +/** Servlet HttpHandler. + * This handler maps requests to servlets that implement the + * javax.servlet.http.HttpServlet API. + *

      + * This handler does not implement the full J2EE features and is intended to + * be used when a full web application is not required. Specifically filters + * and request wrapping are not supported. + * + * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()} + * method must be called manually after start(). + * + * @see org.eclipse.jetty.webapp.WebAppContext + * + */ +public class ServletHandler extends AbstractHandler +{ + /* ------------------------------------------------------------ */ + public static final String __DEFAULT_SERVLET="default"; + + /* ------------------------------------------------------------ */ + private ContextHandler _contextHandler; + private ContextHandler.Context _servletContext; + private FilterHolder[] _filters; + private FilterMapping[] _filterMappings; + private boolean _filterChainsCached=true; + private int _maxFilterChainsCacheSize=1000; + private boolean _startWithUnavailable=true; + private IdentityService _identityService; + + private ServletHolder[] _servlets; + private ServletMapping[] _servletMappings; + + private transient Map _filterNameMap= new HashMap(); + private transient List _filterPathMappings; + private transient MultiMap _filterNameMappings; + + private transient Map _servletNameMap=new HashMap(); + private transient PathMap _servletPathMap; + + protected transient ConcurrentHashMap _chainCache[]; + + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public ServletHandler() + { + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.handler.AbstractHandler#setServer(org.eclipse.jetty.server.Server) + */ + public void setServer(Server server) + { + if (getServer()!=null && getServer()!=server) + { + getServer().getContainer().update(this, _filters, null, "filter",true); + getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true); + getServer().getContainer().update(this, _servlets, null, "servlet",true); + getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true); + } + if (server!=null && getServer()!=server) + { + server.getContainer().update(this, null, _filters, "filter",true); + server.getContainer().update(this, null, _filterMappings, "filterMapping",true); + server.getContainer().update(this, null, _servlets, "servlet",true); + server.getContainer().update(this, null, _servletMappings, "servletMapping",true); + } + super.setServer(server); + } + + /* ----------------------------------------------------------------- */ + protected synchronized void doStart() + throws Exception + { + _servletContext=ContextHandler.getCurrentContext(); + _contextHandler=_servletContext==null?null:_servletContext.getContextHandler(); + + if (_contextHandler!=null) + { + SecurityHandler security_handler = (SecurityHandler)_contextHandler.getChildHandlerByClass(SecurityHandler.class); + if (security_handler!=null) + _identityService=(IdentityService)security_handler.getIdentityService(); + } + + updateNameMappings(); + updateMappings(); + + if(_filterChainsCached) + _chainCache= new ConcurrentHashMap[]{null,new ConcurrentHashMap(),new ConcurrentHashMap(),null,new ConcurrentHashMap(),null,null,null,new ConcurrentHashMap(),null,null,null,null,null,null,null,new ConcurrentHashMap()}; + + super.doStart(); + + if (_contextHandler==null || !(_contextHandler instanceof ServletContextHandler)) + initialize(); + } + + /* ----------------------------------------------------------------- */ + protected synchronized void doStop() + throws Exception + { + super.doStop(); + + // Stop filters + if (_filters!=null) + { + for (int i=_filters.length; i-->0;) + { + try { _filters[i].stop(); }catch(Exception e){Log.warn(Log.EXCEPTION,e);} + } + } + + // Stop servlets + if (_servlets!=null) + { + for (int i=_servlets.length; i-->0;) + { + try { _servlets[i].stop(); }catch(Exception e){Log.warn(Log.EXCEPTION,e);} + } + } + + _filterPathMappings=null; + _filterNameMappings=null; + + _servletPathMap=null; + _chainCache=null; + } + + /* ------------------------------------------------------------ */ + IdentityService getIdentityService() + { + return _identityService; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the contextLog. + */ + public Object getContextLog() + { + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the filterMappings. + */ + public FilterMapping[] getFilterMappings() + { + return _filterMappings; + } + + /* ------------------------------------------------------------ */ + /** Get Filters. + * @return Array of defined servlets + */ + public FilterHolder[] getFilters() + { + return _filters; + } + + /* ------------------------------------------------------------ */ + /** ServletHolder matching path. + * @param pathInContext Path within _context. + * @return PathMap Entries pathspec to ServletHolder + */ + public PathMap.Entry getHolderEntry(String pathInContext) + { + if (_servletPathMap==null) + return null; + return _servletPathMap.getMatch(pathInContext); + } + + /* ------------------------------------------------------------ */ + /** + * @param uriInContext uri to get dispatcher for + * @return A {@link RequestDispatcher dispatcher} wrapping the resource at uriInContext, + * or null if the specified uri cannot be dispatched to. + */ + public RequestDispatcher getRequestDispatcher(String uriInContext) + { + if (uriInContext == null) + return null; + + if (!uriInContext.startsWith("/")) + return null; + + try + { + String query=null; + int q; + if ((q=uriInContext.indexOf('?'))>0) + { + query=uriInContext.substring(q+1); + uriInContext=uriInContext.substring(0,q); + } + if ((q=uriInContext.indexOf(';'))>0) + uriInContext=uriInContext.substring(0,q); + + String pathInContext=URIUtil.canonicalPath(URIUtil.decodePath(uriInContext)); + String uri=URIUtil.addPaths(_contextHandler.getContextPath(), uriInContext); + return new Dispatcher(_contextHandler, uri, pathInContext, query); + } + catch(Exception e) + { + Log.ignore(e); + } + return null; + } + + /* ------------------------------------------------------------ */ + public ServletContext getServletContext() + { + return _servletContext; + } + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletMappings. + */ + public ServletMapping[] getServletMappings() + { + return _servletMappings; + } + + /* ------------------------------------------------------------ */ + /** Get Servlets. + * @return Array of defined servlets + */ + public ServletHolder[] getServlets() + { + return _servlets; + } + + /* ------------------------------------------------------------ */ + public ServletHolder getServlet(String name) + { + return (ServletHolder)_servletNameMap.get(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request,HttpServletResponse response) + throws IOException, ServletException + { + if (!isStarted()) + return; + + // Get the base requests + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + final String old_servlet_name=base_request.getServletName(); + final String old_servlet_path=base_request.getServletPath(); + final String old_path_info=base_request.getPathInfo(); + UserIdentity scoped_identity = null; + + DispatcherType type = request.getDispatcherType(); + Object request_listeners=null; + ServletRequestEvent request_event=null; + ServletHolder servlet_holder=null; + FilterChain chain=null; + UserIdentity old_identity=null; + + // find the servlet + if (target.startsWith("/")) + { + // Look for the servlet by path + PathMap.Entry entry=getHolderEntry(target); + if (entry!=null) + { + servlet_holder=(ServletHolder)entry.getValue(); + + if(Log.isDebugEnabled())Log.debug("servlet="+servlet_holder); + + String servlet_path_spec=(String)entry.getKey(); + String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target); + String path_info=PathMap.pathInfo(servlet_path_spec,target); + + if (DispatcherType.INCLUDE.equals(type)) + { + base_request.setAttribute(Dispatcher.__INCLUDE_SERVLET_PATH,servlet_path); + base_request.setAttribute(Dispatcher.__INCLUDE_PATH_INFO, path_info); + } + else + { + base_request.setServletPath(servlet_path); + base_request.setPathInfo(path_info); + } + + if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0) + chain=getFilterChain(base_request, target, servlet_holder); + } + } + else + { + // look for a servlet by name! + servlet_holder=(ServletHolder)_servletNameMap.get(target); + if (servlet_holder!=null) + { + if (_filterMappings!=null && _filterMappings.length>0) + { + chain=getFilterChain(base_request, null,servlet_holder); + } + } + } + + if (Log.isDebugEnabled()) + { + Log.debug("chain="+chain); + Log.debug("servlet holder="+servlet_holder); + } + + try + { + // Do the filter/handling thang + if (servlet_holder==null) + { + notFound(request, response); + } + else + { + base_request.setServletName(servlet_holder.getName()); + if (_identityService!=null) + { + old_identity=base_request.getUserIdentity(); + scoped_identity=_identityService.associate(old_identity,servlet_holder); + base_request.setUserIdentity(scoped_identity); + } + + // Handle context listeners + request_listeners = base_request.takeRequestListeners(); + if (request_listeners!=null) + { + request_event = new ServletRequestEvent(getServletContext(),request); + final int s=LazyList.size(request_listeners); + for(int i=0;i0;) + { + final ServletRequestListener listener = (ServletRequestListener)LazyList.get(request_listeners,i); + listener.requestDestroyed(request_event); + } + } + + if (scoped_identity!=null) + { + _identityService.disassociate(scoped_identity); + base_request.setUserIdentity(old_identity); + } + base_request.setServletName(old_servlet_name); + + if (!(DispatcherType.INCLUDE.equals(type))) + { + base_request.setServletPath(old_servlet_path); + base_request.setPathInfo(old_path_info); + } + } + } + + /* ------------------------------------------------------------ */ + private FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) + { + String key=pathInContext==null?servletHolder.getName():pathInContext; + int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType()); + + if (_filterChainsCached && _chainCache!=null) + { + FilterChain chain = (FilterChain)_chainCache[dispatch].get(key); + if (chain!=null) + return chain; + } + + // Build list of filters + Object filters= null; + // Path filters + if (pathInContext!=null && _filterPathMappings!=null) + { + for (int i= 0; i < _filterPathMappings.size(); i++) + { + FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i); + if (mapping.appliesTo(pathInContext, dispatch)) + filters= LazyList.add(filters, mapping.getFilterHolder()); + } + } + + // Servlet name filters + if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0) + { + // Servlet name filters + if (_filterNameMappings.size() > 0) + { + Object o= _filterNameMappings.get(servletHolder.getName()); + for (int i=0; i 0) + chain= new CachedChain(filters, servletHolder); + if (_maxFilterChainsCacheSize>0 && _chainCache[dispatch].size()>_maxFilterChainsCacheSize) + _chainCache[dispatch].clear(); + _chainCache[dispatch].put(key,chain); + } + else if (LazyList.size(filters) > 0) + chain = new Chain(baseRequest,filters, servletHolder); + + return chain; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the initializeAtStart. + * @deprecated + */ + public boolean isInitializeAtStart() + { + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @param initializeAtStart The initializeAtStart to set. + * @deprecated + */ + public void setInitializeAtStart(boolean initializeAtStart) + { + } + + /* ------------------------------------------------------------ */ + /** + * @return true if the handler is started and there are no unavailable servlets + */ + public boolean isAvailable() + { + if (!isStarted()) + return false; + ServletHolder[] holders = getServlets(); + for (int i=0;i servlet,String pathSpec) + { + ServletHolder holder = newServletHolder(servlet); + setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class)); + + addServletWithMapping(holder,pathSpec); + + return holder; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + * @param servlet servlet holder to add + * @param pathSpec servlet mappings for the servletHolder + */ + public void addServletWithMapping (ServletHolder servlet,String pathSpec) + { + ServletHolder[] holders=getServlets(); + if (holders!=null) + holders = holders.clone(); + + try + { + setServlets((ServletHolder[])LazyList.addToArray(holders, servlet, ServletHolder.class)); + + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(servlet.getName()); + mapping.setPathSpec(pathSpec); + setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class)); + } + catch (Exception e) + { + setServlets(holders); + if (e instanceof RuntimeException) + throw (RuntimeException)e; + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a servlet with a servlet mapping. + * @param className + * @param pathSpec + * @return + * @deprecated + */ + public ServletHolder addServlet (String className, String pathSpec) + { + return addServletWithMapping (className, pathSpec); + } + + + /* ------------------------------------------------------------ */ + /**Convenience method to add a pre-constructed ServletHolder. + * @param holder + */ + public void addServlet(ServletHolder holder) + { + setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class)); + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a pre-constructed ServletMapping. + * @param mapping + */ + public void addServletMapping (ServletMapping mapping) + { + setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class)); + } + + /* ------------------------------------------------------------ */ + public FilterHolder newFilterHolder(Class filter) + { + return new FilterHolder(filter); + } + + /* ------------------------------------------------------------ */ + /** + * @see {@link #newFilterHolder(Class)} + */ + public FilterHolder newFilterHolder() + { + return new FilterHolder(); + } + + /* ------------------------------------------------------------ */ + public FilterHolder getFilter(String name) + { + return (FilterHolder)_filterNameMap.get(name); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a filter. + * @param filter class of filter to create + * @param pathSpec filter mappings for filter + * @param dispatches see {@link FilterMapping#setDispatches(int)} + * @return The filter holder. + */ + public FilterHolder addFilterWithMapping (Class filter,String pathSpec,int dispatches) + { + FilterHolder holder = newFilterHolder(filter); + addFilterWithMapping(holder,pathSpec,dispatches); + + return holder; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a filter. + * @param className of filter + * @param pathSpec filter mappings for filter + * @param dispatches see {@link FilterMapping#setDispatches(int)} + * @return The filter holder. + */ + public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches) + { + FilterHolder holder = newFilterHolder(null); + holder.setName(className+"-"+holder.hashCode()); + holder.setClassName(className); + + addFilterWithMapping(holder,pathSpec,dispatches); + return holder; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a filter. + * @param holder filter holder to add + * @param pathSpec filter mappings for filter + * @param dispatches see {@link FilterMapping#setDispatches(int)} + */ + public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches) + { + FilterHolder[] holders = getFilters(); + if (holders!=null) + holders = (FilterHolder[])holders.clone(); + + try + { + setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class)); + + FilterMapping mapping = new FilterMapping(); + mapping.setFilterName(holder.getName()); + mapping.setPathSpec(pathSpec); + mapping.setDispatches(dispatches); + setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class)); + } + catch (RuntimeException e) + { + setFilters(holders); + throw e; + } + catch (Error e) + { + setFilters(holders); + throw e; + } + + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a filter with a mapping + * @param className + * @param pathSpec + * @param dispatches + * @return + * @deprecated + */ + public FilterHolder addFilter (String className,String pathSpec,int dispatches) + { + return addFilterWithMapping(className, pathSpec, dispatches); + } + + /* ------------------------------------------------------------ */ + /** + * convenience method to add a filter and mapping + * @param filter + * @param filterMapping + */ + public void addFilter (FilterHolder filter, FilterMapping filterMapping) + { + if (filter != null) + setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class)); + if (filterMapping != null) + setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), filterMapping, FilterMapping.class)); + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a preconstructed FilterHolder + * @param filter + */ + public void addFilter (FilterHolder filter) + { + if (filter != null) + setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class)); + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a preconstructed FilterMapping + * @param mapping + */ + public void addFilterMapping (FilterMapping mapping) + { + if (mapping != null) + setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class)); + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a preconstructed FilterMapping + * @param mapping + */ + public void prependFilterMapping (FilterMapping mapping) + { + if (mapping != null) + { + FilterMapping[] mappings =getFilterMappings(); + if (mappings==null || mappings.length==0) + setFilterMappings(new FilterMapping[] {mapping}); + else + { + + FilterMapping[] new_mappings=new FilterMapping[mappings.length+1]; + System.arraycopy(mappings,0,new_mappings,1,mappings.length); + new_mappings[0]=mapping; + setFilterMappings(new_mappings); + } + } + } + + /* ------------------------------------------------------------ */ + protected synchronized void updateNameMappings() + { + // update filter name map + _filterNameMap.clear(); + if (_filters!=null) + { + for (int i=0;i<_filters.length;i++) + { + _filterNameMap.put(_filters[i].getName(),_filters[i]); + _filters[i].setServletHandler(this); + } + } + + // Map servlet names to holders + _servletNameMap.clear(); + if (_servlets!=null) + { + // update the maps + for (int i=0;i<_servlets.length;i++) + { + _servletNameMap.put(_servlets[i].getName(),_servlets[i]); + _servlets[i].setServletHandler(this); + } + } + } + + /* ------------------------------------------------------------ */ + protected synchronized void updateMappings() + { + // update filter mappings + if (_filterMappings==null) + { + _filterPathMappings=null; + _filterNameMappings=null; + } + else + { + _filterPathMappings=new ArrayList(); + _filterNameMappings=new MultiMap(); + for (int i=0;i<_filterMappings.length;i++) + { + FilterHolder filter_holder = (FilterHolder)_filterNameMap.get(_filterMappings[i].getFilterName()); + if (filter_holder==null) + throw new IllegalStateException("No filter named "+_filterMappings[i].getFilterName()); + _filterMappings[i].setFilterHolder(filter_holder); + if (_filterMappings[i].getPathSpecs()!=null) + _filterPathMappings.add(_filterMappings[i]); + + if (_filterMappings[i].getServletNames()!=null) + { + String[] names=_filterMappings[i].getServletNames(); + for (int j=0;j0) + { + _filterHolder=(FilterHolder)LazyList.get(filters, 0); + filters=LazyList.remove(filters,0); + _next=new CachedChain(filters,servletHolder); + } + else + _servletHolder=servletHolder; + } + + /* ------------------------------------------------------------ */ + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException + { + // pass to next filter + if (_filterHolder!=null) + { + if (Log.isDebugEnabled()) + Log.debug("call filter " + _filterHolder); + Filter filter= _filterHolder.getFilter(); + if (_filterHolder.isAsyncSupported()) + filter.doFilter(request, response, _next); + else + { + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + final boolean suspendable=base_request.isAsyncSupported(); + if (suspendable) + { + try + { + base_request.setAsyncSupported(false); + filter.doFilter(request, response, _next); + } + finally + { + base_request.setAsyncSupported(true); + } + } + else + filter.doFilter(request, response, _next); + } + return; + } + + // Call servlet + if (_servletHolder != null) + { + if (Log.isDebugEnabled()) + Log.debug("call servlet " + _servletHolder); + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + _servletHolder.handle(base_request,request, response); + } + else // Not found + notFound((HttpServletRequest)request, (HttpServletResponse)response); + } + + public String toString() + { + if (_filterHolder!=null) + return _filterHolder+"->"+_next.toString(); + if (_servletHolder!=null) + return _servletHolder.toString(); + return "null"; + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class Chain implements FilterChain + { + final Request _baseRequest; + final Object _chain; + final ServletHolder _servletHolder; + int _filter= 0; + + /* ------------------------------------------------------------ */ + Chain(Request baseRequest, Object filters, ServletHolder servletHolder) + { + _baseRequest=baseRequest; + _chain= filters; + _servletHolder= servletHolder; + } + + /* ------------------------------------------------------------ */ + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException + { + if (Log.isDebugEnabled()) Log.debug("doFilter " + _filter); + + // pass to next filter + if (_filter < LazyList.size(_chain)) + { + FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++); + if (Log.isDebugEnabled()) Log.debug("call filter " + holder); + Filter filter= holder.getFilter(); + + if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported()) + { + filter.doFilter(request, response, this); + } + else + { + try + { + _baseRequest.setAsyncSupported(false); + filter.doFilter(request, response, this); + } + finally + { + _baseRequest.setAsyncSupported(true); + } + } + + return; + } + + // Call servlet + if (_servletHolder != null) + { + if (Log.isDebugEnabled()) Log.debug("call servlet " + _servletHolder); + _servletHolder.handle(_baseRequest,request, response); + } + else // Not found + notFound((HttpServletRequest)request, (HttpServletResponse)response); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + StringBuilder b = new StringBuilder(); + for (int i=0; i"); + } + b.append(_servletHolder); + return b.toString(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return The maximum entries in a filter chain cache. + */ + public int getMaxFilterChainsCacheSize() + { + return _maxFilterChainsCacheSize; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum filter chain cache size. + * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size + * is greater than zero, then the cache is flushed whenever it grows to be this size. + * + * @param maxFilterChainsCacheSize the maximum number of entries in a filter chain cache. + */ + public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize) + { + _maxFilterChainsCacheSize = maxFilterChainsCacheSize; + } + + /** + * Customize a servlet. + * + * Called before the servlet goes into service. + * Subclasses of ServletHandler should override + * this method. + * + * @param servlet + * @return + * @throws Exception + */ + public Servlet customizeServlet (Servlet servlet) + throws Exception + { + return servlet; + } + + + public Servlet customizeServletDestroy (Servlet servlet) + throws Exception + { + return servlet; + } + + + /** + * Customize a Filter. + * + * Called before the Filter goes into service. + * Subclasses of ServletHandler should override + * this method. + * + * @param filter + * @return + * @throws Exception + */ + public Filter customizeFilter (Filter filter) + throws Exception + { + return filter; + } + + + public Filter customizeFilterDestroy (Filter filter) + throws Exception + { + return filter; + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java new file mode 100644 index 00000000000..d37504fe4fe --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -0,0 +1,685 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.SingleThreadModel; +import javax.servlet.UnavailableException; + +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.RunAsToken; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.log.Log; + + + + +/* --------------------------------------------------------------------- */ +/** Servlet Instance and Context Holder. + * Holds the name, params and some state of a javax.servlet.Servlet + * instance. It implements the ServletConfig interface. + * This class will organise the loading of the servlet when needed or + * requested. + * + * + */ +public class ServletHolder extends Holder implements UserIdentity.Scope, Comparable +{ + /* ---------------------------------------------------------------- */ + private int _initOrder; + private boolean _initOnStartup=false; + private Map _roleMap; + private String _forcedPath; + private String _runAsRole; + private RunAsToken _runAsToken; + private IdentityService _identityService; + + + private transient Servlet _servlet; + private transient Config _config; + private transient long _unavailable; + private transient UnavailableException _unavailableEx; + public static final Map NO_MAPPED_ROLES = Collections.emptyMap(); + + /* ---------------------------------------------------------------- */ + /** Constructor . + */ + public ServletHolder() + {} + + /* ---------------------------------------------------------------- */ + /** Constructor for existing servlet. + */ + public ServletHolder(Servlet servlet) + { + setServlet(servlet); + } + + /* ---------------------------------------------------------------- */ + /** Constructor for existing servlet. + */ + public ServletHolder(Class servlet) + { + super(servlet); + } + + /* ---------------------------------------------------------------- */ + /** + * @return The unavailable exception or null if not unavailable + */ + public UnavailableException getUnavailableException() + { + return _unavailableEx; + } + + /* ------------------------------------------------------------ */ + public synchronized void setServlet(Servlet servlet) + { + if (servlet==null || servlet instanceof SingleThreadModel) + throw new IllegalArgumentException(); + + _extInstance=true; + _servlet=servlet; + setHeldClass(servlet.getClass()); + if (getName()==null) + setName(servlet.getClass().getName()+"-"+super.hashCode()); + } + + /* ------------------------------------------------------------ */ + public int getInitOrder() + { + return _initOrder; + } + + /* ------------------------------------------------------------ */ + /** Set the initialize order. + * Holders with order<0, are initialized on use. Those with + * order>=0 are initialized in increasing order when the handler + * is started. + */ + public void setInitOrder(int order) + { + _initOnStartup=true; + _initOrder = order; + } + + /* ------------------------------------------------------------ */ + /** Comparitor by init order. + */ + public int compareTo(Object o) + { + if (o instanceof ServletHolder) + { + ServletHolder sh= (ServletHolder)o; + if (sh==this) + return 0; + if (sh._initOrder<_initOrder) + return 1; + if (sh._initOrder>_initOrder) + return -1; + + int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0; + if (c==0) + c=_name.compareTo(sh._name); + if (c==0) + c=this.hashCode()>o.hashCode()?1:-1; + return c; + } + return 1; + } + + /* ------------------------------------------------------------ */ + public boolean equals(Object o) + { + return compareTo(o)==0; + } + + /* ------------------------------------------------------------ */ + public int hashCode() + { + return _name==null?System.identityHashCode(this):_name.hashCode(); + } + + /* ------------------------------------------------------------ */ + /** Link a user role. + * Translate the role name used by a servlet, to the link name + * used by the container. + * @param name The role name as used by the servlet + * @param link The role name as used by the container. + */ + public synchronized void setUserRoleLink(String name,String link) + { + if (_roleMap==null) + _roleMap=new HashMap(); + _roleMap.put(name,link); + } + + /* ------------------------------------------------------------ */ + /** get a user role link. + * @param name The name of the role + * @return The name as translated by the link. If no link exists, + * the name is returned. + */ + public String getUserRoleLink(String name) + { + if (_roleMap==null) + return name; + String link= _roleMap.get(name); + return (link==null)?name:link; + } + + /* ------------------------------------------------------------ */ + public Map getRoleMap() + { + return _roleMap == null? NO_MAPPED_ROLES : _roleMap; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the forcedPath. + */ + public String getForcedPath() + { + return _forcedPath; + } + + /* ------------------------------------------------------------ */ + /** + * @param forcedPath The forcedPath to set. + */ + public void setForcedPath(String forcedPath) + { + _forcedPath = forcedPath; + } + + /* ------------------------------------------------------------ */ + public void doStart() + throws Exception + { + _unavailable=0; + try + { + super.doStart(); + checkServletType(); + } + catch (UnavailableException ue) + { + makeUnavailable(ue); + } + + _identityService = _servletHandler.getIdentityService(); + if (_identityService!=null && _runAsRole!=null) + _runAsToken=_identityService.newRunAsToken(_runAsRole); + + _config=new Config(); + + if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class)) + _servlet = new SingleThreadedWrapper(); + + if (_extInstance || _initOnStartup) + { + try + { + initServlet(); + } + catch(Exception e) + { + if (_servletHandler.isStartWithUnavailable()) + Log.ignore(e); + else + throw e; + } + } + } + + /* ------------------------------------------------------------ */ + public void doStop() + { + Object old_run_as = null; + if (_servlet!=null) + { + try + { + if (_identityService!=null && _runAsToken!=null) + old_run_as=_identityService.associateRunAs(_runAsToken); + + destroyInstance(_servlet); + } + catch (Exception e) + { + Log.warn(e); + } + finally + { + if (_identityService!=null && _runAsToken!=null) + _identityService.disassociateRunAs(old_run_as); + } + } + + if (!_extInstance) + _servlet=null; + + _config=null; + } + + /* ------------------------------------------------------------ */ + public void destroyInstance (Object o) + throws Exception + { + if (o==null) + return; + Servlet servlet = ((Servlet)o); + servlet.destroy(); + getServletHandler().customizeServletDestroy(servlet); + } + + /* ------------------------------------------------------------ */ + /** Get the servlet. + * @return The servlet + */ + public synchronized Servlet getServlet() + throws ServletException + { + // Handle previous unavailability + if (_unavailable!=0) + { + if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable) + throw _unavailableEx; + _unavailable=0; + _unavailableEx=null; + } + + if (_servlet==null) + initServlet(); + return _servlet; + } + + /* ------------------------------------------------------------ */ + /** + * Check to ensure class of servlet is acceptable. + * @throws UnavailableException + */ + public void checkServletType () + throws UnavailableException + { + if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class)) + { + throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet"); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return true if the holder is started and is not unavailable + */ + public boolean isAvailable() + { + if (isStarted()&& _unavailable==0) + return true; + try + { + getServlet(); + } + catch(Exception e) + { + Log.ignore(e); + } + + return isStarted()&& _unavailable==0; + } + + /* ------------------------------------------------------------ */ + private void makeUnavailable(UnavailableException e) + { + if (_unavailableEx==e && _unavailable!=0) + return; + + _servletHandler.getServletContext().log("unavailable",e); + + _unavailableEx=e; + _unavailable=-1; + if (e.isPermanent()) + _unavailable=-1; + else + { + if (_unavailableEx.getUnavailableSeconds()>0) + _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds(); + else + _unavailable=System.currentTimeMillis()+5000; // TODO configure + } + } + + + /* ------------------------------------------------------------ */ + + private void makeUnavailable(Throwable e) + { + if (e instanceof UnavailableException) + makeUnavailable((UnavailableException)e); + else + { + _servletHandler.getServletContext().log("unavailable",e); + _unavailableEx=new UnavailableException(e.toString(),-1); + _unavailable=-1; + } + } + + /* ------------------------------------------------------------ */ + private void initServlet() + throws ServletException + { + Object old_run_as = null; + try + { + if (_servlet==null) + _servlet=(Servlet)newInstance(); + if (_config==null) + _config=new Config(); + + //handle any cusomizations of the servlet, such as @postConstruct + if (!(_servlet instanceof SingleThreadedWrapper)) + _servlet = getServletHandler().customizeServlet(_servlet); + + // Handle run as + if (_identityService!=null && _runAsToken!=null) + { + old_run_as=_identityService.associateRunAs(_runAsToken); + } + + _servlet.init(_config); + } + catch (UnavailableException e) + { + makeUnavailable(e); + _servlet=null; + _config=null; + throw e; + } + catch (ServletException e) + { + makeUnavailable(e.getCause()==null?e:e.getCause()); + _servlet=null; + _config=null; + throw e; + } + catch (Exception e) + { + makeUnavailable(e); + _servlet=null; + _config=null; + throw new ServletException(this.toString(),e); + } + finally + { + // pop run-as role + if (_identityService!=null && _runAsToken!=null) + _identityService.disassociateRunAs(old_run_as); + } + } + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath() + */ + public String getContextPath() + { + return _config.getServletContext().getContextPath(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap() + */ + public Map getRoleRefMap() + { + return _roleMap; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.UserIdentity.Scope#getRunAsRole() + */ + public String getRunAsRole() + { + return _runAsRole; + } + + /* ------------------------------------------------------------ */ + /** + * Set the run-as role for this servlet + * @param role run-as role for this servlet + */ + public void setRunAsRole(String role) + { + _runAsRole=role; + } + + /* ------------------------------------------------------------ */ + /** Service a request with this servlet. + */ + public void handle(Request baseRequest, + ServletRequest request, + ServletResponse response) + throws ServletException, + UnavailableException, + IOException + { + if (_class==null) + throw new UnavailableException("Servlet Not Initialized"); + + Servlet servlet=_servlet; + synchronized(this) + { + if (_unavailable!=0 || !_initOnStartup) + servlet=getServlet(); + if (servlet==null) + throw new UnavailableException("Could not instantiate "+_class); + } + + // Service the request + boolean servlet_error=true; + Object old_run_as = null; + boolean suspendable = baseRequest.isAsyncSupported(); + try + { + // Handle aliased path + if (_forcedPath!=null) + // TODO complain about poor naming to the Jasper folks + request.setAttribute("org.apache.catalina.jsp_file",_forcedPath); + + // Handle run as + if (_identityService!=null && _runAsToken!=null) + old_run_as=_identityService.associateRunAs(_runAsToken); + + if (!isAsyncSupported()) + baseRequest.setAsyncSupported(false); + + servlet.service(request,response); + servlet_error=false; + } + catch(UnavailableException e) + { + makeUnavailable(e); + throw _unavailableEx; + } + finally + { + baseRequest.setAsyncSupported(suspendable); + + // pop run-as role + if (_identityService!=null && _runAsToken!=null) + _identityService.disassociateRunAs(old_run_as); + + // Handle error params. + if (servlet_error) + request.setAttribute("javax.servlet.error.servlet_name",getName()); + } + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected class Config extends HolderConfig implements ServletConfig + { + /* -------------------------------------------------------- */ + public String getServletName() + { + return getName(); + } + + } + + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + protected class Registration extends HolderRegistration implements ServletRegistration + { + public boolean addMapping(String... urlPatterns) + { + illegalStateIfContextStarted(); + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(ServletHolder.this.getName()); + mapping.setPathSpecs(urlPatterns); + _servletHandler.addServletMapping(mapping); + return true; + } + + public boolean setLoadOnStartup(int loadOnStartup) + { + illegalStateIfContextStarted(); + ServletHolder.this.setInitOrder(loadOnStartup); + return false; + } + } + + public ServletRegistration getRegistration() + { + return new Registration(); + } + + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + private class SingleThreadedWrapper implements Servlet + { + Stack _stack=new Stack(); + + public void destroy() + { + synchronized(this) + { + while(_stack.size()>0) + try { ((Servlet)_stack.pop()).destroy(); } catch (Exception e) { Log.warn(e); } + } + } + + public ServletConfig getServletConfig() + { + return _config; + } + + public String getServletInfo() + { + return null; + } + + public void init(ServletConfig config) throws ServletException + { + synchronized(this) + { + if(_stack.size()==0) + { + try + { + Servlet s = (Servlet) newInstance(); + s = getServletHandler().customizeServlet(s); + s.init(config); + _stack.push(s); + } + catch (ServletException e) + { + throw e; + } + catch (Exception e) + { + throw new ServletException(e); + } + } + } + } + + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + Servlet s; + synchronized(this) + { + if(_stack.size()>0) + s=(Servlet)_stack.pop(); + else + { + try + { + s = (Servlet) newInstance(); + s = getServletHandler().customizeServlet(s); + s.init(_config); + } + catch (ServletException e) + { + throw e; + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new ServletException(e); + } + } + } + + try + { + s.service(req,res); + } + finally + { + synchronized(this) + { + _stack.push(s); + } + } + } + + } +} + + + + + diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java new file mode 100644 index 00000000000..88ecb0963a5 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java @@ -0,0 +1,80 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.util.Arrays; + + +public class ServletMapping +{ + private String[] _pathSpecs; + private String _servletName; + + /* ------------------------------------------------------------ */ + public ServletMapping() + { + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the pathSpec. + */ + public String[] getPathSpecs() + { + return _pathSpecs; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletName. + */ + public String getServletName() + { + return _servletName; + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec The pathSpec to set. + */ + public void setPathSpecs(String[] pathSpecs) + { + _pathSpecs = pathSpecs; + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec The pathSpec to set. + */ + public void setPathSpec(String pathSpec) + { + _pathSpecs = new String[]{pathSpec}; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletName The servletName to set. + */ + public void setServletName(String servletName) + { + _servletName = servletName; + } + + + /* ------------------------------------------------------------ */ + public String toString() + { + return "(S="+_servletName+","+(_pathSpecs==null?"[]":Arrays.asList(_pathSpecs).toString())+")"; + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java new file mode 100644 index 00000000000..bad7293cb47 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java @@ -0,0 +1,240 @@ +package org.eclipse.jetty.servlet; + +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.util.log.Log; + +public class StatisticsServlet extends HttpServlet +{ + boolean _restrictToLocalhost = true; // defaults to true + private Server _server = null; + private StatisticsHandler _statsHandler; + private MemoryMXBean _memoryBean; + private Connector[] _connectors; + + public void init() throws ServletException + { + _memoryBean = ManagementFactory.getMemoryMXBean(); + + ServletContext context = getServletContext(); + ContextHandler.Context scontext = (ContextHandler.Context) context; + _server = scontext.getContextHandler().getServer(); + + Handler handler = _server.getChildHandlerByClass(StatisticsHandler.class); + + if (handler != null) + { + _statsHandler = (StatisticsHandler) handler; + } + else + { + Log.info("Installing Statistics Handler"); + _statsHandler = new StatisticsHandler(); + _server.addHandler(_statsHandler); + } + + + _connectors = _server.getConnectors(); + + if (getInitParameter("restrictToLocalhost") != null) + { + _restrictToLocalhost = "true".equals(getInitParameter("restrictToLocalhost")); + } + + } + + public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException + { + doGet(sreq, sres); + } + + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + + if (_restrictToLocalhost) + { + if (!"127.0.0.1".equals(req.getRemoteAddr())) + { + resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return; + } + } + + if (req.getParameter("xml") != null && "true".equals(req.getParameter("xml"))) + { + sendXmlResponse(resp); + } else + { + sendTextResponse(resp); + } + + } + + private void sendXmlResponse(HttpServletResponse response) throws IOException + { + StringBuilder sb = new StringBuilder(); + + sb.append("\n"); + + sb.append(" \n"); + sb.append(" ").append(_statsHandler.getStatsOnMs()).append("\n"); + sb.append(" ").append(_statsHandler.getRequests()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsTimedout()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsResumed()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsActive()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsActiveMin()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsActiveMax()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsDurationTotal()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsDurationAve()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsDurationMin()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsDurationMax()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsActiveDurationAve()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsActiveDurationMin()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsActiveDurationMax()).append("\n"); + sb.append(" \n"); + + sb.append(" \n"); + sb.append(" ").append(_statsHandler.getResponses1xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponses2xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponses3xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponses4xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponses5xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponsesBytesTotal()).append("\n"); + sb.append(" \n"); + + sb.append(" \n"); + for (Connector connector : _connectors) + { + sb.append(" \n"); + sb.append(" ").append(connector.getName()).append("\n"); + sb.append(" ").append(connector.getStatsOn()).append("\n"); + if (connector.getStatsOn()) + { + sb.append(" ").append(connector.getStatsOnMs()).append("\n"); + sb.append(" ").append(connector.getConnections()).append("\n"); + sb.append(" ").append(connector.getConnectionsOpen()).append("\n"); + sb.append(" ").append(connector.getConnectionsOpenMin()).append("\n"); + sb.append(" ").append(connector.getConnectionsOpenMax()).append("\n"); + sb.append(" ").append(connector.getConnectionsDurationTotal()).append("\n"); + sb.append(" ").append(connector.getConnectionsDurationAve()).append("\n"); + sb.append(" ").append(connector.getConnectionsDurationMin()).append("\n"); + sb.append(" ").append(connector.getConnectionsDurationMax()).append("\n"); + sb.append(" ").append(connector.getRequests()).append("\n"); + sb.append(" ").append(connector.getConnectionsRequestsAve()).append("\n"); + sb.append(" ").append(connector.getConnectionsRequestsMin()).append("\n"); + sb.append(" ").append(connector.getConnectionsRequestsMax()).append("\n"); + } + sb.append(" \n"); + } + sb.append(" \n"); + + sb.append(" \n"); + sb.append(" ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("\n"); + sb.append(" ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("\n"); + sb.append(" \n"); + + sb.append("\n"); + + response.setContentType("text/xml"); + PrintWriter pout = null; + pout = response.getWriter(); + pout.write(sb.toString()); + } + + private void sendTextResponse(HttpServletResponse response) throws IOException + { + + StringBuilder sb = new StringBuilder(); + + sb.append("

      Statistics:

      \n"); + + sb.append("

      Requests:

      \n"); + sb.append("Statistics gathering started " + _statsHandler.getStatsOnMs() + "ms ago").append("
      \n"); + sb.append("Total requests: " + _statsHandler.getRequests()).append("
      \n"); + sb.append("Total requests timed out: " + _statsHandler.getRequestsTimedout()).append("
      \n"); + sb.append("Total requests resumed: " + _statsHandler.getRequestsResumed()).append("
      \n"); + sb.append("Current requests active: " + _statsHandler.getRequestsActive()).append("
      \n"); + sb.append("Min concurrent requests active: " + _statsHandler.getRequestsActiveMin()).append("
      \n"); + sb.append("Max concurrent requests active: " + _statsHandler.getRequestsActiveMax()).append("
      \n"); + sb.append("Total requests duration: " + _statsHandler.getRequestsDurationTotal()).append("
      \n"); + sb.append("Average request duration: " + _statsHandler.getRequestsDurationAve()).append("
      \n"); + sb.append("Min request duration: " + _statsHandler.getRequestsDurationMin()).append("
      \n"); + sb.append("Max request duration: " + _statsHandler.getRequestsDurationMax()).append("
      \n"); + sb.append("Average request active duration: " + _statsHandler.getRequestsActiveDurationAve()).append("
      \n"); + sb.append("Min request active duration: " + _statsHandler.getRequestsActiveDurationMin()).append("
      \n"); + sb.append("Max request active duration: " + _statsHandler.getRequestsActiveDurationMax()).append("
      \n"); + + sb.append("

      Responses:

      \n"); + sb.append("1xx responses: " + _statsHandler.getResponses1xx()).append("
      \n"); + sb.append("2xx responses: " + _statsHandler.getResponses2xx()).append("
      \n"); + sb.append("3xx responses: " + _statsHandler.getResponses3xx()).append("
      \n"); + sb.append("4xx responses: " + _statsHandler.getResponses4xx()).append("
      \n"); + sb.append("5xx responses: " + _statsHandler.getResponses5xx()).append("
      \n"); + sb.append("Bytes sent total: " + _statsHandler.getResponsesBytesTotal()).append("
      \n"); + + sb.append("

      Connections:

      \n"); + for (Connector connector : _connectors) + { + sb.append("

      " + connector.getName() + "

      "); + + if (connector.getStatsOn()) + { + sb.append("Statistics gathering started " + connector.getStatsOnMs() + "ms ago").append("
      \n"); + sb.append("Total connections: " + connector.getConnections()).append("
      \n"); + sb.append("Current connections open: " + connector.getConnectionsOpen()); + sb.append("Min concurrent connections open: " + connector.getConnectionsOpenMin()).append("
      \n"); + sb.append("Max concurrent connections open: " + connector.getConnectionsOpenMax()).append("
      \n"); + sb.append("Total connections duration: " + connector.getConnectionsDurationTotal()).append("
      \n"); + sb.append("Average connection duration: " + connector.getConnectionsDurationAve()).append("
      \n"); + sb.append("Min connection duration: " + connector.getConnectionsDurationMin()).append("
      \n"); + sb.append("Max connection duration: " + connector.getConnectionsDurationMax()).append("
      \n"); + sb.append("Total requests: " + connector.getRequests()).append("
      \n"); + sb.append("Average requests per connection: " + connector.getConnectionsRequestsAve()).append("
      \n"); + sb.append("Min requests per connection: " + connector.getConnectionsRequestsMin()).append("
      \n"); + sb.append("Max requests per connection: " + connector.getConnectionsRequestsMax()).append("
      \n"); + } + else + { + sb.append("Statistics gathering off.\n"); + } + + } + + sb.append("

      Memory:

      \n"); + sb.append("Heap memory usage: " + _memoryBean.getHeapMemoryUsage().getUsed() + " bytes").append("
      \n"); + sb.append("Non-heap memory usage: " + _memoryBean.getNonHeapMemoryUsage().getUsed() + " bytes").append("
      \n"); + + response.setContentType("text/html"); + PrintWriter pout = null; + pout = response.getWriter(); + pout.write(sb.toString()); + + } +} diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AbstractSessionTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AbstractSessionTest.java new file mode 100644 index 00000000000..b49fde7091b --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AbstractSessionTest.java @@ -0,0 +1,176 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import junit.framework.TestCase; + +public abstract class AbstractSessionTest extends TestCase +{ + public static final String __host1 = "localhost"; + public static final String __host2 = __host1; + public static final String __port1 = "8010"; + public static final String __port2 = "8011"; + SessionTestServer _server1; + SessionTestServer _server2; + + public abstract SessionTestServer newServer1 (); + + public abstract SessionTestServer newServer2(); + + public void setUp () throws Exception + { + _server1 = newServer1(); + _server2 = newServer2(); + _server1.start(); + _server2.start(); + } + + public void tearDown () throws Exception + { + if (_server1 != null) + _server1.stop(); + if (_server2 != null) + _server2.stop(); + + _server1=null; + _server2=null; + } + + + public void testSessions () throws Exception + { + SessionTestClient client1 = new SessionTestClient("http://"+__host1+":"+__port1); + SessionTestClient client2 = new SessionTestClient("http://"+__host2+":"+__port2); + // confirm that user has no session + assertFalse(client1.send("/contextA", null)); + String cookie1 = client1.newSession("/contextA"); + assertNotNull(cookie1); + System.err.println("cookie1: " + cookie1); + + // confirm that client2 has the same session attributes as client1 + assertTrue(client1.setAttribute("/contextA", cookie1, "foo", "bar")); + assertTrue(client2.hasAttribute("/contextA", cookie1, "foo", "bar")); + + // confirm that /contextA would share same sessionId as /contextB + assertTrue(client1.send("/contextA/dispatch/forward/contextB", cookie1)); + assertTrue(client2.send("/contextA/dispatch/forward/contextB", cookie1)); + assertTrue(client1.send("/contextB", cookie1)); + + // verify that session attributes on /contextA is different from /contextB + assertFalse(client1.hasAttribute("/contextB/action", cookie1, "foo", "bar")); + + // add new session attributes on /contextB + client1.setAttribute("/contextB/action", cookie1, "zzzzz", "yyyyy"); + assertTrue(client1.hasAttribute("/contextB/action", cookie1, "zzzzz", "yyyyy")); + + // verify that client2 has same sessionAttributes on /contextB + // client1's newly added attribute "zzzzz" needs to be flushed to the database first + // saveInterval is configured at 10s... to test, uncomment the 2 lines below. + //Thread.sleep(10000); + //assertTrue(client2.hasAttribute("/contextB/action", cookie1, "zzzzz", "yyyyy")); + + String cookie2 = client2.newSession("/contextA"); + assertNotNull(cookie2); + System.err.println("cookie2: " + cookie2); + + // confirm that client1 has same session attributes as client2 + assertTrue(client2.setAttribute("/contextA", cookie2, "hello", "world")); + assertTrue(client1.hasAttribute("/contextA", cookie2, "hello", "world")); + + // confirm that /contextA would share same sessionId as /contextB + assertTrue(client1.send("/contextA/dispatch/forward/contextB", cookie2)); + assertTrue(client2.send("/contextA/dispatch/forward/contextB", cookie2)); + assertTrue(client1.send("/contextB", cookie2)); + + // Session invalidate on contextA + assertTrue(client1.invalidate("/contextA", cookie1)); + + // confirm that session on contextB has not been invalidated after contextA has been invalidated + assertTrue(client1.send("/contextB", cookie1)); + + // confirm that session on contextA has been deleted + assertFalse(client1.send("/contextA", cookie1)); + + // Session invalidate on contextB + assertTrue(client1.invalidate("/contextB/action", cookie1)); + + // confirm that session on contextB has been deleted + assertFalse(client1.send("/contextB/action", cookie1)); + + // session will reflect after 10s, so node2 still would not be deleted. + assertTrue(client2.send("/contextB/action", cookie1)); + + // wait for saveInterval and check if the session invalidation has been reflected to the other node + // to test, uncomment 3 lines below + //Thread.sleep(10000); + //assertFalse(client2.send("/contextA", cookie1)); + //assertFalse(client2.send("/contextB/action", cookie1)); + } + + public void testSessionManagerStop() throws Exception + { + SessionTestClient client1 = new SessionTestClient("http://"+__host1+":"+__port1); + SessionTestClient client2 = new SessionTestClient("http://"+__host2+":"+__port2); + // confirm that user has no session + assertFalse(client1.send("/contextA", null)); + String cookie1 = client1.newSession("/contextA"); + assertNotNull(cookie1); + System.err.println("cookie1: " + cookie1); + + // creates a session for contextB + assertTrue(client1.send("/contextB", cookie1)); + + // confirm that /contextA and /contextB sessions are available + assertTrue(client1.send("/contextA", cookie1)); + assertTrue(client1.send("/contextB/action", cookie1)); + assertTrue(client1.setAttribute("/contextA", cookie1, "a", "b")); + assertTrue(client1.setAttribute("/contextB/action", cookie1, "c", "d")); + + // confirm that /contextA and /contextB sessions from client2 are available + assertTrue(client2.send("/contextA", cookie1)); + assertTrue(client2.send("/contextB/action", cookie1)); + assertTrue(client2.hasAttribute("/contextA", cookie1, "a", "b")); + assertTrue(client2.hasAttribute("/contextB/action", cookie1, "c", "d")); + + // stop sessionManager from node1 + _server1._sessionMgr1.stop(); + + // verify session still exists for contextB + assertTrue(client1.send("/contextB/action", cookie1)); + assertTrue(client1.hasAttribute("/contextB/action", cookie1, "c", "d")); + + // stop sessionManager from node2 + _server2._sessionMgr2.stop(); + + // verfiy session still exists for contextA + assertTrue(client2.send("/contextA", cookie1)); + assertTrue(client2.hasAttribute("/contextA", cookie1, "a", "b")); + } + + public void testFailover() throws Exception + { + SessionTestClient client1 = new SessionTestClient("http://"+__host1+":"+__port1); + SessionTestClient client2 = new SessionTestClient("http://"+__host2+":"+__port2); + // confirm that user has no session + assertFalse(client1.send("/contextA", null)); + String cookie1 = client1.newSession("/contextA"); + + assertNotNull(cookie1); + System.err.println("cookie1: " + cookie1); + + assertTrue(client1.setAttribute("/contextA", cookie1, "a", "b")); + + assertTrue(client2.hasAttribute("/contextA", cookie1, "a", "b")); + } +} diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java new file mode 100644 index 00000000000..89d6fba7295 --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java @@ -0,0 +1,295 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Server; + +public class DispatcherTest extends TestCase +{ + private Server _server = new Server(); + private LocalConnector _connector; + private ServletContextHandler _context; + + protected void setUp() throws Exception + { + _server = new Server(); + _server.setSendServerVersion(false); + _connector = new LocalConnector(); + _context = new ServletContextHandler(); + _context.setContextPath("/context"); + _server.addHandler(_context); + _server.addConnector( _connector ); + + _server.start(); + } + + public void testForward() throws Exception + { + _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _context.addServlet(AssertForwardServlet.class, "/AssertForwardServlet/*"); + + String expected= + "HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/html\r\n"+ + "Content-Length: 0\r\n"+ + "\r\n"; + + String responses = _connector.getResponses("GET /context/ForwardServlet?do=assertforward&do=more&test=1 HTTP/1.1\n" + "Host: localhost\n\n"); + + assertEquals(expected, responses); + } + + public void testInclude() throws Exception + { + _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _context.addServlet(AssertIncludeServlet.class, "/AssertIncludeServlet/*"); + + String expected= + "HTTP/1.1 200 OK\r\n"+ + "Content-Length: 0\r\n"+ + "\r\n"; + + String responses = _connector.getResponses("GET /context/IncludeServlet?do=assertinclude&do=more&test=1 HTTP/1.1\n" + "Host: localhost\n\n"); + + assertEquals(expected, responses); + } + + public void testForwardThenInclude() throws Exception + { + _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _context.addServlet(AssertForwardIncludeServlet.class, "/AssertForwardIncludeServlet/*"); + + String expected= + "HTTP/1.1 200 OK\r\n"+ + "Content-Length: 0\r\n"+ + "\r\n"; + + String responses = _connector.getResponses("GET /context/ForwardServlet/forwardpath?do=include HTTP/1.1\n" + "Host: localhost\n\n"); + + assertEquals(expected, responses); + } + + public void testIncludeThenForward() throws Exception + { + _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _context.addServlet(AssertIncludeForwardServlet.class, "/AssertIncludeForwardServlet/*"); + + + String expected= + "HTTP/1.1 200 OK\r\n"+ + "Transfer-Encoding: chunked\r\n"+ + "\r\n"+ + "0\r\n"+ + "\r\n"; + + String responses = _connector.getResponses("GET /context/IncludeServlet/includepath?do=forward HTTP/1.1\n" + "Host: localhost\n\n"); + + assertEquals(expected, responses); + } + + public static class ForwardServlet extends HttpServlet implements Servlet + { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + RequestDispatcher dispatcher = null; + + if(request.getParameter("do").equals("include")) + dispatcher = getServletContext().getRequestDispatcher("/IncludeServlet/includepath?do=assertforwardinclude"); + else if(request.getParameter("do").equals("assertincludeforward")) + dispatcher = getServletContext().getRequestDispatcher("/AssertIncludeForwardServlet/assertpath?do=end"); + else if(request.getParameter("do").equals("assertforward")) + dispatcher = getServletContext().getRequestDispatcher("/AssertForwardServlet?do=end&do=the"); + dispatcher.forward(request, response); + } + } + + public static class IncludeServlet extends HttpServlet implements Servlet + { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + RequestDispatcher dispatcher = null; + + if(request.getParameter("do").equals("forward")) + dispatcher = getServletContext().getRequestDispatcher("/ForwardServlet/forwardpath?do=assertincludeforward"); + else if(request.getParameter("do").equals("assertforwardinclude")) + dispatcher = getServletContext().getRequestDispatcher("/AssertForwardIncludeServlet/assertpath?do=end"); + else if(request.getParameter("do").equals("assertinclude")) + dispatcher = getServletContext().getRequestDispatcher("/AssertIncludeServlet?do=end&do=the"); + dispatcher.include(request, response); + } + } + + public static class AssertForwardServlet extends HttpServlet implements Servlet + { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + assertEquals( "/context/ForwardServlet", request.getAttribute(Dispatcher.__FORWARD_REQUEST_URI)); + assertEquals( "/context", request.getAttribute(Dispatcher.__FORWARD_CONTEXT_PATH) ); + assertEquals( "/ForwardServlet", request.getAttribute(Dispatcher.__FORWARD_SERVLET_PATH)); + assertEquals( null, request.getAttribute(Dispatcher.__FORWARD_PATH_INFO)); + assertEquals( "do=assertforward&do=more&test=1", request.getAttribute(Dispatcher.__FORWARD_QUERY_STRING) ); + + + List expectedAttributeNames = Arrays.asList(new String[] { + Dispatcher.__FORWARD_REQUEST_URI, Dispatcher.__FORWARD_CONTEXT_PATH, + Dispatcher.__FORWARD_SERVLET_PATH, Dispatcher.__FORWARD_QUERY_STRING + }); + List requestAttributeNames = Collections.list(request.getAttributeNames()); + assertTrue(requestAttributeNames.containsAll(expectedAttributeNames)); + + + assertEquals(null, request.getPathInfo()); + assertEquals(null, request.getPathTranslated()); + assertEquals("do=end&do=the&test=1", request.getQueryString()); + assertEquals("/context/AssertForwardServlet", request.getRequestURI()); + assertEquals("/context", request.getContextPath()); + assertEquals("/AssertForwardServlet", request.getServletPath()); + + response.setContentType("text/html"); + response.setStatus(HttpServletResponse.SC_OK); + + } + } + + public static class AssertIncludeServlet extends HttpServlet implements Servlet + { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + assertEquals( "/context/AssertIncludeServlet", request.getAttribute(Dispatcher.__INCLUDE_REQUEST_URI)); + assertEquals( "/context", request.getAttribute(Dispatcher.__INCLUDE_CONTEXT_PATH) ); + assertEquals( "/AssertIncludeServlet", request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH)); + assertEquals( null, request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO)); + assertEquals( "do=end&do=the", request.getAttribute(Dispatcher.__INCLUDE_QUERY_STRING)); + + List expectedAttributeNames = Arrays.asList(new String[] { + Dispatcher.__INCLUDE_REQUEST_URI, Dispatcher.__INCLUDE_CONTEXT_PATH, + Dispatcher.__INCLUDE_SERVLET_PATH, Dispatcher.__INCLUDE_QUERY_STRING + }); + List requestAttributeNames = Collections.list(request.getAttributeNames()); + assertTrue(requestAttributeNames.containsAll(expectedAttributeNames)); + + + + assertEquals(null, request.getPathInfo()); + assertEquals(null, request.getPathTranslated()); + assertEquals("do=assertinclude&do=more&test=1", request.getQueryString()); + assertEquals("/context/IncludeServlet", request.getRequestURI()); + assertEquals("/context", request.getContextPath()); + assertEquals("/IncludeServlet", request.getServletPath()); + + response.setContentType("text/html"); + response.setStatus(HttpServletResponse.SC_OK); + + } + } + + + public static class AssertForwardIncludeServlet extends HttpServlet implements Servlet + { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + // include doesn't hide forward + assertEquals( "/context/ForwardServlet/forwardpath", request.getAttribute(Dispatcher.__FORWARD_REQUEST_URI)); + assertEquals( "/context", request.getAttribute(Dispatcher.__FORWARD_CONTEXT_PATH) ); + assertEquals( "/ForwardServlet", request.getAttribute(Dispatcher.__FORWARD_SERVLET_PATH)); + assertEquals( "/forwardpath", request.getAttribute(Dispatcher.__FORWARD_PATH_INFO)); + assertEquals( "do=include", request.getAttribute(Dispatcher.__FORWARD_QUERY_STRING) ); + + assertEquals( "/context/AssertForwardIncludeServlet/assertpath", request.getAttribute(Dispatcher.__INCLUDE_REQUEST_URI)); + assertEquals( "/context", request.getAttribute(Dispatcher.__INCLUDE_CONTEXT_PATH) ); + assertEquals( "/AssertForwardIncludeServlet", request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH)); + assertEquals( "/assertpath", request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO)); + assertEquals( "do=end", request.getAttribute(Dispatcher.__INCLUDE_QUERY_STRING)); + + + List expectedAttributeNames = Arrays.asList(new String[] { + Dispatcher.__FORWARD_REQUEST_URI, Dispatcher.__FORWARD_CONTEXT_PATH, Dispatcher.__FORWARD_SERVLET_PATH, + Dispatcher.__FORWARD_PATH_INFO, Dispatcher.__FORWARD_QUERY_STRING, + Dispatcher.__INCLUDE_REQUEST_URI, Dispatcher.__INCLUDE_CONTEXT_PATH, Dispatcher.__INCLUDE_SERVLET_PATH, + Dispatcher.__INCLUDE_PATH_INFO, Dispatcher.__INCLUDE_QUERY_STRING + }); + List requestAttributeNames = Collections.list(request.getAttributeNames()); + assertTrue(requestAttributeNames.containsAll(expectedAttributeNames)); + + + assertEquals("/includepath", request.getPathInfo()); + assertEquals(null, request.getPathTranslated()); + assertEquals("do=assertforwardinclude", request.getQueryString()); + assertEquals("/context/IncludeServlet/includepath", request.getRequestURI()); + assertEquals("/context", request.getContextPath()); + assertEquals("/IncludeServlet", request.getServletPath()); + + response.setContentType("text/html"); + response.setStatus(HttpServletResponse.SC_OK); + } + } + + public static class AssertIncludeForwardServlet extends HttpServlet implements Servlet + { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + // forward hides include + assertEquals( null, request.getAttribute(Dispatcher.__INCLUDE_REQUEST_URI)); + assertEquals( null, request.getAttribute(Dispatcher.__INCLUDE_CONTEXT_PATH) ); + assertEquals( null, request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH)); + assertEquals( null, request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO)); + assertEquals( null, request.getAttribute(Dispatcher.__INCLUDE_QUERY_STRING)); + + assertEquals( "/context/IncludeServlet/includepath", request.getAttribute(Dispatcher.__FORWARD_REQUEST_URI)); + assertEquals( "/context", request.getAttribute(Dispatcher.__FORWARD_CONTEXT_PATH) ); + assertEquals( "/IncludeServlet", request.getAttribute(Dispatcher.__FORWARD_SERVLET_PATH)); + assertEquals( "/includepath", request.getAttribute(Dispatcher.__FORWARD_PATH_INFO)); + assertEquals( "do=forward", request.getAttribute(Dispatcher.__FORWARD_QUERY_STRING) ); + + + List expectedAttributeNames = Arrays.asList(new String[] { + Dispatcher.__FORWARD_REQUEST_URI, Dispatcher.__FORWARD_CONTEXT_PATH, Dispatcher.__FORWARD_SERVLET_PATH, + Dispatcher.__FORWARD_PATH_INFO, Dispatcher.__FORWARD_QUERY_STRING, + }); + List requestAttributeNames = Collections.list(request.getAttributeNames()); + assertTrue(requestAttributeNames.containsAll(expectedAttributeNames)); + + + assertEquals("/assertpath", request.getPathInfo()); + assertEquals(null, request.getPathTranslated()); + assertEquals("do=end", request.getQueryString()); + assertEquals("/context/AssertIncludeForwardServlet/assertpath", request.getRequestURI()); + assertEquals("/context", request.getContextPath()); + assertEquals("/AssertIncludeForwardServlet", request.getServletPath()); + + response.setContentType("text/html"); + response.setStatus(HttpServletResponse.SC_OK); + } + } + +} diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java new file mode 100644 index 00000000000..9c98ef751ae --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java @@ -0,0 +1,82 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Server; + +/** + * + * + */ +public class InvokerTest extends TestCase +{ + Server _server; + LocalConnector _connector; + ServletContextHandler _context; + + protected void setUp() throws Exception + { + super.setUp(); + _server = new Server(); + _connector = new LocalConnector(); + _context = new ServletContextHandler(ServletContextHandler.SESSIONS); + + _server.setSendServerVersion(false); + _server.addConnector(_connector); + _server.addHandler(_context); + + _context.setContextPath("/"); + + ServletHolder holder = _context.addServlet(Invoker.class, "/servlet/*"); + holder.setInitParameter("nonContextServlets","true"); + _server.start(); + } + + public void testInvoker() throws Exception + { + String requestPath = "/servlet/"+TestServlet.class.getName(); + String request = "GET "+requestPath+" HTTP/1.0\r\n"+ + "Host: tester\r\n"+ + "\r\n"; + + _connector.reopen(); + String expectedResponse = "HTTP/1.1 200 OK\r\n" + + "Content-Length: 20\r\n" + + "\r\n" + + "Invoked TestServlet!"; + + String response = _connector.getResponses(request); + assertEquals(expectedResponse, response); + } + + public static class TestServlet extends HttpServlet implements Servlet + { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.getWriter().append("Invoked TestServlet!"); + response.getWriter().close(); + } + } +} diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SessionTestClient.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SessionTestClient.java new file mode 100644 index 00000000000..b417b72fa2f --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SessionTestClient.java @@ -0,0 +1,125 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.servlet; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +public class SessionTestClient +{ + + private String _baseUrl; + + // e.g http://localhost:8010 + public SessionTestClient(String baseUrl) + { + _baseUrl = baseUrl; + } + + public boolean send(String context, String cookie) throws Exception + { + HttpURLConnection conn = sendRequest("GET", new URL(_baseUrl + context + "/session/"), + cookie); + return isSessionAvailable(conn); + } + + public String newSession(String context) throws Exception + { + HttpURLConnection conn = sendRequest("POST", new URL(_baseUrl + context + + "/session/?Action=New%20Session"), null); + conn.disconnect(); + return getJSESSIONID(conn.getHeaderField("Set-Cookie")); + } + + public boolean setAttribute(String context, String cookie, String name, String value) throws Exception + { + // should be POST, GET for now + HttpURLConnection conn = sendRequest("GET", new URL(_baseUrl + context + + "/session/?Action=Set&Name=" + name + "&Value=" + value), cookie); + return isAttributeSet(conn, name, value); + } + + public boolean hasAttribute(String context, String cookie, String name, String value) throws Exception + { + HttpURLConnection conn = sendRequest("GET", new URL(_baseUrl + context + "/session/"), + cookie); + return isAttributeSet(conn, name, value); + } + + public boolean invalidate(String context, String cookie) throws Exception + { + // should be POST, GET for now + HttpURLConnection conn = sendRequest("GET", new URL(_baseUrl + context + + "/session/?Action=Invalidate"), cookie); + return !isSessionAvailable(conn); + } + + protected static boolean isSessionAvailable(HttpURLConnection conn) throws Exception + { + return !isTokenPresent(conn, "

      No Session

      "); + } + + protected static boolean isAttributeSet(HttpURLConnection conn, String name, String value) throws Exception + { + return isTokenPresent(conn, "" + name + ": " + value + "
      "); + } + + protected static boolean isTokenPresent(HttpURLConnection conn, String token) throws Exception + { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = null; + boolean present = false; + while((line=br.readLine())!=null) + { + + if(line.indexOf(token)!=-1) + { + present = true; + break; + } + } + conn.disconnect(); + return present; + } + + public HttpURLConnection sendRequest(String method, URL url, String cookie) throws Exception + { + return sendRequest(method, url, cookie, false); + } + + public HttpURLConnection sendRequest(String method, URL url, String cookie, + boolean followRedirects) throws Exception + { + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod(method); + conn.setDoInput(true); + conn.setInstanceFollowRedirects(followRedirects); + if(cookie!=null) + conn.setRequestProperty("Cookie", cookie); + conn.connect(); + return conn; + } + + protected static String getJSESSIONID(String cookie) + { + System.err.println("COOKIE: " + cookie); + int idx = cookie.indexOf("JSESSIONID"); + return cookie.substring(idx, cookie.indexOf(';', idx)); + } + + +} diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SessionTestServer.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SessionTestServer.java new file mode 100644 index 00000000000..2288d5b7b50 --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SessionTestServer.java @@ -0,0 +1,348 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; +import java.util.Enumeration; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionIdManager; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; + +/** + * SessionTestServer + * + * Base class for common backend to test various session plugin + * implementations. + * + * The backend runs 2 jetty servers, with 2 contexts each: + * + * contextA/session - dumps and allows create/delete of a session + * contextA/dispatch/forward/contextB/session - forwards to contextB + * contextB/session - dumps and allows create/delete of a session + * + * Subclasses should implement the configureEnvironment(), + * configureSessionIdManager(), configureSessionManager1(), + * configureSessionManager2() in order to provide the session + * management implementations to test. + */ +public abstract class SessionTestServer extends Server +{ + protected SessionIdManager _sessionIdMgr; + protected SessionManager _sessionMgr1; + protected SessionManager _sessionMgr2; + protected String _workerName; + + + /** + * ForwardingServlet + * Do dispatch forward to test re-use of session id (BUT NOT CONTENTS!) + * + */ + public class ForwardingServlet extends HttpServlet + { + public void doGet (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + String pathInfo = request.getPathInfo(); + + HttpSession session = request.getSession(false); + + if (pathInfo.startsWith("/forward/")) + { + pathInfo = pathInfo.substring(8); + String cpath = pathInfo.substring(0, pathInfo.indexOf('/', 1)); + pathInfo = pathInfo.substring(cpath.length()); + ServletContext context = request.getServletContext().getContext(cpath); + RequestDispatcher dispatcher = context.getRequestDispatcher(pathInfo); + dispatcher.forward(request, response); + } + } + } + + + + /** + * SessionDumpServlet + * + * Servlet to dump the contents of the session. + */ + public class SessionDumpServlet extends HttpServlet + { + int redirectCount=0; + + public void init(ServletConfig config) + throws ServletException + { + super.init(config); + } + + + public void dump(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + response.setContentType("text/html"); + + HttpSession session = request.getSession(getURI(request).indexOf("new")>0); + try + { + if (session!=null) + session.isNew(); + } + catch(IllegalStateException e) + { + session=null; + e.printStackTrace(); + } + + PrintWriter out = response.getWriter(); + out.println("

      Session Dump Servlet:

      "); + String submitUrl = getServletContext().getContextPath(); + submitUrl = (submitUrl.equals("")?"/":submitUrl); + submitUrl = (submitUrl.endsWith("/")?submitUrl:submitUrl+"/"); + submitUrl += "session/"; + out.println("
      "); + + if (session==null) + { + out.println("

      No Session

      "); + out.println(""); + } + else + { + try + { + out.println("ID: "+session.getId()+"
      "); + out.println("New: "+session.isNew()+"
      "); + out.println("Created: "+new Date(session.getCreationTime())+"
      "); + out.println("Last: "+new Date(session.getLastAccessedTime())+"
      "); + out.println("Max Inactive: "+session.getMaxInactiveInterval()+"
      "); + out.println("Context: "+session.getServletContext()+"
      "); + + + Enumeration keys=session.getAttributeNames(); + while(keys.hasMoreElements()) + { + String name=(String)keys.nextElement(); + String value=""+session.getAttribute(name); + + out.println(""+name+": "+value+"
      "); + } + + out.println("Name:
      "); + out.println("Value:
      "); + + out.println(""); + out.println(""); + out.println(""); + out.println("
      "); + + out.println("

      "); + + if (request.isRequestedSessionIdFromCookie()) + out.println("

      Turn off cookies in your browser to try url encoding
      "); + + if (request.isRequestedSessionIdFromURL()) + out.println("

      Turn on cookies in your browser to try cookie encoding
      "); + out.println("Encoded Link
      "); + + } + catch (IllegalStateException e) + { + e.printStackTrace(); + } + } + } + + + public String getServletInfo() { + return "Session Dump Servlet"; + } + + + public String getURI(HttpServletRequest request) + { + String uri=(String)request.getAttribute("javax.servlet.forward.request_uri"); + if (uri==null) + uri=request.getRequestURI(); + return uri; + } + } + /** + * SessionForwardedServlet + * + * Servlet that is target of a dispatch forward. + * It will always try and make a new session, and then dump its + * contents as html. + */ + public class SessionForwardedServlet extends SessionDumpServlet + { + + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + handleForm(request, response); + dump(request, response); + } + + protected void handleForm(HttpServletRequest request, + HttpServletResponse response) + { + HttpSession session = request.getSession(false); + String action = request.getParameter("Action"); + String name = request.getParameter("Name"); + String value = request.getParameter("Value"); + + if (action!=null) + { + if(action.equals("New Session")) + { + session = request.getSession(true); + session.setAttribute("test","value"); + } + else if (session!=null) + { + if (action.equals("Invalidate")) + session.invalidate(); + else if (action.equals("Set") && name!=null && name.length()>0) + session.setAttribute(name,value); + else if (action.equals("Remove")) + session.removeAttribute(name); + } + } + } + + + public void doGet(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + request.getSession(true); + dump(request, response); + } + } + + + /** + * SessionActionServlet + * + * Servlet to allow making a new session under user control + * by clicking the "New Session" button (ie query params ?Action=New Session) + */ + public class SessionActionServlet extends SessionDumpServlet + { + protected void handleForm(HttpServletRequest request, + HttpServletResponse response) + { + HttpSession session = request.getSession(false); + String action = request.getParameter("Action"); + String name = request.getParameter("Name"); + String value = request.getParameter("Value"); + + if (action!=null) + { + if(action.equals("New Session")) + { + session = request.getSession(true); + session.setAttribute("test","value"); + } + else if (session!=null) + { + if (action.equals("Invalidate")) + session.invalidate(); + else if (action.equals("Set") && name!=null && name.length()>0) + session.setAttribute(name,value); + else if (action.equals("Remove")) + session.removeAttribute(name); + } + } + } + + public void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + handleForm(request,response); + String nextUrl = getURI(request)+"?R="+redirectCount++; + String encodedUrl=response.encodeRedirectURL(nextUrl); + response.sendRedirect(encodedUrl); + } + + public void doGet (HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + handleForm(request,response); + dump(request, response); + } + } + + public SessionTestServer(int port, String workerName) + { + super(port); + _workerName = workerName; + configureEnvironment(); + configureIdManager(); + configureSessionManager1(); + configureSessionManager2(); + configureServer(); + } + + public abstract void configureEnvironment (); + + public abstract void configureIdManager(); + + public abstract void configureSessionManager1(); + + public abstract void configureSessionManager2(); + + public void configureServer () + { + if (_sessionIdMgr == null || _sessionMgr1 == null || _sessionMgr2 == null) + throw new IllegalStateException ("Must set a SessionIdManager instance and 2 SessionManager instances"); + + setSessionIdManager(_sessionIdMgr); + //set up 2 contexts and a filter than can forward between them + ContextHandlerCollection contextsA = new ContextHandlerCollection(); + setHandler(contextsA); + setSessionIdManager(_sessionIdMgr); + + ServletContextHandler contextA1 = new ServletContextHandler(contextsA,"/contextA",ServletContextHandler.SESSIONS); + contextA1.addServlet(new ServletHolder(new SessionActionServlet()), "/session/*"); + contextA1.addServlet(new ServletHolder(new ForwardingServlet()), "/dispatch/*"); + contextA1.getSessionHandler().setSessionManager(_sessionMgr1); + _sessionMgr1.setIdManager(_sessionIdMgr); + + + ServletContextHandler contextA2 = new ServletContextHandler(contextsA, "/contextB", ServletContextHandler.SESSIONS); + contextA2.addServlet(new ServletHolder(new SessionForwardedServlet()), "/session/*"); + contextA2.addServlet(new ServletHolder(new SessionActionServlet()), "/action/session/*"); + contextA2.getSessionHandler().setSessionManager(_sessionMgr2); + _sessionMgr2.setIdManager(_sessionIdMgr); + } + +} diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml new file mode 100644 index 00000000000..747c0aec0d8 --- /dev/null +++ b/jetty-servlets/pom.xml @@ -0,0 +1,83 @@ + + + + jetty-project + org.eclipse.jetty + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-servlets + Jetty :: Utility Servlets and Filters + Utility Servlets from Jetty + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.servlets + J2SE-1.5 + org.eclipse.jetty.servlets;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + + + junit + junit + test + + + org.eclipse.jetty + jetty-webapp + ${project.version} + provided + + + org.eclipse.jetty + jetty-client + ${project.version} + + + org.eclipse.jetty + jetty-util + ${project.version} + + + org.mortbay.jetty + servlet-api + provided + + + org.eclipse.jetty + jetty-servlet-tester + ${project.version} + test + + + diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncProxyServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncProxyServlet.java new file mode 100644 index 00000000000..a25fb62fe93 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncProxyServlet.java @@ -0,0 +1,352 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Socket; +import java.util.Enumeration; +import java.util.HashSet; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + + + +/** + * EXPERIMENTAL Proxy servlet. + * + * + */ +public class AsyncProxyServlet implements Servlet +{ + HttpClient _client; + + protected HashSet _DontProxyHeaders = new HashSet(); + { + _DontProxyHeaders.add("proxy-connection"); + _DontProxyHeaders.add("connection"); + _DontProxyHeaders.add("keep-alive"); + _DontProxyHeaders.add("transfer-encoding"); + _DontProxyHeaders.add("te"); + _DontProxyHeaders.add("trailer"); + _DontProxyHeaders.add("proxy-authorization"); + _DontProxyHeaders.add("proxy-authenticate"); + _DontProxyHeaders.add("upgrade"); + } + + private ServletConfig config; + private ServletContext context; + + /* (non-Javadoc) + * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig) + */ + public void init(ServletConfig config) throws ServletException + { + this.config=config; + this.context=config.getServletContext(); + + _client=new HttpClient(); + //_client.setConnectorType(HttpClient.CONNECTOR_SOCKET); + _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + try + { + _client.start(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + /* (non-Javadoc) + * @see javax.servlet.Servlet#getServletConfig() + */ + public ServletConfig getServletConfig() + { + return config; + } + + /* (non-Javadoc) + * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + public void service(ServletRequest req, ServletResponse res) throws ServletException, + IOException + { + final HttpServletRequest request = (HttpServletRequest)req; + final HttpServletResponse response = (HttpServletResponse)res; + if ("CONNECT".equalsIgnoreCase(request.getMethod())) + { + handleConnect(request,response); + } + else + { + final InputStream in=request.getInputStream(); + final OutputStream out=response.getOutputStream(); + + if (request.getDispatcherType()!=DispatcherType.ASYNC) + { + String uri=request.getRequestURI(); + if (request.getQueryString()!=null) + uri+="?"+request.getQueryString(); + + HttpURI url=proxyHttpURI(request.getScheme(), + request.getServerName(), + request.getServerPort(), + uri); + if (url==null) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + final AsyncContext async=request.startAsync(request,response); + + HttpExchange exchange = new HttpExchange() + { + protected void onRequestCommitted() throws IOException + { + } + + protected void onRequestComplete() throws IOException + { + } + + protected void onResponseComplete() throws IOException + { + async.complete(); + } + + protected void onResponseContent(Buffer content) throws IOException + { + content.writeTo(out); + } + + protected void onResponseHeaderComplete() throws IOException + { + } + + protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if (reason!=null && reason.length()>0) + response.setStatus(status,reason.toString()); + else + response.setStatus(status); + } + + protected void onResponseHeader(Buffer name, Buffer value) throws IOException + { + String s = name.toString().toLowerCase(); + if (!_DontProxyHeaders.contains(s)) + response.addHeader(name.toString(),value.toString()); + } + + protected void onConnectionFailed(Throwable ex) + { + onException(ex); + } + + protected void onException(Throwable ex) + { + if (ex instanceof EofException) + { + Log.ignore(ex); + return; + } + Log.warn(ex.toString()); + Log.debug(ex); + if (!response.isCommitted()) + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + async.complete(); + } + + protected void onExpire() + { + if (!response.isCommitted()) + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + async.complete(); + } + + }; + + exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER); + exchange.setMethod(request.getMethod()); + exchange.setURL(url.toString()); + exchange.setVersion(request.getProtocol()); + + if (Log.isDebugEnabled()) + Log.debug("PROXY TO "+url); + + // check connection header + String connectionHdr = request.getHeader("Connection"); + if (connectionHdr!=null) + { + connectionHdr=connectionHdr.toLowerCase(); + if (connectionHdr.indexOf("keep-alive")<0 && + connectionHdr.indexOf("close")<0) + connectionHdr=null; + } + + // copy headers + boolean xForwardedFor=false; + boolean hasContent=false; + long contentLength=-1; + Enumeration enm = request.getHeaderNames(); + while (enm.hasMoreElements()) + { + // TODO could be better than this! + String hdr=(String)enm.nextElement(); + String lhdr=hdr.toLowerCase(); + + if (_DontProxyHeaders.contains(lhdr)) + continue; + if (connectionHdr!=null && connectionHdr.indexOf(lhdr)>=0) + continue; + + if ("content-type".equals(lhdr)) + hasContent=true; + if ("content-length".equals(lhdr)) + { + contentLength=request.getContentLength(); + exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(contentLength)); + if (contentLength>0) + hasContent=true; + } + + Enumeration vals = request.getHeaders(hdr); + while (vals.hasMoreElements()) + { + String val = (String)vals.nextElement(); + if (val!=null) + { + exchange.setRequestHeader(lhdr,val); + xForwardedFor|="X-Forwarded-For".equalsIgnoreCase(hdr); + } + } + } + + // Proxy headers + exchange.setRequestHeader("Via","1.1 (jetty)"); + if (!xForwardedFor) + exchange.addRequestHeader("X-Forwarded-For", + request.getRemoteAddr()); + + if (hasContent) + exchange.setRequestContentSource(in); + + _client.send(exchange); + + } + } + } + + + /* ------------------------------------------------------------ */ + public void handleConnect(HttpServletRequest request, + HttpServletResponse response) + throws IOException + { + String uri = request.getRequestURI(); + + context.log("CONNECT: "+uri); + + String port = ""; + String host = ""; + + int c = uri.indexOf(':'); + if (c>=0) + { + port = uri.substring(c+1); + host = uri.substring(0,c); + if (host.indexOf('/')>0) + host = host.substring(host.indexOf('/')+1); + } + + // TODO - make this async! + + + InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port)); + + //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false)) + //{ + // sendForbid(request,response,uri); + //} + //else + { + InputStream in=request.getInputStream(); + OutputStream out=response.getOutputStream(); + + Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort()); + context.log("Socket: "+socket); + + response.setStatus(200); + response.setHeader("Connection","close"); + response.flushBuffer(); + // TODO prevent real close! + + context.log("out<-in"); + IO.copyThread(socket.getInputStream(),out); + context.log("in->out"); + IO.copy(in,socket.getOutputStream()); + } + } + + /* ------------------------------------------------------------ */ + protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri) + throws MalformedURLException + { + return new HttpURI(scheme+"://"+serverName+":"+serverPort+uri); + } + + + /* (non-Javadoc) + * @see javax.servlet.Servlet#getServletInfo() + */ + public String getServletInfo() + { + return "Proxy Servlet"; + } + + /* (non-Javadoc) + * @see javax.servlet.Servlet#destroy() + */ + public void destroy() + { + + } + +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java new file mode 100644 index 00000000000..5f494baf575 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java @@ -0,0 +1,417 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; + +//----------------------------------------------------------------------------- +/** + * CGI Servlet. + * + * The cgi bin directory can be set with the "cgibinResourceBase" init parameter + * or it will default to the resource base of the context. + * + * The "commandPrefix" init parameter may be used to set a prefix to all + * commands passed to exec. This can be used on systems that need assistance to + * execute a particular file type. For example on windows this can be set to + * "perl" so that perl scripts are executed. + * + * The "Path" init param is passed to the exec environment as PATH. Note: Must + * be run unpacked somewhere in the filesystem. + * + * Any initParameter that starts with ENV_ is used to set an environment + * variable with the name stripped of the leading ENV_ and using the init + * parameter value. + * + * + * + */ +public class CGI extends HttpServlet +{ + private boolean _ok; + private File _docRoot; + private String _path; + private String _cmdPrefix; + private EnvList _env; + private boolean _ignoreExitState; + + /* ------------------------------------------------------------ */ + public void init() throws ServletException + { + _env=new EnvList(); + _cmdPrefix=getInitParameter("commandPrefix"); + + String tmp=getInitParameter("cgibinResourceBase"); + if (tmp==null) + { + tmp=getInitParameter("resourceBase"); + if (tmp==null) + tmp=getServletContext().getRealPath("/"); + } + + if (tmp==null) + { + Log.warn("CGI: no CGI bin !"); + return; + } + + File dir=new File(tmp); + if (!dir.exists()) + { + Log.warn("CGI: CGI bin does not exist - "+dir); + return; + } + + if (!dir.canRead()) + { + Log.warn("CGI: CGI bin is not readable - "+dir); + return; + } + + if (!dir.isDirectory()) + { + Log.warn("CGI: CGI bin is not a directory - "+dir); + return; + } + + try + { + _docRoot=dir.getCanonicalFile(); + } + catch (IOException e) + { + Log.warn("CGI: CGI bin failed - "+dir,e); + return; + } + + _path=getInitParameter("Path"); + if (_path!=null) + _env.set("PATH",_path); + + _ignoreExitState="true".equalsIgnoreCase(getInitParameter("ignoreExitState")); + Enumeration e=getInitParameterNames(); + while (e.hasMoreElements()) + { + String n=(String)e.nextElement(); + if (n!=null&&n.startsWith("ENV_")) + _env.set(n.substring(4),getInitParameter(n)); + } + if(!_env.envMap.containsKey("SystemRoot")) + { + String os = System.getProperty("os.name"); + if (os!=null && os.toLowerCase().indexOf("windows")!=-1) + { + String windir = System.getProperty("windir"); + _env.set("SystemRoot", windir!=null ? windir : "C:\\WINDOWS"); + } + } + + _ok=true; + } + + /* ------------------------------------------------------------ */ + public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException + { + if (!_ok) + { + res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return; + } + + String pathInContext=StringUtil.nonNull(req.getServletPath())+StringUtil.nonNull(req.getPathInfo()); + + if (Log.isDebugEnabled()) + { + Log.debug("CGI: ContextPath : "+req.getContextPath()); + Log.debug("CGI: ServletPath : "+req.getServletPath()); + Log.debug("CGI: PathInfo : "+req.getPathInfo()); + Log.debug("CGI: _docRoot : "+_docRoot); + Log.debug("CGI: _path : "+_path); + Log.debug("CGI: _ignoreExitState: "+_ignoreExitState); + } + + // pathInContext may actually comprises scriptName/pathInfo...We will + // walk backwards up it until we find the script - the rest must + // be the pathInfo; + + String both=pathInContext; + String first=both; + String last=""; + + File exe=new File(_docRoot,first); + + while ((first.endsWith("/")||!exe.exists())&&first.length()>=0) + { + int index=first.lastIndexOf('/'); + + first=first.substring(0,index); + last=both.substring(index,both.length()); + exe=new File(_docRoot,first); + } + + if (first.length()==0||!exe.exists()||exe.isDirectory()||!exe.getCanonicalPath().equals(exe.getAbsolutePath())) + { + res.sendError(404); + } + else + { + if (Log.isDebugEnabled()) + { + Log.debug("CGI: script is "+exe); + Log.debug("CGI: pathInfo is "+last); + } + exec(exe,last,req,res); + } + } + + /* ------------------------------------------------------------ */ + /* + * @param root @param path @param req @param res @exception IOException + */ + private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException + { + String path=command.getAbsolutePath(); + File dir=command.getParentFile(); + String scriptName=req.getRequestURI().substring(0,req.getRequestURI().length()-pathInfo.length()); + String scriptPath=getServletContext().getRealPath(scriptName); + String pathTranslated=req.getPathTranslated(); + + int len=req.getContentLength(); + if (len<0) + len=0; + if ((pathTranslated==null)||(pathTranslated.length()==0)) + pathTranslated=path; + + EnvList env=new EnvList(_env); + // these ones are from "The WWW Common Gateway Interface Version 1.1" + // look at : + // http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1 + env.set("AUTH_TYPE",req.getAuthType()); + env.set("CONTENT_LENGTH",Integer.toString(len)); + env.set("CONTENT_TYPE",req.getContentType()); + env.set("GATEWAY_INTERFACE","CGI/1.1"); + if ((pathInfo!=null)&&(pathInfo.length()>0)) + { + env.set("PATH_INFO",pathInfo); + } + env.set("PATH_TRANSLATED",pathTranslated); + env.set("QUERY_STRING",req.getQueryString()); + env.set("REMOTE_ADDR",req.getRemoteAddr()); + env.set("REMOTE_HOST",req.getRemoteHost()); + // The identity information reported about the connection by a + // RFC 1413 [11] request to the remote agent, if + // available. Servers MAY choose not to support this feature, or + // not to request the data for efficiency reasons. + // "REMOTE_IDENT" => "NYI" + env.set("REMOTE_USER",req.getRemoteUser()); + env.set("REQUEST_METHOD",req.getMethod()); + env.set("SCRIPT_NAME",scriptName); + env.set("SCRIPT_FILENAME",scriptPath); + env.set("SERVER_NAME",req.getServerName()); + env.set("SERVER_PORT",Integer.toString(req.getServerPort())); + env.set("SERVER_PROTOCOL",req.getProtocol()); + env.set("SERVER_SOFTWARE",getServletContext().getServerInfo()); + + Enumeration enm=req.getHeaderNames(); + while (enm.hasMoreElements()) + { + String name=(String)enm.nextElement(); + String value=req.getHeader(name); + env.set("HTTP_"+name.toUpperCase().replace('-','_'),value); + } + + // these extra ones were from printenv on www.dev.nomura.co.uk + env.set("HTTPS",(req.isSecure()?"ON":"OFF")); + // "DOCUMENT_ROOT" => root + "/docs", + // "SERVER_URL" => "NYI - http://us0245", + // "TZ" => System.getProperty("user.timezone"), + + // are we meant to decode args here ? or does the script get them + // via PATH_INFO ? if we are, they should be decoded and passed + // into exec here... + String execCmd=path; + if ((execCmd.charAt(0)!='"')&&(execCmd.indexOf(" ")>=0)) + execCmd="\""+execCmd+"\""; + if (_cmdPrefix!=null) + execCmd=_cmdPrefix+" "+execCmd; + + Process p=(dir==null)?Runtime.getRuntime().exec(execCmd,env.getEnvArray()):Runtime.getRuntime().exec(execCmd,env.getEnvArray(),dir); + + // hook processes input to browser's output (async) + final InputStream inFromReq=req.getInputStream(); + final OutputStream outToCgi=p.getOutputStream(); + final int inLength=len; + + IO.copyThread(p.getErrorStream(),System.err); + + new Thread(new Runnable() + { + public void run() + { + try + { + if (inLength>0) + IO.copy(inFromReq,outToCgi,inLength); + outToCgi.close(); + } + catch (IOException e) + { + Log.ignore(e); + } + } + }).start(); + + // hook processes output to browser's input (sync) + // if browser closes stream, we should detect it and kill process... + OutputStream os = null; + try + { + // read any headers off the top of our input stream + // NOTE: Multiline header items not supported! + String line=null; + InputStream inFromCgi=p.getInputStream(); + + //br=new BufferedReader(new InputStreamReader(inFromCgi)); + //while ((line=br.readLine())!=null) + while( (line = getTextLineFromStream( inFromCgi )).length() > 0 ) + { + if (!line.startsWith("HTTP")) + { + int k=line.indexOf(':'); + if (k>0) + { + String key=line.substring(0,k).trim(); + String value = line.substring(k+1).trim(); + if ("Location".equals(key)) + { + res.sendRedirect(value); + } + else if ("Status".equals(key)) + { + String[] token = value.split( " " ); + int status=Integer.parseInt(token[0]); + res.setStatus(status); + } + else + { + // add remaining header items to our response header + res.addHeader(key,value); + } + } + } + } + // copy cgi content to response stream... + os = res.getOutputStream(); + IO.copy(inFromCgi, os); + p.waitFor(); + + if (!_ignoreExitState) + { + int exitValue=p.exitValue(); + if (0!=exitValue) + { + Log.warn("Non-zero exit status ("+exitValue+") from CGI program: "+path); + if (!res.isCommitted()) + res.sendError(500,"Failed to exec CGI"); + } + } + } + catch (IOException e) + { + // browser has probably closed its input stream - we + // terminate and clean up... + Log.debug("CGI: Client closed connection!"); + } + catch (InterruptedException ie) + { + Log.debug("CGI: interrupted!"); + } + finally + { + if( os != null ) + os.close(); + os = null; + p.destroy(); + // Log.debug("CGI: terminated!"); + } + } + + /** + * Utility method to get a line of text from the input stream. + * @param is the input stream + * @return the line of text + * @throws IOException + */ + private String getTextLineFromStream( InputStream is ) throws IOException { + StringBuilder buffer = new StringBuilder(); + int b; + + while( (b = is.read()) != -1 && b != (int) '\n' ) { + buffer.append( (char) b ); + } + return buffer.toString().trim(); + } + /* ------------------------------------------------------------ */ + /** + * private utility class that manages the Environment passed to exec. + */ + private static class EnvList + { + private Map envMap; + + EnvList() + { + envMap=new HashMap(); + } + + EnvList(EnvList l) + { + envMap=new HashMap(l.envMap); + } + + /** + * Set a name/value pair, null values will be treated as an empty String + */ + public void set(String name, String value) + { + envMap.put(name,name+"="+StringUtil.nonNull(value)); + } + + /** Get representation suitable for passing to exec. */ + public String[] getEnvArray() + { + return (String[])envMap.values().toArray(new String[envMap.size()]); + } + + public String toString() + { + return envMap.toString(); + } + } +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java new file mode 100644 index 00000000000..7010c63916b --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java @@ -0,0 +1,120 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + +import java.io.IOException; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/* ------------------------------------------------------------ */ +/** Concatenation Servlet + * This servlet may be used to concatenate multiple resources into + * a single response. It is intended to be used to load multiple + * javascript or css files, but may be used for any content of the + * same mime type that can be meaningfully concatenated. + *

      + * The servlet uses {@link RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} + * to combine the requested content, so dynamically generated content + * may be combined (Eg engine.js for DWR). + *

      + * The servlet uses parameter names of the query string as resource names + * relative to the context root. So these script tags: + *

      + *  <script type="text/javascript" src="../js/behaviour.js"></script>
      + *  <script type="text/javascript" src="../js/ajax.js&/chat/chat.js"></script>
      + *  <script type="text/javascript" src="../chat/chat.js"></script>
      + * 
      can be replaced with the single tag (with the ConcatServlet mapped to /concat): + *
      + *  <script type="text/javascript" src="../concat?/js/behaviour.js&/js/ajax.js&/chat/chat.js"></script>
      + * 
      + * The {@link ServletContext#getMimeType(String)} method is used to determine the + * mime type of each resource. If the types of all resources do not match, then a 415 + * UNSUPPORTED_MEDIA_TYPE error is returned. + *

      + * If the init parameter "development" is set to "true" then the servlet will run in + * development mode and the content will be concatenated on every request. Otherwise + * the init time of the servlet is used as the lastModifiedTime of the combined content + * and If-Modified-Since requests are handled with 206 NOT Modified responses if + * appropriate. This means that when not in development mode, the servlet must be + * restarted before changed content will be served. + * + * + * + */ +public class ConcatServlet extends HttpServlet +{ + boolean _development; + long _lastModified; + ServletContext _context; + + /* ------------------------------------------------------------ */ + public void init() throws ServletException + { + _lastModified=System.currentTimeMillis(); + _context=getServletContext(); + _development="true".equals(getInitParameter("development")); + } + + /* ------------------------------------------------------------ */ + /* + * @return The start time of the servlet unless in development mode, in which case -1 is returned. + */ + protected long getLastModified(HttpServletRequest req) + { + return _development?-1:_lastModified; + } + + /* ------------------------------------------------------------ */ + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + String q=req.getQueryString(); + if (q==null) + { + resp.sendError(HttpServletResponse.SC_NO_CONTENT); + return; + } + + String[] parts = q.split("\\&"); + String type=null; + for (int i=0;i + *

    • The filter is mapped to a matching path
    • + *
    • The response status code is >=200 and <300 + *
    • The content length is unknown or more than the minGzipSize initParameter or the minGzipSize is 0(default)
    • + *
    • The content-type is in the coma separated list of mimeTypes set in the mimeTypes initParameter or + * if no mimeTypes are defined the content-type is not "application/gzip"
    • + *
    • No content-encoding is specified by the resource
    • + *
    + * + *

    + * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and + * CPU cycles. If this filter is mapped for static content, then use of efficient direct NIO may be + * prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is + * advised instead. + *

    + *

    + * This filter extends {@link UserAgentFilter} and if the the initParameter excludedAgents + * is set to a comma separated list of user agents, then these agents will be excluded from gzip content. + *

    + * + * + * + */ +public class GzipFilter extends UserAgentFilter +{ + protected Set _mimeTypes; + protected int _bufferSize=8192; + protected int _minGzipSize=1; + protected Set _excluded; + + public void init(FilterConfig filterConfig) throws ServletException + { + super.init(filterConfig); + + String tmp=filterConfig.getInitParameter("bufferSize"); + if (tmp!=null) + _bufferSize=Integer.parseInt(tmp); + + tmp=filterConfig.getInitParameter("minGzipSize"); + if (tmp!=null) + _minGzipSize=Integer.parseInt(tmp); + + tmp=filterConfig.getInitParameter("mimeTypes"); + if (tmp!=null) + { + _mimeTypes=new HashSet(); + StringTokenizer tok = new StringTokenizer(tmp,",",false); + while (tok.hasMoreTokens()) + _mimeTypes.add(tok.nextToken()); + } + + tmp=filterConfig.getInitParameter("excludedAgents"); + if (tmp!=null) + { + _excluded=new HashSet(); + StringTokenizer tok = new StringTokenizer(tmp,",",false); + while (tok.hasMoreTokens()) + _excluded.add(tok.nextToken()); + } + } + + public void destroy() + { + } + + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest request=(HttpServletRequest)req; + HttpServletResponse response=(HttpServletResponse)res; + + String ae = request.getHeader("accept-encoding"); + if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding")) + { + if (_excluded!=null) + { + String ua=getUserAgent(request); + if (_excluded.contains(ua)) + { + super.doFilter(request,response,chain); + return; + } + } + + final GZIPResponseWrapper wrappedResponse=newGZIPResponseWrapper(request,response); + + boolean exceptional=true; + try + { + super.doFilter(request,wrappedResponse,chain); + exceptional=false; + } + finally + { + if (request.isAsyncStarted() && !req.getAsyncContext().hasOriginalRequestAndResponse()) + { + request.addAsyncListener(new AsyncListener() + { + public void onComplete(AsyncEvent event) throws IOException + { + wrappedResponse.finish(); + } + + public void onTimeout(AsyncEvent event) throws IOException + {} + }); + } + else if (exceptional && !response.isCommitted()) + { + wrappedResponse.resetBuffer(); + wrappedResponse.noGzip(); + } + else + wrappedResponse.finish(); + } + } + else + { + super.doFilter(request,response,chain); + } + } + + protected GZIPResponseWrapper newGZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response) + { + return new GZIPResponseWrapper(request,response); + } + + public class GZIPResponseWrapper extends HttpServletResponseWrapper + { + HttpServletRequest _request; + boolean _noGzip; + PrintWriter _writer; + GzipStream _gzStream; + long _contentLength=-1; + + public GZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response) + { + super(response); + _request=request; + } + + public void setContentType(String ct) + { + super.setContentType(ct); + int colon=ct.indexOf(";"); + if (colon>0) + ct=ct.substring(0,colon); + + if ((_gzStream==null || _gzStream._out==null) && + (_mimeTypes==null && "application/gzip".equalsIgnoreCase(ct) || + _mimeTypes!=null && !_mimeTypes.contains(StringUtil.asciiToLowerCase(ct)))) + { + noGzip(); + } + } + + public void setStatus(int sc, String sm) + { + super.setStatus(sc,sm); + if (sc<200||sc>=300) + noGzip(); + } + + public void setStatus(int sc) + { + super.setStatus(sc); + if (sc<200||sc>=300) + noGzip(); + } + + public void setContentLength(int length) + { + _contentLength=length; + if (_gzStream!=null) + _gzStream.setContentLength(length); + } + + public void addHeader(String name, String value) + { + if ("content-length".equalsIgnoreCase(name)) + { + _contentLength=Long.parseLong(value); + if (_gzStream!=null) + _gzStream.setContentLength(_contentLength); + } + else if ("content-type".equalsIgnoreCase(name)) + { + setContentType(value); + } + else if ("content-encoding".equalsIgnoreCase(name)) + { + super.addHeader(name,value); + if (!isCommitted()) + { + noGzip(); + } + } + else + super.addHeader(name,value); + } + + public void setHeader(String name, String value) + { + if ("content-length".equalsIgnoreCase(name)) + { + _contentLength=Long.parseLong(value); + if (_gzStream!=null) + _gzStream.setContentLength(_contentLength); + } + else if ("content-type".equalsIgnoreCase(name)) + { + setContentType(value); + } + else if ("content-encoding".equalsIgnoreCase(name)) + { + super.setHeader(name,value); + if (!isCommitted()) + { + noGzip(); + } + } + else + super.setHeader(name,value); + } + + public void setIntHeader(String name, int value) + { + if ("content-length".equalsIgnoreCase(name)) + { + _contentLength=value; + if (_gzStream!=null) + _gzStream.setContentLength(_contentLength); + } + else + super.setIntHeader(name,value); + } + + public void flushBuffer() throws IOException + { + if (_writer!=null) + _writer.flush(); + if (_gzStream!=null) + _gzStream.finish(); + else + getResponse().flushBuffer(); + } + + public void reset() + { + super.reset(); + if (_gzStream!=null) + _gzStream.resetBuffer(); + _writer=null; + _gzStream=null; + _noGzip=false; + _contentLength=-1; + } + + public void resetBuffer() + { + super.resetBuffer(); + if (_gzStream!=null) + _gzStream.resetBuffer(); + _writer=null; + _gzStream=null; + } + + public void sendError(int sc, String msg) throws IOException + { + resetBuffer(); + super.sendError(sc,msg); + } + + public void sendError(int sc) throws IOException + { + resetBuffer(); + super.sendError(sc); + } + + public void sendRedirect(String location) throws IOException + { + resetBuffer(); + super.sendRedirect(location); + } + + public ServletOutputStream getOutputStream() throws IOException + { + if (_gzStream==null) + { + if (getResponse().isCommitted() || _noGzip) + return getResponse().getOutputStream(); + + _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize); + } + else if (_writer!=null) + throw new IllegalStateException("getWriter() called"); + + return _gzStream; + } + + public PrintWriter getWriter() throws IOException + { + if (_writer==null) + { + if (_gzStream!=null) + throw new IllegalStateException("getOutputStream() called"); + + if (getResponse().isCommitted() || _noGzip) + return getResponse().getWriter(); + + _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize); + String encoding = getCharacterEncoding(); + _writer=encoding==null?new PrintWriter(_gzStream):new PrintWriter(new OutputStreamWriter(_gzStream,encoding)); + } + return _writer; + } + + void noGzip() + { + _noGzip=true; + if (_gzStream!=null) + { + try + { + _gzStream.doNotGzip(); + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + } + } + + void finish() throws IOException + { + if (_writer!=null) + _writer.flush(); + if (_gzStream!=null) + _gzStream.finish(); + } + + protected GzipStream newGzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException + { + return new GzipStream(request,response,contentLength,bufferSize,minGzipSize); + } + } + + + public static class GzipStream extends ServletOutputStream + { + protected HttpServletRequest _request; + protected HttpServletResponse _response; + protected OutputStream _out; + protected ByteArrayOutputStream2 _bOut; + protected GZIPOutputStream _gzOut; + protected boolean _closed; + protected int _bufferSize; + protected int _minGzipSize; + protected long _contentLength; + + public GzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException + { + _request=request; + _response=response; + _contentLength=contentLength; + _bufferSize=bufferSize; + _minGzipSize=minGzipSize; + if (minGzipSize==0) + doGzip(); + } + + public void resetBuffer() + { + _closed=false; + _out=null; + _bOut=null; + if (_gzOut!=null && !_response.isCommitted()) + _response.setHeader("Content-Encoding",null); + _gzOut=null; + } + + public void setContentLength(long length) + { + _contentLength=length; + } + + public void flush() throws IOException + { + if (_out==null || _bOut!=null) + { + if (_contentLength>0 && _contentLength<_minGzipSize) + doNotGzip(); + else + doGzip(); + } + + _out.flush(); + } + + public void close() throws IOException + { + if (_request.getAttribute("javax.servlet.include.request_uri")!=null) + flush(); + else + { + if (_bOut!=null) + { + if (_contentLength<0) + _contentLength=_bOut.getCount(); + if (_contentLength<_minGzipSize) + doNotGzip(); + else + doGzip(); + } + else if (_out==null) + { + doNotGzip(); + } + + if (_gzOut!=null) + _gzOut.finish(); + _out.close(); + _closed=true; + } + } + + public void finish() throws IOException + { + if (!_closed) + { + if (_out==null || _bOut!=null) + { + if (_contentLength>0 && _contentLength<_minGzipSize) + doNotGzip(); + else + doGzip(); + } + + if (_gzOut!=null) + _gzOut.finish(); + } + } + + public void write(int b) throws IOException + { + checkOut(1); + _out.write(b); + } + + public void write(byte b[]) throws IOException + { + checkOut(b.length); + _out.write(b); + } + + public void write(byte b[], int off, int len) throws IOException + { + checkOut(len); + _out.write(b,off,len); + } + + protected boolean setContentEncodingGzip() + { + _response.setHeader("Content-Encoding", "gzip"); + return _response.containsHeader("Content-Encoding"); + } + + public void doGzip() throws IOException + { + if (_gzOut==null) + { + if (_response.isCommitted()) + throw new IllegalStateException(); + + if (setContentEncodingGzip()) + { + _out=_gzOut=new GZIPOutputStream(_response.getOutputStream(),_bufferSize); + + if (_bOut!=null) + { + _out.write(_bOut.getBuf(),0,_bOut.getCount()); + _bOut=null; + } + } + else + doNotGzip(); + } + } + + public void doNotGzip() throws IOException + { + if (_gzOut!=null) + throw new IllegalStateException(); + if (_out==null || _bOut!=null ) + { + _out=_response.getOutputStream(); + if (_contentLength>=0) + { + if(_contentLength=0 && _contentLength<_minGzipSize)) + doNotGzip(); + else if (length>_minGzipSize) + doGzip(); + else + _out=_bOut=new ByteArrayOutputStream2(_bufferSize); + } + else if (_bOut!=null) + { + if (_response.isCommitted() || (_contentLength>=0 && _contentLength<_minGzipSize)) + doNotGzip(); + else if (length>=(_bOut.size()-_bOut.getCount())) + doGzip(); + } + } + } + +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java new file mode 100644 index 00000000000..531b270a762 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java @@ -0,0 +1,75 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + + +/* ------------------------------------------------------------ */ +/** Includable GZip Filter. + * This extension to the {@link GzipFilter} that uses Jetty features to allow + * headers to be set during calls to + * {@link javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}. + * This allows the gzip filter to function correct during includes and to make a decision to gzip or not + * at the time the buffer fills and on the basis of all response headers. + * + * + * + */ +public class IncludableGzipFilter extends GzipFilter +{ + + protected GZIPResponseWrapper newGZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response) + { + return new IncludableResponseWrapper(request,response); + } + + public class IncludableResponseWrapper extends GzipFilter.GZIPResponseWrapper + { + public IncludableResponseWrapper(HttpServletRequest request, HttpServletResponse response) + { + super(request,response); + } + + protected GzipStream newGzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException + { + return new IncludableGzipStream(request,response,contentLength,bufferSize,minGzipSize); + } + } + + public class IncludableGzipStream extends GzipFilter.GzipStream + { + public IncludableGzipStream(HttpServletRequest request, HttpServletResponse response, long contentLength, int bufferSize, int minGzipSize) + throws IOException + { + super(request,response,contentLength,bufferSize,minGzipSize); + } + + protected boolean setContentEncodingGzip() + { + if (_request.getAttribute("javax.servlet.include.request_uri")!=null) + _response.setHeader("org.eclipse.jetty.server.include.Content-Encoding", "gzip"); + else + _response.setHeader("Content-Encoding", "gzip"); + + return _response.containsHeader("Content-Encoding"); + } + + } + +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java new file mode 100644 index 00000000000..c24f635f7e2 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java @@ -0,0 +1,456 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.servlets; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; + +/* ------------------------------------------------------------ */ +/** + * Multipart Form Data Filter. + *

    + * This class decodes the multipart/form-data stream sent by a HTML form that uses a file input + * item. Any files sent are stored to a tempary file and a File object added to the request + * as an attribute. All other values are made available via the normal getParameter API and + * the setCharacterEncoding mechanism is respected when converting bytes to Strings. + * + * If the init paramter "delete" is set to "true", any files created will be deleted when the + * current request returns. + * + * + * + */ +public class MultiPartFilter implements Filter +{ + private final static String FILES ="org.eclipse.jetty.servlet.MultiPartFilter.files"; + private File tempdir; + private boolean _deleteFiles; + private ServletContext _context; + private int _fileOutputBuffer = 0; + + /* ------------------------------------------------------------------------------- */ + /** + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + public void init(FilterConfig filterConfig) throws ServletException + { + tempdir=(File)filterConfig.getServletContext().getAttribute("javax.servlet.context.tempdir"); + _deleteFiles="true".equals(filterConfig.getInitParameter("deleteFiles")); + String fileOutputBuffer = filterConfig.getInitParameter("fileOutputBuffer"); + if(fileOutputBuffer!=null) + _fileOutputBuffer = Integer.parseInt(fileOutputBuffer); + _context=filterConfig.getServletContext(); + } + + /* ------------------------------------------------------------------------------- */ + /** + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, + * javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest srequest=(HttpServletRequest)request; + if(srequest.getContentType()==null||!srequest.getContentType().startsWith("multipart/form-data")) + { + chain.doFilter(request,response); + return; + } + + BufferedInputStream in = new BufferedInputStream(request.getInputStream()); + String content_type=srequest.getContentType(); + + // TODO - handle encodings + + String boundary="--"+value(content_type.substring(content_type.indexOf("boundary="))); + byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1); + // cross-container + MultiMap params = new MultiMap(request.getParameterMap()); + + // jetty-specific but more efficient + /*MultiMap params = new MultiMap(); + if(srequest instanceof org.eclipse.jetty.server.Request) + { + org.eclipse.jetty.server.Request req = ((org.eclipse.jetty.server.Request)srequest); + req.getUri().decodeQueryTo(params, req.getQueryEncoding()); + }*/ + + try + { + // Get first boundary + byte[] bytes=TypeUtil.readLine(in); + String line=bytes==null?null:new String(bytes,"UTF-8"); + if(line==null || !line.equals(boundary)) + { + throw new IOException("Missing initial multi part boundary"); + } + + // Read each part + boolean lastPart=false; + String content_disposition=null; + while(!lastPart) + { + while(true) + { + bytes=TypeUtil.readLine(in); + // If blank line, end of part headers + if(bytes==null || bytes.length==0) + break; + line=new String(bytes,"UTF-8"); + + // place part header key and value in map + int c=line.indexOf(':',0); + if(c>0) + { + String key=line.substring(0,c).trim().toLowerCase(); + String value=line.substring(c+1,line.length()).trim(); + if(key.equals("content-disposition")) + content_disposition=value; + } + } + // Extract content-disposition + boolean form_data=false; + if(content_disposition==null) + { + throw new IOException("Missing content-disposition"); + } + + StringTokenizer tok=new StringTokenizer(content_disposition,";"); + String name=null; + String filename=null; + while(tok.hasMoreTokens()) + { + String t=tok.nextToken().trim(); + String tl=t.toLowerCase(); + if(t.startsWith("form-data")) + form_data=true; + else if(tl.startsWith("name=")) + name=value(t); + else if(tl.startsWith("filename=")) + filename=value(t); + } + + // Check disposition + if(!form_data) + { + continue; + } + if(name==null||name.length()==0) + { + continue; + } + + OutputStream out=null; + File file=null; + try + { + if (filename!=null && filename.length()>0) + { + file = File.createTempFile("MultiPart", "", tempdir); + out = new FileOutputStream(file); + if(_fileOutputBuffer>0) + out = new BufferedOutputStream(out, _fileOutputBuffer); + request.setAttribute(name,file); + params.put(name, filename); + + if (_deleteFiles) + { + file.deleteOnExit(); + ArrayList files = (ArrayList)request.getAttribute(FILES); + if (files==null) + { + files=new ArrayList(); + request.setAttribute(FILES,files); + } + files.add(file); + } + + } + else + out=new ByteArrayOutputStream(); + + int state=-2; + int c; + boolean cr=false; + boolean lf=false; + + // loop for all lines` + while(true) + { + int b=0; + while((c=(state!=-2)?state:in.read())!=-1) + { + state=-2; + // look for CR and/or LF + if(c==13||c==10) + { + if(c==13) + state=in.read(); + break; + } + // look for boundary + if(b>=0&&b0) + out.write(byteBoundary,0,b); + b=-1; + out.write(c); + } + } + // check partial boundary + if((b>0&&b0||c==-1) + { + if(b==byteBoundary.length) + lastPart=true; + if(state==10) + state=-2; + break; + } + // handle CR LF + if(cr) + out.write(13); + if(lf) + out.write(10); + cr=(c==13); + lf=(c==10||state==10); + if(state==10) + state=-2; + } + } + finally + { + out.close(); + } + + if (file==null) + { + bytes = ((ByteArrayOutputStream)out).toByteArray(); + params.add(name,bytes); + } + } + + // handle request + chain.doFilter(new Wrapper(srequest,params),response); + } + finally + { + deleteFiles(request); + } + } + + private void deleteFiles(ServletRequest request) + { + ArrayList files = (ArrayList)request.getAttribute(FILES); + if (files!=null) + { + Iterator iter = files.iterator(); + while (iter.hasNext()) + { + File file=(File)iter.next(); + try + { + file.delete(); + } + catch(Exception e) + { + _context.log("failed to delete "+file,e); + } + } + } + } + /* ------------------------------------------------------------ */ + private String value(String nameEqualsValue) + { + String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim(); + int i=value.indexOf(';'); + if(i>0) + value=value.substring(0,i); + if(value.startsWith("\"")) + { + value=value.substring(1,value.indexOf('"',1)); + } + else + { + i=value.indexOf(' '); + if(i>0) + value=value.substring(0,i); + } + return value; + } + + /* ------------------------------------------------------------------------------- */ + /** + * @see javax.servlet.Filter#destroy() + */ + public void destroy() + { + } + + private static class Wrapper extends HttpServletRequestWrapper + { + String encoding="UTF-8"; + MultiMap map; + + /* ------------------------------------------------------------------------------- */ + /** Constructor. + * @param request + */ + public Wrapper(HttpServletRequest request, MultiMap map) + { + super(request); + this.map=map; + } + + /* ------------------------------------------------------------------------------- */ + /** + * @see javax.servlet.ServletRequest#getContentLength() + */ + public int getContentLength() + { + return 0; + } + + /* ------------------------------------------------------------------------------- */ + /** + * @see javax.servlet.ServletRequest#getParameter(java.lang.String) + */ + public String getParameter(String name) + { + Object o=map.get(name); + if (o instanceof byte[]) + { + try + { + String s=new String((byte[])o,encoding); + return s; + } + catch(Exception e) + { + e.printStackTrace(); + } + } + else if (o instanceof String) + return (String)o; + else if (o instanceof String[]) + { + String[] s = (String[])o; + return s.length>0 ? s[0] : null; + } + return null; + } + + /* ------------------------------------------------------------------------------- */ + /** + * @see javax.servlet.ServletRequest#getParameterMap() + */ + public Map getParameterMap() + { + return map; + } + + /* ------------------------------------------------------------------------------- */ + /** + * @see javax.servlet.ServletRequest#getParameterNames() + */ + public Enumeration getParameterNames() + { + return Collections.enumeration(map.keySet()); + } + + /* ------------------------------------------------------------------------------- */ + /** + * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String) + */ + public String[] getParameterValues(String name) + { + List l=map.getValues(name); + if (l==null || l.size()==0) + return new String[0]; + String[] v = new String[l.size()]; + for (int i=0;i=0) + continue; + + if ("content-type".equals(lhdr)) + hasContent=true; + + Enumeration vals = request.getHeaders(hdr); + while (vals.hasMoreElements()) + { + String val = (String)vals.nextElement(); + if (val!=null) + { + connection.addRequestProperty(hdr,val); + context.log("req "+hdr+": "+val); + xForwardedFor|="X-Forwarded-For".equalsIgnoreCase(hdr); + } + } + } + + // Proxy headers + connection.setRequestProperty("Via","1.1 (jetty)"); + if (!xForwardedFor) + connection.addRequestProperty("X-Forwarded-For", + request.getRemoteAddr()); + + // a little bit of cache control + String cache_control = request.getHeader("Cache-Control"); + if (cache_control!=null && + (cache_control.indexOf("no-cache")>=0 || + cache_control.indexOf("no-store")>=0)) + connection.setUseCaches(false); + + // customize Connection + + try + { + connection.setDoInput(true); + + // do input thang! + InputStream in=request.getInputStream(); + if (hasContent) + { + connection.setDoOutput(true); + IO.copy(in,connection.getOutputStream()); + } + + // Connect + connection.connect(); + } + catch (Exception e) + { + context.log("proxy",e); + } + + InputStream proxy_in = null; + + // handler status codes etc. + int code=500; + if (http!=null) + { + proxy_in = http.getErrorStream(); + + code=http.getResponseCode(); + response.setStatus(code, http.getResponseMessage()); + context.log("response = "+http.getResponseCode()); + } + + if (proxy_in==null) + { + try {proxy_in=connection.getInputStream();} + catch (Exception e) + { + context.log("stream",e); + proxy_in = http.getErrorStream(); + } + } + + // clear response defaults. + response.setHeader("Date",null); + response.setHeader("Server",null); + + // set response headers + int h=0; + String hdr=connection.getHeaderFieldKey(h); + String val=connection.getHeaderField(h); + while(hdr!=null || val!=null) + { + String lhdr = hdr!=null?hdr.toLowerCase():null; + if (hdr!=null && val!=null && !_DontProxyHeaders.contains(lhdr)) + response.addHeader(hdr,val); + + context.log("res "+hdr+": "+val); + + h++; + hdr=connection.getHeaderFieldKey(h); + val=connection.getHeaderField(h); + } + response.addHeader("Via","1.1 (jetty)"); + + // Handle + if (proxy_in!=null) + IO.copy(proxy_in,response.getOutputStream()); + + } + } + + + /* ------------------------------------------------------------ */ + public void handleConnect(HttpServletRequest request, + HttpServletResponse response) + throws IOException + { + String uri = request.getRequestURI(); + + context.log("CONNECT: "+uri); + + String port = ""; + String host = ""; + + int c = uri.indexOf(':'); + if (c>=0) + { + port = uri.substring(c+1); + host = uri.substring(0,c); + if (host.indexOf('/')>0) + host = host.substring(host.indexOf('/')+1); + } + + + + + InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port)); + + //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false)) + //{ + // sendForbid(request,response,uri); + //} + //else + { + InputStream in=request.getInputStream(); + OutputStream out=response.getOutputStream(); + + Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort()); + context.log("Socket: "+socket); + + response.setStatus(200); + response.setHeader("Connection","close"); + response.flushBuffer(); + + + + context.log("out<-in"); + IO.copyThread(socket.getInputStream(),out); + context.log("in->out"); + IO.copy(in,socket.getOutputStream()); + } + } + + + + + /* (non-Javadoc) + * @see javax.servlet.Servlet#getServletInfo() + */ + public String getServletInfo() + { + return "Proxy Servlet"; + } + + /* (non-Javadoc) + * @see javax.servlet.Servlet#destroy() + */ + public void destroy() + { + + } +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java new file mode 100644 index 00000000000..ecaac08edeb --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java @@ -0,0 +1,327 @@ +// ======================================================================== +// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.URIUtil; + +/** + * PutFilter + * + * A Filter that handles PUT, DELETE and MOVE methods. + * Files are hidden during PUT operations, so that 404's result. + * + * The following init paramters pay be used:

      + *
    • baseURI - The file URI of the document root for put content. + *
    • delAllowed - boolean, if true DELETE and MOVE methods are supported. + *
    + * + */ +public class PutFilter implements Filter +{ + public final static String __PUT="PUT"; + public final static String __DELETE="DELETE"; + public final static String __MOVE="MOVE"; + public final static String __OPTIONS="OPTIONS"; + + Set _operations = new HashSet(); + protected ConcurrentMap _hidden = new ConcurrentHashMap(); + + protected ServletContext _context; + protected String _baseURI; + protected boolean _delAllowed; + + /* ------------------------------------------------------------ */ + public void init(FilterConfig config) throws ServletException + { + _context=config.getServletContext(); + if (_context.getRealPath("/")==null) + throw new UnavailableException("Packed war"); + + String b = config.getInitParameter("baseURI"); + if (b != null) + { + _baseURI=b; + } + else + { + File base=new File(_context.getRealPath("/")); + _baseURI=base.toURI().toString(); + } + + _delAllowed = getInitBoolean(config,"delAllowed"); + + _operations.add(__OPTIONS); + _operations.add(__PUT); + if (_delAllowed) + { + _operations.add(__DELETE); + _operations.add(__MOVE); + } + } + + /* ------------------------------------------------------------ */ + private boolean getInitBoolean(FilterConfig config,String name) + { + String value = config.getInitParameter(name); + return value != null && value.length() > 0 && (value.startsWith("t") || value.startsWith("T") || value.startsWith("y") || value.startsWith("Y") || value.startsWith("1")); + } + + /* ------------------------------------------------------------ */ + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException + { + + HttpServletRequest request=(HttpServletRequest)req; + HttpServletResponse response=(HttpServletResponse)res; + + String servletPath =request.getServletPath(); + String pathInfo = request.getPathInfo(); + String pathInContext = URIUtil.addPaths(servletPath, pathInfo); + + String resource = URIUtil.addPaths(_baseURI,pathInContext); + + String method = request.getMethod(); + boolean op = _operations.contains(method); + + if (op) + { + File file = null; + try + { + if (method.equals(__OPTIONS)) + handleOptions(request, response); + else + { + file=new File(new URI(resource)); + boolean exists = file.exists(); + if (exists && !passConditionalHeaders(request, response, file)) + return; + + if (method.equals(__PUT)) + handlePut(request, response,pathInContext, file); + else if (method.equals(__DELETE)) + handleDelete(request, response, pathInContext, file); + else if (method.equals(__MOVE)) + handleMove(request, response, pathInContext, file); + else + throw new IllegalStateException(); + } + } + catch(Exception e) + { + _context.log(e.toString(),e); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + else + { + if (isHidden(pathInContext)) + response.sendError(HttpServletResponse.SC_NOT_FOUND); + else + chain.doFilter(request,response); + return; + } + } + + /* ------------------------------------------------------------ */ + private boolean isHidden(String pathInContext) + { + return _hidden.containsKey(pathInContext); + } + + /* ------------------------------------------------------------ */ + public void destroy() + { + } + + /* ------------------------------------------------------------------- */ + public void handlePut(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException + { + boolean exists = file.exists(); + if (pathInContext.endsWith("/")) + { + if (!exists) + { + if (!file.mkdirs()) + response.sendError(HttpServletResponse.SC_FORBIDDEN); + else + { + response.setStatus(HttpServletResponse.SC_CREATED); + response.flushBuffer(); + } + } + else + { + response.setStatus(HttpServletResponse.SC_OK); + response.flushBuffer(); + } + } + else + { + boolean ok=false; + try + { + _hidden.put(pathInContext,pathInContext); + File parent = file.getParentFile(); + parent.mkdirs(); + int toRead = request.getContentLength(); + InputStream in = request.getInputStream(); + OutputStream out = new FileOutputStream(file,false); + if (toRead >= 0) + IO.copy(in, out, toRead); + else + IO.copy(in, out); + out.close(); + + response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED); + response.flushBuffer(); + ok=true; + } + catch (Exception ex) + { + _context.log(ex.toString(),ex); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + finally + { + if (!ok) + { + try + { + if (file.exists()) + file.delete(); + } + catch(Exception e) + { + _context.log(e.toString(),e); + } + } + _hidden.remove(pathInContext); + } + } + } + + /* ------------------------------------------------------------------- */ + public void handleDelete(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException + { + try + { + // delete the file + if (file.delete()) + { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + response.flushBuffer(); + } + else + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + catch (SecurityException sex) + { + _context.log(sex.toString(),sex); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + } + + /* ------------------------------------------------------------------- */ + public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) + throws ServletException, IOException, URISyntaxException + { + String newPath = URIUtil.canonicalPath(request.getHeader("new-uri")); + if (newPath == null) + { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + String contextPath = request.getContextPath(); + if (contextPath != null && !newPath.startsWith(contextPath)) + { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + String newInfo = newPath; + if (contextPath != null) + newInfo = newInfo.substring(contextPath.length()); + + String new_resource = URIUtil.addPaths(_baseURI,newInfo); + File new_file=new File(new URI(new_resource)); + + file.renameTo(new_file); + + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + response.flushBuffer(); + + + } + + /* ------------------------------------------------------------ */ + public void handleOptions(HttpServletRequest request, HttpServletResponse response) throws IOException + { + // TODO implement + throw new UnsupportedOperationException("Not Implemented"); + } + + /* ------------------------------------------------------------ */ + /* + * Check modification date headers. + */ + protected boolean passConditionalHeaders(HttpServletRequest request, HttpServletResponse response, File file) throws IOException + { + long date = 0; + + if ((date = request.getDateHeader("if-unmodified-since")) > 0) + { + if (file.lastModified() / 1000 > date / 1000) + { + response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); + return false; + } + } + + if ((date = request.getDateHeader("if-modified-since")) > 0) + { + if (file.lastModified() / 1000 <= date / 1000) + { + response.reset(); + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + response.flushBuffer(); + return false; + } + } + return true; + } +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/QoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/QoSFilter.java new file mode 100644 index 00000000000..a7c92b269c9 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/QoSFilter.java @@ -0,0 +1,227 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + +import java.io.IOException; +import java.util.Queue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.util.ArrayQueue; + +/** + * Quality of Service Filter. + * This filter limits the number of active requests to the number set by the "maxRequests" init parameter (default 10). + * If more requests are received, they are suspended and placed on priority queues. Priorities are determined by + * the {@link #getPriority(ServletRequest)} method and are a value between 0 and the value given by the "maxPriority" + * init parameter (default 10), with higher values having higher priority. + *

    + * This filter is ideal to prevent wasting threads waiting for slow/limited + * resources such as a JDBC connection pool. It avoids the situation where all of a + * containers thread pool may be consumed blocking on such a slow resource. + * By limiting the number of active threads, a smaller thread pool may be used as + * the threads are not wasted waiting. Thus more memory may be available for use by + * the active threads. + *

    + * Furthermore, this filter uses a priority when resuming waiting requests. So that if + * a container is under load, and there are many requests waiting for resources, + * the {@link #getPriority(ServletRequest)} method is used, so that more important + * requests are serviced first. For example, this filter could be deployed with a + * maxRequest limit slightly smaller than the containers thread pool and a high priority + * allocated to admin users. Thus regardless of load, admin users would always be + * able to access the web application. + *

    + * The maxRequest limit is policed by a {@link Semaphore} and the filter will wait a short while attempting to acquire + * the semaphore. This wait is controlled by the "waitMs" init parameter and allows the expense of a suspend to be + * avoided if the semaphore is shortly available. If the semaphore cannot be obtained, the request will be suspended + * for the default suspend period of the container or the valued set as the "suspendMs" init parameter. + * + * + * + */ +public class QoSFilter implements Filter +{ + final static int __DEFAULT_MAX_PRIORITY=10; + final static int __DEFAULT_PASSES=10; + final static int __DEFAULT_WAIT_MS=50; + final static long __DEFAULT_TIMEOUT_MS = -1; + + final static String MAX_REQUESTS_INIT_PARAM="maxRequests"; + final static String MAX_PRIORITY_INIT_PARAM="maxPriority"; + final static String MAX_WAIT_INIT_PARAM="waitMs"; + final static String SUSPEND_INIT_PARAM="suspendMs"; + + ServletContext _context; + long _waitMs; + long _suspendMs; + Semaphore _passes; + Queue[] _queue; + String _suspended="QoSFilter@"+this.hashCode(); + + public void init(FilterConfig filterConfig) + { + _context=filterConfig.getServletContext(); + + int max_priority=__DEFAULT_MAX_PRIORITY; + if (filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM)!=null) + max_priority=Integer.parseInt(filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM)); + _queue=new Queue[max_priority+1]; + for (int p=0;p<_queue.length;p++) + _queue[p]=new ArrayQueue(); + + int passes=__DEFAULT_PASSES; + if (filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM)!=null) + passes=Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM)); + _passes=new Semaphore(passes,true); + + long wait = __DEFAULT_WAIT_MS; + if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM)!=null) + wait=Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM)); + _waitMs=wait; + + long suspend = __DEFAULT_TIMEOUT_MS; + if (filterConfig.getInitParameter(SUSPEND_INIT_PARAM)!=null) + suspend=Integer.parseInt(filterConfig.getInitParameter(SUSPEND_INIT_PARAM)); + _suspendMs=suspend; + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + boolean accepted=false; + try + { + if (request.getAttribute(_suspended)==null) + { + accepted=_passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS); + if (accepted) + { + request.setAttribute(_suspended,Boolean.FALSE); + } + else + { + request.setAttribute(_suspended,Boolean.TRUE); + if (_suspendMs>0) + request.setAsyncTimeout(_suspendMs); + request.startAsync(); + + int priority = getPriority(request); + _queue[priority].add(request); + return; + } + } + else + { + Boolean suspended=(Boolean)request.getAttribute(_suspended); + + if (suspended.booleanValue()) + { + request.setAttribute(_suspended,Boolean.FALSE); + if (request.getAttribute("javax.servlet.resumed")==Boolean.TRUE) + { + _passes.acquire(); + accepted=true; + } + else + { + // Timeout! try 1 more time. + accepted = _passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS); + } + } + else + { + // pass through resume of previously accepted request + _passes.acquire(); + accepted = true; + } + } + + + if (accepted) + { + chain.doFilter(request,response); + } + else + { + ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + } + + + + } + catch(InterruptedException e) + { + _context.log("QoS",e); + ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + } + finally + { + if (accepted) + { + for (int p=_queue.length;p-->0;) + { + ServletRequest r=_queue[p].poll(); + if (r!=null) + { + r.getAsyncContext().dispatch(); + break; + } + } + _passes.release(); + } + } + } + + /** + * Get the request Priority. + *

    The default implementation assigns the following priorities:

      + *
    • 2 - for a authenticated request + *
    • 1 - for a request with valid /non new session + *
    • 0 - for all other requests. + *
    + * This method may be specialised to provide application specific priorities. + * + * @param request + * @return + */ + protected int getPriority(ServletRequest request) + { + HttpServletRequest base_request = (HttpServletRequest)request; + if (base_request.getUserPrincipal() != null ) + return 2; + else + { + HttpSession session = base_request.getSession(false); + if (session!=null && !session.isNew()) + return 1; + else + return 0; + } + } + + + public void destroy(){} + +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/UserAgentFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/UserAgentFilter.java new file mode 100644 index 00000000000..f865b986e37 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/UserAgentFilter.java @@ -0,0 +1,147 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +/* ------------------------------------------------------------ */ +/** User Agent Filter. + *

    + * This filter allows efficient matching of user agent strings for + * downstream or extended filters to use for browser specific logic. + *

    + *

    + * The filter is configured with the following init parameters: + *

    + *
    attribute
    If set, then the request attribute of this name is set with the matched user agent string
    + *
    cacheSize
    The size of the user-agent cache, used to avoid reparsing of user agent strings. The entire cache is flushed + * when this size is reached
    + *
    userAgent
    A regex {@link Pattern} to extract the essential elements of the user agent. + * The concatenation of matched pattern groups is used as the user agent name
    + *
    + * An example value for pattern is (?:Mozilla[^\(]*\(compatible;\s*+([^;]*);.*)|(?:.*?([^\s]+/[^\s]+).*). These two + * pattern match the common compatibility user-agent strings and extract the real user agent, failing that, the first + * element of the agent string is returned. + * + * + */ +public class UserAgentFilter implements Filter +{ + private Pattern _pattern; + private Map _agentCache = new ConcurrentHashMap(); + private int _agentCacheSize=1024; + private String _attribute; + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.Filter#destroy() + */ + public void destroy() + { + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + if (_attribute!=null && _pattern!=null) + { + String ua=getUserAgent(request); + request.setAttribute(_attribute,ua); + } + chain.doFilter(request,response); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + public void init(FilterConfig filterConfig) throws ServletException + { + _attribute=filterConfig.getInitParameter("attribute"); + + String p=filterConfig.getInitParameter("userAgent"); + if (p!=null) + _pattern=Pattern.compile(p); + + String size=filterConfig.getInitParameter("cacheSize"); + if (size!=null) + _agentCacheSize=Integer.parseInt(size); + } + + /* ------------------------------------------------------------ */ + public String getUserAgent(ServletRequest request) + { + String ua=((HttpServletRequest)request).getHeader("User-Agent"); + return getUserAgent(ua); + } + + /* ------------------------------------------------------------ */ + /** Get UserAgent. + * The configured agent patterns are used to match against the passed user agent string. + * If any patterns match, the concatenation of pattern groups is returned as the user agent + * string. Match results are cached. + * @param ua A user agent string + * @return The matched pattern groups or the original user agent string + */ + public String getUserAgent(String ua) + { + if (ua==null) + return null; + + String tag = (String)_agentCache.get(ua); + + + if (tag==null) + { + Matcher matcher=_pattern.matcher(ua); + if (matcher.matches()) + { + if(matcher.groupCount()>0) + { + for (int g=1;g<=matcher.groupCount();g++) + { + String group=matcher.group(g); + if (group!=null) + tag=tag==null?group:(tag+group); + } + } + else + tag=matcher.group(); + } + else + tag=ua; + + if (_agentCache.size()>=_agentCacheSize) + _agentCache.clear(); + _agentCache.put(ua,tag); + + } + return tag; + } +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java new file mode 100644 index 00000000000..b540700b14a --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java @@ -0,0 +1,65 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +/* ------------------------------------------------------------ */ +/** Welcome Filter + * This filter can be used to server an index file for a directory + * when no index file actually exists (thus the web.xml mechanism does + * not work). + * + * This filter will dispatch requests to a directory (URLs ending with /) + * to the welcome URL determined by the "welcome" init parameter. So if + * the filter "welcome" init parameter is set to "index.do" then a request + * to "/some/directory/" will be dispatched to "/some/directory/index.do" and + * will be handled by any servlets mapped to that URL. + * + * Requests to "/some/directory" will be redirected to "/some/directory/". + */ +public class WelcomeFilter implements Filter +{ + private String welcome; + + public void init(FilterConfig filterConfig) + { + welcome=filterConfig.getInitParameter("welcome"); + if (welcome==null) + welcome="index.html"; + } + + /* ------------------------------------------------------------ */ + public void doFilter(ServletRequest request, + ServletResponse response, + FilterChain chain) + throws IOException, ServletException + { + String path=((HttpServletRequest)request).getServletPath(); + if (welcome!=null && path.endsWith("/")) + request.getRequestDispatcher(path+welcome).forward(request,response); + else + chain.doFilter(request, response); + } + + public void destroy() {} +} + diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java new file mode 100644 index 00000000000..f6c68ed0394 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java @@ -0,0 +1,45 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +public class AsyncProxyServer +{ + public static void main(String[] args) + throws Exception + { + Server server = new Server(); + Connector connector=new SelectChannelConnector(); + connector.setPort(8888); + server.setConnectors(new Connector[]{connector}); + + ServletHandler handler=new ServletHandler(); + server.setHandler(handler); + + FilterHolder gzip = handler.addFilterWithMapping("org.eclipse.jetty.servlet.GzipFilter","/*",0); + gzip.setAsyncSupported(true); + gzip.setInitParameter("minGzipSize","256"); + ServletHolder proxy = handler.addServletWithMapping("org.eclipse.proxy.AsyncProxyServlet","/"); + proxy.setAsyncSupported(true); + + server.start(); + server.join(); + } +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java new file mode 100644 index 00000000000..deb68aa7a86 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java @@ -0,0 +1,250 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlets; + +import java.io.File; +import java.io.FileInputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.URL; + +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.testing.HttpTester; +import org.eclipse.jetty.testing.ServletTester; +import org.eclipse.jetty.util.IO; + +public class PutFilterTest extends TestCase +{ + File _dir; + ServletTester tester; + + protected void setUp() throws Exception + { + _dir = File.createTempFile("testPutFilter",null); + _dir.delete(); + _dir.mkdir(); + _dir.deleteOnExit(); + assertTrue(_dir.isDirectory()); + + super.setUp(); + tester=new ServletTester(); + tester.setContextPath("/context"); + tester.setResourceBase(_dir.getCanonicalPath()); + tester.addServlet(org.eclipse.jetty.servlet.DefaultServlet.class, "/"); + FilterHolder holder = tester.addFilter(PutFilter.class,"/*",0); + holder.setInitParameter("delAllowed","true"); + tester.start(); + + + } + + protected void tearDown() throws Exception + { + super.tearDown(); + } + + public void testHandlePut() throws Exception + { + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + + // test GET + request.setMethod("GET"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/file.txt"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_NOT_FOUND,response.getStatus()); + + // test PUT0 + request.setMethod("PUT"); + request.setURI("/context/file.txt"); + request.setHeader("Content-Type","text/plain"); + String data0="Now is the time for all good men to come to the aid of the party"; + request.setContent(data0); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_CREATED,response.getStatus()); + + File file=new File(_dir,"file.txt"); + assertTrue(file.exists()); + assertEquals(data0,IO.toString(new FileInputStream(file))); + + // test GET1 + request.setMethod("GET"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/file.txt"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + assertEquals(data0,response.getContent()); + + // test PUT1 + request.setMethod("PUT"); + request.setURI("/context/file.txt"); + request.setHeader("Content-Type","text/plain"); + String data1="How Now BROWN COW!!!!"; + request.setContent(data1); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + + file=new File(_dir,"file.txt"); + assertTrue(file.exists()); + assertEquals(data1,IO.toString(new FileInputStream(file))); + + + + // test PUT2 + request.setMethod("PUT"); + request.setURI("/context/file.txt"); + request.setHeader("Content-Type","text/plain"); + String data2="Blah blah blah Blah blah"; + request.setContent(data2); + String to_send = request.generate(); + URL url = new URL(tester.createSocketConnector(true)); + Socket socket=new Socket(url.getHost(),url.getPort()); + OutputStream out = socket.getOutputStream(); + int l = to_send.length(); + out.write(to_send.substring(0,l-10).getBytes()); + out.flush(); + out.write(to_send.substring(l-10,l-5).getBytes()); + out.flush(); + Thread.sleep(100); + + // test GET + request.setMethod("GET"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/file.txt"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_NOT_FOUND,response.getStatus()); + + out.write(to_send.substring(l-5).getBytes()); + out.flush(); + String in=IO.toString(socket.getInputStream()); + + request.setMethod("GET"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/file.txt"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + assertEquals(data2,response.getContent()); + + + } + + public void testHandleDelete() throws Exception + { + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + + // test PUT1 + request.setMethod("PUT"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/file.txt"); + request.setHeader("Content-Type","text/plain"); + String data1="How Now BROWN COW!!!!"; + request.setContent(data1); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_CREATED,response.getStatus()); + + File file=new File(_dir,"file.txt"); + assertTrue(file.exists()); + FileInputStream fis = new FileInputStream(file); + assertEquals(data1,IO.toString(fis)); + fis.close(); + + + request.setMethod("DELETE"); + request.setURI("/context/file.txt"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_NO_CONTENT,response.getStatus()); + + + assertTrue(!file.exists()); + + request.setMethod("DELETE"); + request.setURI("/context/file.txt"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_FORBIDDEN,response.getStatus()); + + + } + + public void testHandleMove() throws Exception + { + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + + // test PUT1 + request.setMethod("PUT"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/file.txt"); + request.setHeader("Content-Type","text/plain"); + String data1="How Now BROWN COW!!!!"; + request.setContent(data1); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_CREATED,response.getStatus()); + + File file=new File(_dir,"file.txt"); + assertTrue(file.exists()); + FileInputStream fis = new FileInputStream(file); + assertEquals(data1,IO.toString(fis)); + fis.close(); + + + request.setMethod("MOVE"); + request.setURI("/context/file.txt"); + request.setHeader("new-uri","/context/blah.txt"); + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_NO_CONTENT,response.getStatus()); + + assertTrue(!file.exists()); + + File n_file=new File(_dir,"blah.txt"); + assertTrue(n_file.exists()); + + } + + public void testHandleOptions() + { + // TODO implement + } + + public void testPassConditionalHeaders() + { + // TODO implement + } + +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java new file mode 100644 index 00000000000..a1f5e23b2b2 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java @@ -0,0 +1,235 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.servlets; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.FilterMapping; +import org.eclipse.jetty.testing.HttpTester; +import org.eclipse.jetty.testing.ServletTester; +import org.eclipse.jetty.util.log.Log; + +public class QoSFilterTest extends TestCase +{ + private ServletTester _tester; + private LocalConnector[] _connectors; + private CountDownLatch _doneRequests; + private final int NUM_CONNECTIONS = 8; + private final int NUM_LOOPS = 6; + private final int MAX_QOS = 4; + + protected void setUp() throws Exception + { + _tester = new ServletTester(); + _tester.setContextPath("/context"); + _tester.addServlet(TestServlet.class, "/test"); + TestServlet.__maxSleepers=0; + TestServlet.__sleepers=0; + + _connectors = new LocalConnector[NUM_CONNECTIONS]; + for(int i = 0; i < _connectors.length; ++i) + _connectors[i] = _tester.createLocalConnector(); + + _doneRequests = new CountDownLatch(NUM_CONNECTIONS*NUM_LOOPS); + + _tester.start(); + } + + protected void tearDown() throws Exception + { + _tester.stop(); + } + + public void testNoFilter() throws Exception + { + for(int i = 0; i < NUM_CONNECTIONS; ++i ) + { + new Thread(new Worker(i)).start(); + } + + _doneRequests.await(10,TimeUnit.SECONDS); + + assertFalse("TEST WAS NOT PARALLEL ENOUGH!",TestServlet.__maxSleepers<=MAX_QOS); + assertTrue(TestServlet.__maxSleepers<=NUM_CONNECTIONS); + } + + public void testBlockingQosFilter() throws Exception + { + FilterHolder holder = new FilterHolder(QoSFilter2.class); + holder.setAsyncSupported(true); + holder.setInitParameter(QoSFilter.MAX_REQUESTS_INIT_PARAM, ""+MAX_QOS); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",FilterMapping.DEFAULT); + + for(int i = 0; i < NUM_CONNECTIONS; ++i ) + { + new Thread(new Worker(i)).start(); + } + + _doneRequests.await(10,TimeUnit.SECONDS); + assertFalse("TEST WAS NOT PARALLEL ENOUGH!",TestServlet.__maxSleepers __maxSleepers) + __maxSleepers = __sleepers; + } + + Thread.sleep(50); + + synchronized(TestServlet.class) + { + // System.err.println(_count++); + __sleepers--; + if(__sleepers > __maxSleepers) + __maxSleepers = __sleepers; + } + + response.setContentType("text/plain"); + response.getWriter().println("DONE!"); + } + catch (InterruptedException e) + { + e.printStackTrace(); + response.sendError(500); + } + } + } + + public static class QoSFilter2 extends QoSFilter + { + public int getPriority(ServletRequest request) + { + String p = ((HttpServletRequest)request).getParameter("priority"); + if (p!=null) + return Integer.parseInt(p); + return 0; + } + } + +} diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml new file mode 100644 index 00000000000..0a2e86ee9b3 --- /dev/null +++ b/jetty-start/pom.xml @@ -0,0 +1,28 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-start + Jetty :: Start + The jetty startup artifact. + + + + maven-jar-plugin + + + + org.eclipse.jetty.start.Main + + + + + + + + start.jar + + diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java new file mode 100644 index 00000000000..5f39f25124d --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java @@ -0,0 +1,201 @@ +// ======================================================================== +// Copyright (c) 2002-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.start; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.StringTokenizer; +import java.util.Vector; + + +/** + * Class to handle CLASSPATH construction + * + */ +public class Classpath { + + Vector _elements = new Vector(); + + public Classpath() + {} + + public Classpath(String initial) + { + addClasspath(initial); + } + + public File[] getElements() + { + return (File[])_elements.toArray(new File[_elements.size()]); + } + + public boolean addComponent(String component) + { + if ((component != null)&&(component.length()>0)) { + try { + File f = new File(component); + if (f.exists()) + { + File key = f.getCanonicalFile(); + if (!_elements.contains(key)) + { + _elements.add(key); + return true; + } + } + } catch (IOException e) {} + } + return false; + } + + public boolean addComponent(File component) + { + if (component != null) { + try { + if (component.exists()) { + File key = component.getCanonicalFile(); + if (!_elements.contains(key)) { + _elements.add(key); + return true; + } + } + } catch (IOException e) {} + } + return false; + } + + public boolean addClasspath(String s) + { + boolean added=false; + if (s != null) + { + StringTokenizer t = new StringTokenizer(s, File.pathSeparator); + while (t.hasMoreTokens()) + { + added|=addComponent(t.nextToken()); + } + } + return added; + } + + public String toString() + { + StringBuffer cp = new StringBuffer(1024); + int cnt = _elements.size(); + if (cnt >= 1) { + cp.append( ((File)(_elements.elementAt(0))).getPath() ); + } + for (int i=1; i < cnt; i++) { + cp.append(File.pathSeparatorChar); + cp.append( ((File)(_elements.elementAt(i))).getPath() ); + } + return cp.toString(); + } + + public ClassLoader getClassLoader() { + int cnt = _elements.size(); + URL[] urls = new URL[cnt]; + for (int i=0; i < cnt; i++) { + try { + String u=((File)(_elements.elementAt(i))).toURL().toString(); + urls[i] = new URL(encodeFileURL(u)); + } catch (MalformedURLException e) {} + } + + ClassLoader parent = Thread.currentThread().getContextClassLoader(); + if (parent == null) { + parent = Classpath.class.getClassLoader(); + } + if (parent == null) { + parent = ClassLoader.getSystemClassLoader(); + } + return new Loader(urls, parent); + } + + private class Loader extends URLClassLoader + { + String name; + + Loader(URL[] urls, ClassLoader parent) + { + super(urls, parent); + name = "StartLoader"+Arrays.asList(urls); + } + + public String toString() + { + return name; + } + } + + public static String encodeFileURL(String path) + { + byte[] bytes; + try + { + bytes=path.getBytes("utf-8"); + } + catch (UnsupportedEncodingException e) + { + bytes=path.getBytes(); + } + + StringBuffer buf = new StringBuffer(bytes.length*2); + buf.append("file:"); + + synchronized(buf) + { + for (int i=5;i='a' && b<='z' || b>='A' && b<='Z' || b>='0' && b<='9') + { + buf.append((char)b); + continue; + } + } + buf.append('%'); + buf.append(Integer.toHexString((0xf0&(int)b)>>4)); + buf.append(Integer.toHexString((0x0f&(int)b))); + continue; + } + } + } + + return buf.toString(); + } +} diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java new file mode 100644 index 00000000000..6088b87d0ad --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -0,0 +1,698 @@ +// ======================================================================== +// Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.start; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.Policy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; + +/*-------------------------------------------*/ +/** + * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the + * start.jar archive. It allows an application to be started with the command "java -jar + * start.jar". The behaviour of Main is controlled by the "org/eclipse/start/start.config" file + * obtained as a resource or file. This can be overridden with the START system property. The + * format of each line in this file is: + * + *
    + * 
    + * SUBJECT [ [!] CONDITION [AND|OR] ]*
    + * 
    + * 
    + * + * where SUBJECT: + * + *
    + * ends with ".class" is the Main class to run.
    + * ends with ".xml" is a configuration file for the command line
    + * ends with "/" is a directory from which add all jar and zip files from.
    + * ends with "/*" is a directory from which add all unconsidered jar and zip files from.
    + * Containing = are used to assign system properties.
    + * all other subjects are treated as files to be added to the classpath.
    + * 
    + * + * Subjects may include system properties with $(propertyname) syntax. The $(version) property is + * defined as the maven version of the start.jar. File subjects starting with + * "/" are considered absolute, all others are relative to the home directory. + *

    + * CONDITION is one of: + * + *

    + * 
    + * always
    + * never
    + * available package.class 
    + * java OPERATOR n.n 
    + * nargs OPERATOR n
    + * OPERATOR := one of "<",">"," <=",">=","==","!="
    + * 
    + * 
    + * + * CONTITIONS can be combined with AND OR or !, with AND being the assume operator for a list of + * CONDITIONS. Classpath operations are evaluated on the fly, so once a class or jar is added to + * the classpath, subsequent available conditions will see that class. The system parameter + * CLASSPATH, if set is given to the start classloader before any paths from the configuration + * file. Programs started with start.jar may be stopped with the stop.jar, which connects via a + * local port to stop the server. The default port can be set with the STOP.PORT system property (a + * port of < 0 disables the stop mechanism). If the STOP.KEY system property is set, then a random + * key is generated and written to stdout. This key must be passed to the stop.jar. + *

    + * The configuration file may be divided into sections with option names like: + *

    + * [ssl,default]
    + * 
    + * Clauses after a section header will only be included if they match one of the tags in the + * options property. By default options are set to "default,*" or the OPTIONS property may + * be used to pass in a list of tags, eg. : + *
    + *  java -DOPTIONS=jetty,jsp,ssl -jar start.jar
    + * 
    + * The tag '*' is always appended to the options, so any section with the * tag is always + * applied. + * + * + * + * + */ +public class Main +{ + private static final String _version = (Main.class.getPackage()!=null && Main.class.getPackage().getImplementationVersion()!=null) + ?Main.class.getPackage().getImplementationVersion() + :"7.0-SNAPSHOT"; + + static boolean _debug=System.getProperty("DEBUG",null)!=null; + private String _classname=null; + private Classpath _classpath=new Classpath(); + private String _config=System.getProperty("START","org/eclipse/jetty/start/start.config"); + private ArrayList _xml=new ArrayList(); + private boolean _showVersions=false; + private Set _options = new HashSet(); + + public static void main(String[] args) + { + try + { + if (args.length>0&&args[0].equalsIgnoreCase("--help")) + { + usage(); + } + else if (args.length>0&&args[0].equalsIgnoreCase("--stop")) + { + new Main().stop(); + } + else if (args.length>0&&(args[0].equalsIgnoreCase("--version")||args[0].equalsIgnoreCase("--info"))) + { + String[] nargs=new String[args.length-1]; + System.arraycopy(args,1,nargs,0,nargs.length); + Main main=new Main(); + main._showVersions=true; + main.start(nargs); + } + else + { + new Main().start(args); + } + } + catch (Exception e) + { + e.printStackTrace(); + usage(); + } + } + + private static void usage() + { + System.err.println("Usage: java [-DDEBUG] [-DSTART=start.config] [-DOPTIONS=opts] [-Dmain.class=org.MyMain] -jar start.jar [--help|--stop|--version|--info] [config ...]"); + System.exit(1); + } + + static File getDirectory(String name) + { + try + { + if (name!=null) + { + File dir=new File(name).getCanonicalFile(); + if (dir.isDirectory()) + { + return dir; + } + } + } + catch (IOException e) + { + } + return null; + } + + boolean isAvailable(String classname) + { + try + { + Class.forName(classname); + return true; + } + catch (NoClassDefFoundError e) + { + } + catch (ClassNotFoundException e) + { + } + ClassLoader loader=_classpath.getClassLoader(); + try + { + loader.loadClass(classname); + return true; + } + catch (NoClassDefFoundError e) + { + } + catch (ClassNotFoundException e) + { + } + return false; + } + + public void invokeMain(ClassLoader classloader, String classname, String[] args) throws IllegalAccessException, InvocationTargetException, + NoSuchMethodException, ClassNotFoundException + { + Class invoked_class=null; + + try + { + invoked_class=classloader.loadClass(classname); + } + catch(ClassNotFoundException e) + { + //ignored + } + + if (_debug || _showVersions || invoked_class==null) + { + if (invoked_class==null) + System.err.println("ClassNotFound: "+classname); + else + System.err.println(classname+" "+invoked_class.getPackage().getImplementationVersion()); + File[] elements = _classpath.getElements(); + for (int i=0;i0; + } + else if (condition.equals("java")) + { + String operator=st.nextToken(); + String version=st.nextToken(); + ver.parse(version); + eval=(operator.equals("<")&&java_version.compare(ver)<0)||(operator.equals(">")&&java_version.compare(ver)>0) + ||(operator.equals("<=")&&java_version.compare(ver)<=0)||(operator.equals("=<")&&java_version.compare(ver)<=0) + ||(operator.equals("=>")&&java_version.compare(ver)>=0)||(operator.equals(">=")&&java_version.compare(ver)>=0) + ||(operator.equals("==")&&java_version.compare(ver)==0)||(operator.equals("!=")&&java_version.compare(ver)!=0); + } + else if (condition.equals("nargs")) + { + String operator=st.nextToken(); + int number=Integer.parseInt(st.nextToken()); + eval=(operator.equals("<")&&nargs")&&nargs>number)||(operator.equals("<=")&&nargs<=number) + ||(operator.equals("=<")&&nargs<=number)||(operator.equals("=>")&&nargs>=number)||(operator.equals(">=")&&nargs>=number) + ||(operator.equals("==")&&nargs==number)||(operator.equals("!=")&&nargs!=number); + } + else + { + System.err.println("ERROR: Unknown condition: "+condition); + eval=false; + } + expression&=not?!eval:eval; + not=false; + } + String file=expand(subject).replace('/',File.separatorChar); + if (_debug) + System.err.println((expression?"T ":"F ")+line); + if (!expression) + { + done.put(file,file); + continue; + } + // Handle the subject + if (subject.indexOf("=")>0) + { + int i=file.indexOf("="); + String property=file.substring(0,i); + String value=file.substring(i+1); + if (_debug) + System.err.println(" "+property+"="+value); + System.setProperty(property,value); + } + else if (subject.endsWith("/*")) + { + // directory of JAR files - only add jars and zips + // within the directory + File dir=new File(file.substring(0,file.length()-1)); + addJars(dir,done,false); + } + else if (subject.endsWith("/**")) + { + //directory hierarchy of jar files - recursively add all + //jars and zips in the hierarchy + File dir=new File(file.substring(0,file.length()-2)); + addJars(dir,done,true); + } + else if (subject.endsWith("/")) + { + // class directory + File cd=new File(file); + String d=cd.getCanonicalPath(); + if (!done.containsKey(d)) + { + done.put(d,d); + boolean added=_classpath.addComponent(d); + if (_debug) + System.err.println((added?" CLASSPATH+=":" !")+d); + } + } + else if (subject.toLowerCase().endsWith(".xml")) + { + // Config file + File f=new File(file); + if (f.exists()) + _xml.add(f.getCanonicalPath()); + if (_debug) + System.err.println(" ARGS+="+f); + } + else if (subject.toLowerCase().endsWith(".class")) + { + // Class + String cn=expand(subject.substring(0,subject.length()-6)); + if (cn!=null&&cn.length()>0) + { + if (_debug) + System.err.println(" CLASS="+cn); + _classname=cn; + } + } + else if (subject.toLowerCase().endsWith(".path")) + { + //classpath (jetty.class.path?) to add to runtime classpath + String cn=expand(subject.substring(0,subject.length()-5)); + if (cn!=null&&cn.length()>0) + { + if (_debug) + System.err.println(" PATH="+cn); + _classpath.addClasspath(cn); + } + } + else + { + // single JAR file + File f=new File(file); + if(f.exists()) + { + String d=f.getCanonicalPath(); + if (!done.containsKey(d)) + { + done.put(d,d); + boolean added=_classpath.addComponent(d); + if (!added) + { + added=_classpath.addClasspath(expand(subject)); + if (_debug) + System.err.println((added?" CLASSPATH+=":" !")+d); + } + else if (_debug) + System.err.println((added?" CLASSPATH+=":" !")+d); + } + } + } + } + catch (Exception e) + { + System.err.println("on line: '"+line+"'"); + e.printStackTrace(); + } + } + + if (unsatisfiedOptions!=null && unsatisfiedOptions.size()>0) + { + String home = System.getProperty("jetty.home"); + String lib = System.getProperty("jetty.lib"); + File libDir = null; + if (lib!=null) + { + libDir = new File (lib); + } + else if (home != null) + { + libDir = new File (home, "lib"); + } + + for (int i=0; i< unsatisfiedOptions.size(); i++) + { + if (libDir != null) + { + File dir = new File (libDir, (String)unsatisfiedOptions.get(i)); + if (dir.exists()) + addJars(dir,done,true); + else + if (_debug) + System.err.println("Unsatisfied option:"+unsatisfiedOptions.get(i)); + } + else + if (_debug) + System.err.println("Unsatisfied option:"+unsatisfiedOptions.get(i)); + } + } + } + + /* ------------------------------------------------------------ */ + public void start(String[] args) + { + ArrayList al=new ArrayList(); + for (int i=0; i + * If the stop port is set to zero, then a random port is assigned and the port number + * is printed to stdout. + *

    + * Commands "stop" and * "status" are currently supported. + * + */ +public class Monitor extends Thread +{ + private int _port = Integer.getInteger("STOP.PORT", -1).intValue(); + private String _key = System.getProperty("STOP.KEY", null); + + ServerSocket _socket; + + Monitor() + { + try + { + if(_port<0) + return; + setDaemon(true); + setName("StopMonitor"); + _socket=new ServerSocket(_port,1,InetAddress.getByName("127.0.0.1")); + if (_port==0) + { + _port=_socket.getLocalPort(); + System.out.println(_port); + } + + if (_key==null) + { + _key=Long.toString((long)(Long.MAX_VALUE*Math.random()+this.hashCode()+System.currentTimeMillis()),36); + System.out.println("-DSTOP.KEY="+_key); + } + } + catch(Exception e) + { + if (Main._debug) + e.printStackTrace(); + else + System.err.println(e.toString()); + } + if (_socket!=null) + this.start(); + else + System.err.println("WARN: Not listening on monitor port: "+_port); + } + + public void run() + { + while (true) + { + Socket socket=null; + try{ + socket=_socket.accept(); + + LineNumberReader lin= + new LineNumberReader(new InputStreamReader(socket.getInputStream())); + String key=lin.readLine(); + if (!_key.equals(key)) + continue; + + String cmd=lin.readLine(); + if (Main._debug) System.err.println("command="+cmd); + if ("stop".equals(cmd)) + { + try {socket.close();}catch(Exception e){e.printStackTrace();} + try {_socket.close();}catch(Exception e){e.printStackTrace();} + System.exit(0); + } + else if ("status".equals(cmd)) + { + socket.getOutputStream().write("OK\r\n".getBytes()); + socket.getOutputStream().flush(); + } + } + catch(Exception e) + { + if (Main._debug) + e.printStackTrace(); + else + System.err.println(e.toString()); + } + finally + { + if (socket!=null) + { + try{socket.close();}catch(Exception e){} + } + socket=null; + } + } + } + + /** Start a Monitor. + * This static method starts a monitor that listens for admin requests. + */ + public static void monitor() + { + new Monitor(); + } + +} diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt b/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt new file mode 100644 index 00000000000..a7e630d9489 --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt @@ -0,0 +1,38 @@ +Jetty start +----------- + +Jetty start provides a cross platform replacement for startup scripts. +It makes use of executable JAR mechanism, which lets application packaged as JAR +to be started with simple command line: + + java -jar start.jar [jetty.xml ... ] + +or to see debug output + + java -Dorg.eclipse.jetty.launcher.debug=true -jar start.jar [jetty.xml ... ] + +What launcher does is: + +- Figures out correct location of Jetty home directory. + +- Configures classpath based on which JDK version it has been run with +and what classes are available (for example, someone might have servlet classes +in ext directory of JDK, or JSSE needs to be inlcuded on JDK 1.3 but is +built-in in JDK 1.4 etc.) +- Looks for Sun's JAVAC (required for Jasper JSP engine) +and puts it in classpath. For this to work, launcher has to be started +with JDK, not JRE! +- After classpath has been configured, it invokes org.eclipse.jetty.Server +with any command line arguments it received. +- When there are no commandline args, launcher starts Jetty Demo +(using configuration files etc/demo.xml and etc/admin.xml) and on Windows +platform it also attemts to invoke Internet Explorer with jetty demo URL +(http://localhost:8080/). + +This means Windows users who have file type association for JAR files +setup to launch jar using JDK can now lauch jetty with simple +doubleclick on start.jar, or typing "start.jar" in shell window when in Jetty +home directory. + +Any unknown JARs found in ext subdirectory of Jetty home will be +added to classpath. Users can place libraries common to multiple contexts there. diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java new file mode 100644 index 00000000000..d1bc4b9cb5e --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java @@ -0,0 +1,109 @@ +// ======================================================================== +// Copyright (c) 2002-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.start; + +/** + * Utility class for parsing and comparing version strings. + * JDK 1.1 compatible. + * + */ + +public class Version { + + int _version = 0; + int _revision = 0; + int _subrevision = 0; + String _suffix = ""; + + public Version() { + } + + public Version(String version_string) { + parse(version_string); + } + + /** + * parses version string in the form version[.revision[.subrevision[extension]]] + * into this instance. + */ + public void parse(String version_string) { + _version = 0; + _revision = 0; + _subrevision = 0; + _suffix = ""; + int pos = 0; + int startpos = 0; + int endpos = version_string.length(); + while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) { + pos++; + } + _version = Integer.parseInt(version_string.substring(startpos,pos)); + if ((pos < endpos) && version_string.charAt(pos)=='.') { + startpos = ++pos; + while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) { + pos++; + } + _revision = Integer.parseInt(version_string.substring(startpos,pos)); + } + if ((pos < endpos) && version_string.charAt(pos)=='.') { + startpos = ++pos; + while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) { + pos++; + } + _subrevision = Integer.parseInt(version_string.substring(startpos,pos)); + } + if (pos < endpos) { + _suffix = version_string.substring(pos); + } + } + + /** + * @return string representation of this version + */ + public String toString() { + StringBuffer sb = new StringBuffer(10); + sb.append(_version); + sb.append('.'); + sb.append(_revision); + sb.append('.'); + sb.append(_subrevision); + sb.append(_suffix); + return sb.toString(); + } + + // java.lang.Comparable is Java 1.2! Cannot use it + /** + * Compares with other version. Does not take extension into account, + * as there is no reliable way to order them. + * @return -1 if this is older version that other, + * 0 if its same version, + * 1 if it's newer version than other + */ + public int compare(Version other) { + if (other == null) throw new NullPointerException("other version is null"); + if (this._version < other._version) return -1; + if (this._version > other._version) return 1; + if (this._revision < other._revision) return -1; + if (this._revision > other._revision) return 1; + if (this._subrevision < other._subrevision) return -1; + if (this._subrevision > other._subrevision) return 1; + return 0; + } + + /** + * Check whether this verion is in range of versions specified + */ + public boolean isInRange(Version low, Version high) { + return (compare(low)>=0 && compare(high)<=0); + } +} diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config b/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config new file mode 100644 index 00000000000..24af2b7013c --- /dev/null +++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config @@ -0,0 +1,141 @@ +# This file controls what file are to be put on classpath or command line. +# +# Format is as follows: +# Each line contains entry for one JAR file. +# Format of line: +# +# SUBJECT [ [!] CONDITION [AND|OR] ]* +# +# where SUBJECT: +# ends with ".class" is the Main class to run. +# ends with ".xml" is a configuration file for the command line +# ends with "/" is a directory from which to add all jar and zip files. +# ends with "/*" is a directory from which to add all unconsidered jar and zip files. +# ends with "/**" is a directory from which to recursively add all unconsidered jar and zip files. +# Containing = are used to assign system properties. +# all other subjects are treated as files to be added to the classpath. +# +# Subjects may include system properties with $(propertyname) syntax. The +# property version is defined as the version of the start.jar +# +# Files starting with "/" are considered absolute, all others are relative to +# the home directory. +# +# CONDITION is one of: +# always +# never +# available classname # true if class on classpath +# property name # true if set +# exists file # true if file/dir exists +# java OPERATOR version # java version compared to literal +# nargs OPERATOR number # number of command line args compared to literal +# OPERATOR := one of "<",">","<=",">=","==","!=" +# +# CONTITIONS can be combined with AND OR or !, with AND being the assume +# operator for a list of CONDITIONS. +# Classpath operations are evaluated on the fly, so once a class or jar is +# added to the classpath, subsequent available conditions will see that class. +# + +# add a property defined classpath +$(jetty.class.path).path always + +# add a property defined library directory +$(jetty.lib)/** exists $(jetty.lib) + +# find a maven repository +repository=./repository ! exists $(repository)/org/eclipse/jetty/jetty-start/$(version)/jetty-start-$(version).jar +repository=$(user.home)/.m2/repository ! exists $(repository)/org/eclipse/jetty/jetty-start/$(version)/jetty-start-$(version).jar +repository=./repository ! exists $(repository)/org/eclipse/jetty/jetty-start/$(version)/jetty-start-$(version).jar + +# Try different settings of jetty.home until the jetty.jar is found. +jetty.home=. ! exists $(jetty.home)/start.jar +jetty.home=.. ! exists $(jetty.home)/start.jar +jetty.home=jetty-assembly/src/main/resources ! exists $(jetty.home)/start.jar +jetty.home=../jetty-assembly/src/main/resources ! exists $(jetty.home)/start.jar +jetty.home=. ! exists $(jetty.home)/start.jar + +# The main class to run +org.eclipse.jetty.xml.XmlConfiguration.class +$(start.class).class + +# The default configuration files +$(jetty.home)/etc/jetty.xml nargs == 0 +./jetty-server/src/main/config/etc/jetty.xml nargs == 0 AND ! exists $(jetty.home)/etc/jetty.xml + +# Add a ext lib directory if it is there +$(jetty.home)/lib/ext/** + +# Add a resources directory if it is there +$(jetty.home)/resources/ + +# Add servlet api +[servlet-api,default,server] +$(jetty.home)/lib/servlet-api-3.0-SNAPSHOT.jar ! available javax.servlet.ServletContext +$(repository)/org/eclipse/jetty/servlet-api/3.0-SNAPSHOT/servlet-api-3.0-SNAPSHOT.jar ! available javax.servlet.ServletContext + +# Add jetty modules +[jetty-util,*] +$(jetty.home)/lib/jetty-util-$(version).jar ! available org.eclipse.jetty.util.StringUtil +$(repository)/org/eclipse/jetty/jetty-util/$(version)/jetty-util-$(version).jar ! available org.eclipse.jetty.util.StringUtil + +[io,*] +$(jetty.home)/lib/jetty-io-$(version).jar ! available org.eclipse.jetty.io.Buffer +$(repository)/org/eclipse/jetty/jetty-io/$(version)/jetty-io-$(version).jar ! available org.eclipse.jetty.io.Buffer + +[xml,*] +$(jetty.home)/lib/jetty-xml-$(version).jar ! available org.eclipse.jetty.xml.XmlParser +$(repository)/org/eclipse/jetty/jetty-xml/$(version)/jetty-xml-$(version).jar ! available org.eclipse.jetty.xml.XmlParser + +[http,default,Server,Client] +$(jetty.home)/lib/jetty-http-$(version).jar ! available org.eclipse.jetty.http.HttpParser +$(repository)/org/eclipse/jetty/jetty-http/$(version)/jetty-http-$(version).jar ! available org.eclipse.jetty.http.HttpParser + +[server,default,Server] +$(jetty.home)/lib/jetty-server-$(version).jar ! available org.eclipse.jetty.server.Server +$(repository)/org/eclipse/jetty/jetty-server/$(version)/jetty-server-$(version).jar ! available org.eclipse.jetty.server.Server + +[security,default,Server] +$(jetty.home)/lib/jetty-security-$(version).jar ! available org.eclipse.jetty.security.LoginService +$(repository)/org/eclipse/jetty/jetty-security/$(version)/jetty-security-$(version).jar ! available org.eclipse.jetty.security.LoginService + +[servlet,default,Server] +$(jetty.home)/lib/jetty-servlet-$(version).jar ! available org.eclipse.jetty.servlet.ServletHandler +$(repository)/org/eclipse/jetty/jetty-servlet/$(version)/jetty-servlet-$(version).jar ! available org.eclipse.jetty.servlet.ServletHandler + +[webapp,default,Server] +$(jetty.home)/lib/jetty-webapp-$(version).jar ! available org.eclipse.jetty.webapp.WebAppContext +$(repository)/org/eclipse/jetty/jetty-webapp/$(version)/jetty-webapp-$(version).jar ! available org.eclipse.jetty.webapp.WebAppContext + +[deploy,default,Server] +$(jetty.home)/lib/jetty-deploy-$(version).jar ! available org.eclipse.jetty.deploy.ContextDeployer +$(repository)/org/eclipse/jetty/jetty-deploy/$(version)/jetty-deploy-$(version).jar ! available org.eclipse.jetty.deploy.ContextDeployer + +[client,Client] +$(jetty.home)/lib/jetty-client-$(version).jar ! available org.eclipse.jetty.client.HttpClient +$(repository)/org/eclipse/jetty/jetty-client/$(version)/jetty-client-$(version).jar ! available org.eclipse.jetty.client.HttpClient + +[servlets,default,Server] +$(jetty.home)/lib/jetty-servlets-$(version).jar ! available org.eclipse.jetty.servlets.WelcomeFilter +$(repository)/org/eclipse/jetty/jetty-servlets/$(version)/jetty-servlets-$(version).jar ! available org.eclipse.jetty.servlets.WelcomeFilter + +[rewrite] +$(jetty.home)/lib/jetty-rewrite-$(version).jar ! available org.eclipse.jetty.rewrite.handler.RewriteHandler +$(repository)/org/eclipse/jetty/jetty-rewrite/$(version)/jetty-rewrite-$(version).jar ! available org.eclipse.jetty.rewrite.handler.RewriteHandler + +[jmx] +$(jetty.home)/lib/jetty-jmx-$(version).jar ! available org.eclipse.jetty.jmx.MBeanContainer +$(repository)/org/eclipse/jetty/jetty-jmx/$(version)/jetty-jmx-$(version).jar ! available org.eclipse.jetty.jmx.MBeanContainer + +[slf4j] +$(jetty.home)/lib/slf4j/** exists $(jetty.home)/lib/slf4j + +[jsp,jsp-2.1] +$(jetty.home)/lib/jsp-2.1/** exists $(jetty.home)/lib/jsp-2.1 + + + + + + + diff --git a/jetty-test-webapp/jetty-chat.jmx b/jetty-test-webapp/jetty-chat.jmx new file mode 100644 index 00000000000..088b7e0c485 --- /dev/null +++ b/jetty-test-webapp/jetty-chat.jmx @@ -0,0 +1,318 @@ + + + + + + + + false + false + + + + + 1165945030000 + + + 400 + false + + 1 + false + + 1165945030000 + continue + 10 + + + + + + = + 50 + rooms + + + = + + + + + + + + /test/chat/ + localhost + http + + + + 8080 + + + + true + + + + + + GET + true + + true + + + + + = + poll + true + ajax + false + + + = + poll + true + message + false + + + = + 0 + true + timeout + false + + + = + ${__javaScript(${__threadNum}%${rooms},room)} + true + room + false + + + + + + false + + + false + + + + + POST + true + + false + + + + + = + join + true + ajax + false + + + = + Elvis${__threadNum} + true + message + false + + + = + ${__javaScript(${__threadNum}%${rooms},room)} + true + room + false + + + + + + false + + + false + + + + -1 + true + + + + ${__Random(3,20,random)} + true + + + + + GET + true + + true + + + + + = + poll + true + ajax + false + + + = + poll + true + message + false + + + = + ${__Random(3000,10000,poll)} + true + timeout + false + + + = + ${__javaScript(${__threadNum}%${rooms},room)} + true + room + false + + + + + + false + + + false + + + + + + POST + true + + false + + + + + = + chat + true + ajax + false + + + = + Give me ${__Random(1,200,mars)} deep fried mars bars + true + message + false + + + = + ${__javaScript(${__threadNum}%${rooms},room)} + true + room + false + + + + + + false + + + false + + + + + + POST + true + + false + + + + + = + leave + true + ajax + false + + + = + Elvis${__threadNum} + true + message + false + + + = + ${__javaScript(${__threadNum}%${rooms},room)} + true + room + false + + + + + + false + + + false + + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + diff --git a/jetty-test-webapp/pom.xml b/jetty-test-webapp/pom.xml new file mode 100644 index 00000000000..10696ffcd75 --- /dev/null +++ b/jetty-test-webapp/pom.xml @@ -0,0 +1,138 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + org.eclipse.jetty + jetty-test-webapp + Jetty :: Webapp Example + war + + + + org.apache.maven.plugins + maven-surefire-plugin + + + test + test + + + + + **/WebAppTest.java + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + config.xml + + + + + + + + + + + + + + org.eclipse.jetty + jetty-servlets + ${project.version} + + + org.mortbay.jetty + servlet-api + provided + + + org.mortbay.jetty + jsp-api-2.1-glassfish + 9.1.02.B04.p0 + provided + + + junit + junit + test + + + org.eclipse.jetty + jetty-server + ${project.version} + provided + + + diff --git a/jetty-test-webapp/src/main/config/contexts/demo.xml b/jetty-test-webapp/src/main/config/contexts/demo.xml new file mode 100644 index 00000000000..af8dc18ff07 --- /dev/null +++ b/jetty-test-webapp/src/main/config/contexts/demo.xml @@ -0,0 +1,11 @@ + + + + + /demo + /test + false + false + false + -1 + diff --git a/jetty-test-webapp/src/main/config/contexts/test.d/override-web.xml b/jetty-test-webapp/src/main/config/contexts/test.d/override-web.xml new file mode 100644 index 00000000000..432ba813440 --- /dev/null +++ b/jetty-test-webapp/src/main/config/contexts/test.d/override-web.xml @@ -0,0 +1,42 @@ + + + + + + + + + context-override-example + a context value + + + + + Dump + + servlet-override-example + a servlet value + + + + + + Dump + *.more + + + + + Session + com.acme.SessionDump + 5 + + + + + diff --git a/jetty-test-webapp/src/main/config/contexts/test.xml b/jetty-test-webapp/src/main/config/contexts/test.xml new file mode 100644 index 00000000000..f22579f3826 --- /dev/null +++ b/jetty-test-webapp/src/main/config/contexts/test.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + / + /webapps/test.war + + + + + false + false + /etc/webdefault.xml + /contexts/test.d/override-web.xml + + + + + + + + + Test Realm + /etc/realm.properties + + + + + + true + + + + + + diff --git a/jetty-test-webapp/src/main/java/com/acme/ChatServlet.java b/jetty-test-webapp/src/main/java/com/acme/ChatServlet.java new file mode 100644 index 00000000000..0fc8bf5e0f9 --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/ChatServlet.java @@ -0,0 +1,276 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package com.acme; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + + +// Simple asynchronous Chat room. +// This does not handle duplicate usernames or multiple frames/tabs from the same browser +// Some code is duplicated for clarity. +public class ChatServlet extends HttpServlet +{ + AsyncListener _asyncListener = new AsyncListener() + { + public void onComplete(AsyncEvent event) throws IOException + {} + + public void onTimeout(AsyncEvent event) throws IOException + { + event.getRequest().getAsyncContext().dispatch(); + } + }; + + // inner class to hold message queue for each chat room member + class Member + { + String _name; + AsyncContext _asyncContext; + Queue _queue = new LinkedList(); + } + + Map> _rooms = new HashMap>(); + + + // Handle Ajax calls from browser + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + // Ajax calls are form encoded + String action = request.getParameter("action"); + String message = request.getParameter("message"); + String username = request.getParameter("user"); + + if (action.equals("join")) + join(request,response,username); + else if (action.equals("poll")) + poll(request,response,username); + else if (action.equals("chat")) + chat(request,response,username,message); + } + + private synchronized void join(HttpServletRequest request,HttpServletResponse response,String username) + throws IOException + { + Member member = new Member(); + member._name=username; + Map room=_rooms.get(request.getPathInfo()); + if (room==null) + { + room=new HashMap(); + _rooms.put(request.getPathInfo(),room); + } + room.put(username,member); + response.setContentType("text/json;charset=utf-8"); + PrintWriter out=response.getWriter(); + out.print("{action:\"join\"}"); + } + + private synchronized void poll(HttpServletRequest request,HttpServletResponse response,String username) + throws IOException + { + Map room=_rooms.get(request.getPathInfo()); + if (room==null) + { + response.sendError(503); + return; + } + Member member = room.get(username); + if (room==null) + { + response.sendError(503); + return; + } + + if (member._queue.size()>0) + { + // Send one chat message + response.setContentType("text/json;charset=utf-8"); + StringBuilder buf=new StringBuilder(); + + buf.append("{\"action\":\"poll\","); + buf.append("\"from\":\""); + buf.append(member._queue.poll()); + buf.append("\","); + + String message = member._queue.poll(); + int quote=message.indexOf('"'); + while (quote>=0) + { + message=message.substring(0,quote)+'\\'+message.substring(quote); + quote=message.indexOf('"',quote+2); + } + buf.append("\"chat\":\""); + buf.append(message); + buf.append("\"}"); + byte[] bytes = buf.toString().getBytes("utf-8"); + response.setContentLength(bytes.length); + response.getOutputStream().write(bytes); + } + else if (request.isAsyncStarted()) + { + // Timeout so send empty response + response.setContentType("text/json;charset=utf-8"); + PrintWriter out=response.getWriter(); + out.print("{action:\"poll\"}"); + } + else + { + // No chat in queue, so suspend and wait for timeout or chat + request.addAsyncListener(_asyncListener); + member._asyncContext=request.startAsync(); + } + } + + private synchronized void chat(HttpServletRequest request,HttpServletResponse response,String username,String message) + throws IOException + { + Map room=_rooms.get(request.getPathInfo()); + // Post chat to all members + for (Member m:room.values()) + { + m._queue.add(username); // from + m._queue.add(message); // chat + + // wakeup member if polling + if (m._asyncContext!=null) + { + m._asyncContext.dispatch(); + m._asyncContext=null; + } + } + + response.setContentType("text/json;charset=utf-8"); + PrintWriter out=response.getWriter(); + out.print("{action:\"chat\"}"); + } + + + // Serve the HTML with embedded CSS and Javascript. + // This should be static content and should use real JS libraries. + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + if (!request.getRequestURI().endsWith("/")) + { + response.sendRedirect(request.getRequestURI()+"/"); + return; + } + if (request.getParameter("action")!=null) + { + doPost(request,response); + return; + } + + response.setContentType("text/html"); + PrintWriter out=response.getWriter(); + out.println(""); + out.println(" async chat"); + out.println(" "); + out.println(" "); + out.println(""); + out.println("

    "); + out.println("
    "); + out.println("
    "); + out.println(" Username: "); + out.println("
    "); + out.println(" "); + out.println("
    "); + out.println(""); + out.println(""); + } + +} diff --git a/jetty-test-webapp/src/main/java/com/acme/CometServlet.java b/jetty-test-webapp/src/main/java/com/acme/CometServlet.java new file mode 100644 index 00000000000..2f063bb82d1 --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/CometServlet.java @@ -0,0 +1,85 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.util.ajax.Continuation; +import org.eclipse.jetty.util.ajax.ContinuationSupport; + +/** CometServlet + * This servlet implements the Comet API from tc6.x with the exception of the read method. + * + * + * + */ +public class CometServlet extends HttpServlet +{ + public void begin(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + request.setAttribute("org.apache.tomcat.comet",Boolean.TRUE); + } + + public void end(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + synchronized(request) + { + request.removeAttribute("org.apache.tomcat.comet"); + + Continuation continuation=ContinuationSupport.getContinuation(request,request); + if (continuation.isPending()) + continuation.resume(); + } + } + + public void error(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + end(request,response); + } + + public boolean read(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + throw new UnsupportedOperationException(); + } + + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + synchronized(request) + { + // TODO: wrap response so we can reset timeout on writes. + + Continuation continuation=ContinuationSupport.getContinuation(request,request); + + if (!continuation.isPending()) + begin(request,response); + + Integer timeout=(Integer)request.getAttribute("org.apache.tomcat.comet.timeout"); + boolean resumed=continuation.suspend(timeout==null?60000:timeout.intValue()); + + if (!resumed) + error(request,response); + } + } + + public void setTimeout(HttpServletRequest request, HttpServletResponse response, int timeout) throws IOException, ServletException, + UnsupportedOperationException + { + request.setAttribute("org.apache.tomcat.comet.timeout",new Integer(timeout)); + } +} diff --git a/jetty-test-webapp/src/main/java/com/acme/CookieDump.java b/jetty-test-webapp/src/main/java/com/acme/CookieDump.java new file mode 100644 index 00000000000..0fa216a0126 --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/CookieDump.java @@ -0,0 +1,114 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/* ------------------------------------------------------------ */ +/** Test Servlet Cookies. + * + * + */ +public class CookieDump extends HttpServlet +{ + int redirectCount=0; + + /* ------------------------------------------------------------ */ + public void init(ServletConfig config) + throws ServletException + { + super.init(config); + } + + /* ------------------------------------------------------------ */ + protected void handleForm(HttpServletRequest request, + HttpServletResponse response) + { + String action = request.getParameter("Action"); + String name = request.getParameter("Name"); + String value = request.getParameter("Value"); + String age = request.getParameter("Age"); + + if (name!=null && name.length()>0) + { + Cookie cookie = new Cookie(name,value); + if (age!=null && age.length()>0) + cookie.setMaxAge(Integer.parseInt(age)); + response.addCookie(cookie); + } + } + + /* ------------------------------------------------------------ */ + public void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + handleForm(request,response); + String nextUrl = getURI(request)+"?R="+redirectCount++; + String encodedUrl=response.encodeRedirectURL(nextUrl); + response.sendRedirect(encodedUrl); + } + + /* ------------------------------------------------------------ */ + public void doGet(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + handleForm(request,response); + + response.setContentType("text/html"); + + + PrintWriter out = response.getWriter(); + out.println("

    Cookie Dump Servlet:

    "); + + Cookie[] cookies = request.getCookies(); + + for (int i=0;cookies!=null && i"+cookies[i].getName()+"="+cookies[i].getValue()+"
    "); + } + + out.println("
    "); + + out.println("Name:
    "); + out.println("Value:
    "); + out.println("Max-Age:
    "); + out.println(""); + + } + + /* ------------------------------------------------------------ */ + public String getServletInfo() { + return "Session Dump Servlet"; + } + + /* ------------------------------------------------------------ */ + private String getURI(HttpServletRequest request) + { + String uri=(String)request.getAttribute("javax.servlet.forward.request_uri"); + if (uri==null) + uri=request.getRequestURI(); + return uri; + } + +} diff --git a/jetty-test-webapp/src/main/java/com/acme/Counter.java b/jetty-test-webapp/src/main/java/com/acme/Counter.java new file mode 100644 index 00000000000..62b89abe42d --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/Counter.java @@ -0,0 +1,37 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + + +public class Counter +{ + int counter=0; + String last; + + public int getCount() + { + counter++; + return counter; + } + + public void setLast(String uri) { + last=uri; + } + + public String getLast() { + return last; + } + +} + diff --git a/jetty-test-webapp/src/main/java/com/acme/Date2Tag.java b/jetty-test-webapp/src/main/java/com/acme/Date2Tag.java new file mode 100644 index 00000000000..d120f49f594 --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/Date2Tag.java @@ -0,0 +1,48 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.StringTokenizer; + +import javax.servlet.jsp.JspContext; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.JspFragment; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +public class Date2Tag extends SimpleTagSupport +{ + String format; + + public void setFormat(String value) { + this.format = value; + } + + public void doTag() throws JspException, IOException { + String formatted = + new SimpleDateFormat("long".equals(format)?"EEE 'the' d:MMM:yyyy":"d:MM:yy") + .format(new Date()); + StringTokenizer tok = new StringTokenizer(formatted,":"); + JspContext context = getJspContext(); + context.setAttribute("day", tok.nextToken() ); + context.setAttribute("month", tok.nextToken() ); + context.setAttribute("year", tok.nextToken() ); + + JspFragment fragment = getJspBody(); + fragment.invoke(null); + } +} + diff --git a/jetty-test-webapp/src/main/java/com/acme/DateTag.java b/jetty-test-webapp/src/main/java/com/acme/DateTag.java new file mode 100644 index 00000000000..978a8676e97 --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/DateTag.java @@ -0,0 +1,65 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspTagException; +import javax.servlet.jsp.PageContext; +import javax.servlet.jsp.tagext.BodyContent; +import javax.servlet.jsp.tagext.BodyTagSupport; +import javax.servlet.jsp.tagext.Tag; + +public class DateTag extends BodyTagSupport +{ + Tag parent; + BodyContent body; + String tz="GMT"; + + public void setParent(Tag parent) {this.parent=parent;} + public Tag getParent() {return parent;} + public void setBodyContent(BodyContent content) {body=content;} + public void setPageContext(PageContext pageContext) {} + + public void setTz(String value) {tz=value;} + + public int doStartTag() throws JspException {return EVAL_BODY_TAG;} + + public int doEndTag() throws JspException {return EVAL_PAGE;} + + public void doInitBody() throws JspException {} + + public int doAfterBody() throws JspException { + try + { + SimpleDateFormat format = new SimpleDateFormat(body.getString()); + format.setTimeZone(TimeZone.getTimeZone(tz)); + body.getEnclosingWriter().write(format.format(new Date())); + return SKIP_BODY; + } + catch (Exception ex) { + ex.printStackTrace(); + throw new JspTagException(ex.toString()); + } + } + + public void release() + { + body=null; + } +} + diff --git a/jetty-test-webapp/src/main/java/com/acme/DispatchServlet.java b/jetty-test-webapp/src/main/java/com/acme/DispatchServlet.java new file mode 100644 index 00000000000..ec87346fbb0 --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/DispatchServlet.java @@ -0,0 +1,260 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import org.eclipse.jetty.util.URIUtil; + + +/* ------------------------------------------------------------ */ +/** Test Servlet RequestDispatcher. + * + * + */ +public class DispatchServlet extends HttpServlet +{ + /* ------------------------------------------------------------ */ + String pageType; + + /* ------------------------------------------------------------ */ + public void init(ServletConfig config) throws ServletException + { + super.init(config); + } + + /* ------------------------------------------------------------ */ + public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException + { + doGet(sreq, sres); + } + + /* ------------------------------------------------------------ */ + public void doGet(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException + { + if (sreq.getParameter("wrap") != null) + { + sreq= new HttpServletRequestWrapper(sreq); + sres= new HttpServletResponseWrapper(sres); + } + + if (sreq.getParameter("session") != null) + sreq.getSession(true); + + String prefix= + sreq.getContextPath() != null ? sreq.getContextPath() + sreq.getServletPath() : sreq.getServletPath(); + + String info; + + if (sreq.getAttribute("javax.servlet.include.servlet_path") != null) + info= (String)sreq.getAttribute("javax.servlet.include.path_info"); + else + info= sreq.getPathInfo(); + + if (info == null) + info= "NULL"; + + if (info.startsWith("/includeW/")) + { + sres.setContentType("text/html"); + info= info.substring(9); + if (info.indexOf('?') < 0) + info += "?Dispatch=include"; + else + info += "&Dispatch=include"; + + PrintWriter pout= null; + pout= sres.getWriter(); + pout.write("

    Include (writer): " + info + "


    "); + + RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info); + if (dispatch == null) + { + pout= sres.getWriter(); + pout.write("

    Null dispatcher

    "); + } + else + dispatch.include(sreq, sres); + + pout.write("

    -- Included (writer)

    "); + } + else if (info.startsWith("/includeS/")) + { + sres.setContentType("text/html"); + info= info.substring(9); + if (info.indexOf('?') < 0) + info += "?Dispatch=include"; + else + info += "&Dispatch=include"; + + OutputStream out= null; + out= sres.getOutputStream(); + out.write(("

    Include (outputstream): " + info + "


    ").getBytes()); + + RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info); + if (dispatch == null) + { + out= sres.getOutputStream(); + out.write("

    Null dispatcher

    ".getBytes()); + } + else + dispatch.include(sreq, sres); + + out.write("

    -- Included (outputstream)

    ".getBytes()); + + } + else if (info.startsWith("/forward/")) + { + info= info.substring(8); + if (info.indexOf('?') < 0) + info += "?Dispatch=forward"; + else + info += "&Dispatch=forward"; + RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info); + if (dispatch != null) + { + ServletOutputStream out =sres.getOutputStream(); + out.print("Can't see this"); + dispatch.forward(sreq, sres); + try + { + out.println("IOException"); + throw new IllegalStateException(); + } + catch(IOException e) + {} + } + else + { + sres.setContentType("text/html"); + PrintWriter pout= sres.getWriter(); + pout.write("

    No dispatcher for: " + info + "


    "); + pout.flush(); + } + } + else if (info.startsWith("/forwardC/")) + { + info= info.substring(9); + if (info.indexOf('?') < 0) + info += "?Dispatch=forward"; + else + info += "&Dispatch=forward"; + + String cpath= info.substring(0, info.indexOf('/', 1)); + info= info.substring(cpath.length()); + + ServletContext context= getServletContext().getContext(cpath); + RequestDispatcher dispatch= context.getRequestDispatcher(info); + + if (dispatch != null) + { + dispatch.forward(sreq, sres); + } + else + { + sres.setContentType("text/html"); + PrintWriter pout= sres.getWriter(); + pout.write("

    No dispatcher for: " + cpath + URIUtil.SLASH + info + "


    "); + pout.flush(); + } + } + else if (info.startsWith("/includeN/")) + { + sres.setContentType("text/html"); + info= info.substring(10); + if (info.indexOf(URIUtil.SLASH) >= 0) + info= info.substring(0, info.indexOf(URIUtil.SLASH)); + + PrintWriter pout; + if (info.startsWith("/null")) + info= info.substring(5); + else + { + pout= sres.getWriter(); + pout.write("

    Include named: " + info + "


    "); + } + + RequestDispatcher dispatch= getServletContext().getNamedDispatcher(info); + if (dispatch != null) + dispatch.include(sreq, sres); + else + { + pout= sres.getWriter(); + pout.write("

    No servlet named: " + info + "

    "); + } + + pout= sres.getWriter(); + pout.write("

    Included "); + } + else if (info.startsWith("/forwardN/")) + { + info= info.substring(10); + if (info.indexOf(URIUtil.SLASH) >= 0) + info= info.substring(0, info.indexOf(URIUtil.SLASH)); + RequestDispatcher dispatch= getServletContext().getNamedDispatcher(info); + if (dispatch != null) + dispatch.forward(sreq, sres); + else + { + sres.setContentType("text/html"); + PrintWriter pout= sres.getWriter(); + pout.write("

    No servlet named: " + info + "

    "); + pout.flush(); + } + } + else + { + sres.setContentType("text/html"); + PrintWriter pout= sres.getWriter(); + pout.write( + "

    Dispatch URL must be of the form:

    " + + "
    "
    +                    + prefix
    +                    + "/includeW/path\n"
    +                    + prefix
    +                    + "/includeS/path\n"
    +                    + prefix
    +                    + "/forward/path\n"
    +                    + prefix
    +                    + "/includeN/name\n"
    +                    + prefix
    +                    + "/forwardC/_context/path\n
    "); + } + + } + + /* ------------------------------------------------------------ */ + public String getServletInfo() + { + return "Include Servlet"; + } + + /* ------------------------------------------------------------ */ + public synchronized void destroy() + { + } + +} diff --git a/jetty-test-webapp/src/main/java/com/acme/Dump.java b/jetty-test-webapp/src/main/java/com/acme/Dump.java new file mode 100644 index 00000000000..9dab8d17748 --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/Dump.java @@ -0,0 +1,899 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Enumeration; +import java.util.Locale; + +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; +import javax.servlet.UnavailableException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.ajax.Continuation; +import org.eclipse.jetty.util.ajax.ContinuationSupport; + + + +/* ------------------------------------------------------------ */ +/** Dump Servlet Request. + * + */ +public class Dump extends HttpServlet +{ + static boolean fixed; + /* ------------------------------------------------------------ */ + public void init(ServletConfig config) throws ServletException + { + super.init(config); + + if (config.getInitParameter("unavailable")!=null && !fixed) + { + + fixed=true; + throw new UnavailableException("Unavailable test",Integer.parseInt(config.getInitParameter("unavailable"))); + } + } + + /* ------------------------------------------------------------ */ + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + doGet(request, response); + } + + /* ------------------------------------------------------------ */ + public void doGet(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + if(request.getPathInfo()!=null && request.getPathInfo().toLowerCase().indexOf("script")!=-1) + { + response.sendRedirect(getServletContext().getContextPath() + "/dump/info"); + return; + } + + request.setCharacterEncoding("UTF-8"); + + if (request.getParameter("empty")!=null) + { + response.setStatus(200); + response.flushBuffer(); + return; + } + + if (request.getParameter("sleep")!=null) + { + try + { + long s = Long.parseLong(request.getParameter("sleep")); + if (request.getHeader(HttpHeaders.EXPECT)!=null &&request.getHeader(HttpHeaders.EXPECT).indexOf("102")>=0) + { + Thread.sleep(s/2); + response.sendError(102); + Thread.sleep(s/2); + } + else + Thread.sleep(s); + } + catch (InterruptedException e) + { + return; + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + if (request.getAttribute("RESUME")==null && request.getParameter("resume")!=null) + { + request.setAttribute("RESUME",Boolean.TRUE); + + final long resume=Long.parseLong(request.getParameter("resume")); + new Thread(new Runnable() + { + public void run() + { + try + { + Thread.sleep(resume); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + Continuation continuation = ContinuationSupport.getContinuation(request, null); + continuation.resume(); + } + + }).start(); + } + + if (request.getParameter("continue")!=null) + { + try + { + Continuation continuation = ContinuationSupport.getContinuation(request, null); + continuation.suspend(Long.parseLong(request.getParameter("continue"))); + } + catch(Exception e) + { + throw new ServletException(e); + } + } + + if (request.getAttribute("ASYNC")==null) + { + request.setAttribute("ASYNC",Boolean.TRUE); + + if (request.getParameter("dispatch")!=null) + { + final long resume=Long.parseLong(request.getParameter("dispatch")); + final String path=request.getParameter("dispatchPath"); + new Thread(new Runnable() + { + public void run() + { + try + { + Thread.sleep(resume); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + if (path!=null) + request.getAsyncContext().dispatch(path); + else + request.getAsyncContext().dispatch(); + + } + + }).start(); + } + + if (request.getParameter("complete")!=null) + { + final long complete=Long.parseLong(request.getParameter("complete")); + new Thread(new Runnable() + { + public void run() + { + try + { + Thread.sleep(complete); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + try + { + HttpServletResponse response = (HttpServletResponse) request.getAsyncContext().getResponse(); + response.setContentType("text/html"); + response.getOutputStream().println("

    COMPLETED

    "); + request.getAsyncContext().complete(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + }).start(); + } + + if (request.getParameter("async")!=null) + { + final long async=Long.parseLong(request.getParameter("async")); + request.addAsyncListener(new AsyncListener(){ + public void onComplete(AsyncEvent event) throws IOException + { + } + public void onTimeout(AsyncEvent event) throws IOException + { + event.getRequest().getAsyncContext().dispatch(); + } + }); + request.setAsyncTimeout(async); + request.startAsync(); + return; + } + } + + request.setAttribute("Dump", this); + getServletContext().setAttribute("Dump",this); + // getServletContext().log("dump "+request.getRequestURI()); + + // Force a content length response + String length= request.getParameter("length"); + if (length != null && length.length() > 0) + { + response.setContentLength(Integer.parseInt(length)); + } + + // Handle a dump of data + String data= request.getParameter("data"); + String block= request.getParameter("block"); + String dribble= request.getParameter("dribble"); + boolean flush= request.getParameter("flush")!=null?Boolean.parseBoolean(request.getParameter("flush")):false; + if (data != null && data.length() > 0) + { + int d=Integer.parseInt(data); + int b=(block!=null&&block.length()>0)?Integer.parseInt(block):50; + byte[] buf=new byte[b]; + for (int i=0;i 0) + { + if (b==1) + { + out.write(d%80==0?'\n':'.'); + d--; + } + else if (d>=b) + { + out.write(buf); + d=d-b; + } + else + { + out.write(buf,0,d); + d=0; + } + + if (dribble!=null) + { + out.flush(); + try + { + Thread.sleep(Long.parseLong(dribble)); + } + catch (Exception e) + { + e.printStackTrace(); + break; + } + } + + } + + if (flush) + out.flush(); + + return; + } + + + // handle an exception + String info= request.getPathInfo(); + if (info != null && info.endsWith("Exception")) + { + try + { + throw (Throwable) Thread.currentThread().getContextClassLoader().loadClass(info.substring(1)).newInstance(); + } + catch (Throwable th) + { + throw new ServletException(th); + } + } + + // test a reset + String reset= request.getParameter("reset"); + if (reset != null && reset.length() > 0) + { + response.getOutputStream().println("THIS SHOULD NOT BE SEEN!"); + response.setHeader("SHOULD_NOT","BE SEEN"); + response.reset(); + } + + + // handle an redirect + String redirect= request.getParameter("redirect"); + if (redirect != null && redirect.length() > 0) + { + response.getOutputStream().println("THIS SHOULD NOT BE SEEN!"); + response.sendRedirect(redirect); + try + { + response.getOutputStream().println("THIS SHOULD NOT BE SEEN!"); + } + catch(IOException e) + { + // ignored as stream is closed. + } + return; + } + + // handle an error + String error= request.getParameter("error"); + if (error != null && error.length() > 0 && request.getAttribute("javax.servlet.error.status_code")==null) + { + response.getOutputStream().println("THIS SHOULD NOT BE SEEN!"); + response.sendError(Integer.parseInt(error)); + try + { + response.getOutputStream().println("THIS SHOULD NOT BE SEEN!"); + } + catch(IllegalStateException e) + { + try + { + response.getWriter().println("NOR THIS!!"); + } + catch(IOException e2){} + } + catch(IOException e){} + return; + } + + + String buffer= request.getParameter("buffer"); + if (buffer != null && buffer.length() > 0) + response.setBufferSize(Integer.parseInt(buffer)); + + String charset= request.getParameter("charset"); + if (charset==null) + charset="UTF-8"; + response.setCharacterEncoding(charset); + response.setContentType("text/html"); + + if (info != null && info.indexOf("Locale/") >= 0) + { + try + { + String locale_name= info.substring(info.indexOf("Locale/") + 7); + Field f= java.util.Locale.class.getField(locale_name); + response.setLocale((Locale)f.get(null)); + } + catch (Exception e) + { + e.printStackTrace(); + response.setLocale(Locale.getDefault()); + } + } + + String cn= request.getParameter("cookie"); + String cv=request.getParameter("cookiev"); + if (cn!=null && cv!=null) + { + Cookie cookie= new Cookie(cn, cv); + if (request.getParameter("version")!=null) + cookie.setVersion(Integer.parseInt(request.getParameter("version"))); + cookie.setComment("Cookie from dump servlet"); + response.addCookie(cookie); + } + + String pi= request.getPathInfo(); + if (pi != null && pi.startsWith("/ex")) + { + OutputStream out= response.getOutputStream(); + out.write("This text should be reset".getBytes()); + if ("/ex0".equals(pi)) + throw new ServletException("test ex0", new Throwable()); + else if ("/ex1".equals(pi)) + throw new IOException("test ex1"); + else if ("/ex2".equals(pi)) + throw new UnavailableException("test ex2"); + else if (pi.startsWith("/ex3/")) + throw new UnavailableException("test ex3",Integer.parseInt(pi.substring(5))); + throw new RuntimeException("test"); + } + + if ("true".equals(request.getParameter("close"))) + response.setHeader("Connection","close"); + + String buffered= request.getParameter("buffered"); + + PrintWriter pout=null; + + try + { + pout =response.getWriter(); + } + catch(IllegalStateException e) + { + pout=new PrintWriter(new OutputStreamWriter(response.getOutputStream(),charset)); + } + if (buffered!=null) + pout = new PrintWriter(new BufferedWriter(pout,Integer.parseInt(buffered))); + + try + { + pout.write("\n\n"); + pout.write("

    Dump Servlet

    \n"); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + pout.write("\n"); + pout.write(""); + pout.write(""); + + pout.write("\n"); + pout.write(""); + pout.write(""); + + pout.write("\n"); + pout.write(""); + pout.write(""); + + Enumeration locales= request.getLocales(); + while (locales.hasMoreElements()) + { + pout.write("\n"); + pout.write(""); + pout.write(""); + } + pout.write("\n"); + + pout.write(""); + Enumeration h= request.getHeaderNames(); + String name; + while (h.hasMoreElements()) + { + name= (String)h.nextElement(); + + Enumeration h2= request.getHeaders(name); + while (h2.hasMoreElements()) + { + String hv= (String)h2.nextElement(); + pout.write("\n"); + pout.write(""); + pout.write(""); + } + } + + pout.write("\n"); + pout.write(""); + h= request.getParameterNames(); + while (h.hasMoreElements()) + { + name= (String)h.nextElement(); + pout.write("\n"); + pout.write(""); + pout.write(""); + String[] values= request.getParameterValues(name); + if (values == null) + { + pout.write("\n"); + pout.write(""); + pout.write(""); + } + else if (values.length > 1) + { + for (int i= 0; i < values.length; i++) + { + pout.write("\n"); + pout.write(""); + pout.write(""); + } + } + } + + pout.write("\n"); + pout.write(""); + Cookie[] cookies = request.getCookies(); + for (int i=0; cookies!=null && i\n"); + pout.write(""); + pout.write(""); + } + + String content_type=request.getContentType(); + if (content_type!=null && + !content_type.startsWith("application/x-www-form-urlencoded") && + !content_type.startsWith("multipart/form-data")) + { + pout.write("\n"); + pout.write(""); + pout.write("\n"); + pout.write(""); + } + + + pout.write("\n"); + pout.write(""); + Enumeration a= request.getAttributeNames(); + while (a.hasMoreElements()) + { + name= (String)a.nextElement(); + pout.write("\n"); + pout.write(""); + pout.write(""); + } + + + pout.write("\n"); + pout.write(""); + a= getInitParameterNames(); + while (a.hasMoreElements()) + { + name= (String)a.nextElement(); + pout.write("\n"); + pout.write(""); + pout.write(""); + } + + pout.write("\n"); + pout.write(""); + a= getServletContext().getInitParameterNames(); + while (a.hasMoreElements()) + { + name= (String)a.nextElement(); + pout.write("\n"); + pout.write(""); + pout.write(""); + } + + pout.write("\n"); + pout.write(""); + a= getServletContext().getAttributeNames(); + while (a.hasMoreElements()) + { + name= (String)a.nextElement(); + pout.write("\n"); + pout.write(""); + pout.write(""); + } + + + String res= request.getParameter("resource"); + if (res != null && res.length() > 0) + { + pout.write("\n"); + pout.write(""); + + pout.write("\n"); + pout.write(""); + pout.write(""); + + pout.write("\n"); + pout.write(""); + pout.write(""); + + pout.write("\n"); + pout.write(""); + pout.write(""); + + pout.write("\n"); + pout.write(""); + try{pout.write("");} + catch(Exception e) {pout.write("");} + } + + pout.write("
    getDispatcherTYpe: " + request.getDispatcherType()+"
    getMethod: " + notag(request.getMethod())+"
    getContentLength: "+Integer.toString(request.getContentLength())+"
    getContentType: "+notag(request.getContentType())+"
    getRequestURI: "+notag(request.getRequestURI())+"
    getRequestURL: "+notag(request.getRequestURL().toString())+"
    getContextPath: "+request.getContextPath()+"
    getServletPath: "+notag(request.getServletPath())+"
    getPathInfo: "+notag(request.getPathInfo())+"
    getPathTranslated: "+notag(request.getPathTranslated())+"
    getQueryString: "+notag(request.getQueryString())+"
    isAsyncSupported: "+request.isAsyncSupported()+"
    isAsyncStarted: "+request.isAsyncStarted()+"
    getProtocol: "+request.getProtocol()+"
    getScheme: "+request.getScheme()+"
    getServerName: "+notag(request.getServerName())+"
    getServerPort: "+Integer.toString(request.getServerPort())+"
    getLocalName: "+request.getLocalName()+"
    getLocalAddr: "+request.getLocalAddr()+"
    getLocalPort: "+Integer.toString(request.getLocalPort())+"
    getRemoteUser: "+request.getRemoteUser()+"
    getRemoteAddr: "+request.getRemoteAddr()+"
    getRemoteHost: "+request.getRemoteHost()+"
    getRemotePort: "+request.getRemotePort()+"
    getRequestedSessionId: "+request.getRequestedSessionId()+"
    isSecure(): "+request.isSecure()+"
    isUserInRole(admin): "+request.isUserInRole("admin")+"
    getLocale: "+request.getLocale()+"
    getLocales: "+locales.nextElement()+"

    Other HTTP Headers:
    "+notag(name)+": "+notag(hv)+"

    Request Parameters:
    "+notag(name)+": "+notag(request.getParameter(name))+"
    "+notag(name)+" Values: "+"NULL!"+"
    "+notag(name)+"["+i+"]: "+notag(values[i])+"

    Cookies:
    "+notag(cookie.getName())+": "+notag(cookie.getValue())+"

    Content:
    ");
    +                char[] content= new char[4096];
    +                int len;
    +                try{
    +                    Reader in=request.getReader();
    +                    
    +                    while((len=in.read(content))>=0)
    +                        pout.write(notag(new String(content,0,len)));
    +                }
    +                catch(IOException e)
    +                {
    +                    pout.write(e.toString());
    +                }
    +                
    +                pout.write("

    Request Attributes:
    "+name.replace("."," .")+": "+"
    " + toString(request.getAttribute(name)) + "
    "+"

    Servlet InitParameters:
    "+name+": "+ toString(getInitParameter(name)) +"

    Context InitParameters:
    "+name.replace("."," .")+": "+ toString(getServletContext().getInitParameter(name)) + "

    Context Attributes:
    "+name.replace("."," .")+": "+"
    " + toString(getServletContext().getAttribute(name)) + "
    "+"

    Get Resource: \""+res+"\"
    this.getClass().getResource(...): "+this.getClass().getResource(res)+"
    this.getClass().getClassLoader().getResource(...): "+this.getClass().getClassLoader().getResource(res)+"
    Thread.currentThread().getContextClassLoader().getResource(...): "+Thread.currentThread().getContextClassLoader().getResource(res)+"
    getServletContext().getResource(...): "+getServletContext().getResource(res)+""+"" +e+"
    \n"); + + /* ------------------------------------------------------------ */ + pout.write("

    Request Wrappers

    \n"); + ServletRequest rw=request; + int w=0; + while (rw !=null) + { + pout.write((w++)+": "+rw.getClass().getName()+"
    "); + if (rw instanceof HttpServletRequestWrapper) + rw=((HttpServletRequestWrapper)rw).getRequest(); + else if (rw instanceof ServletRequestWrapper) + rw=((ServletRequestWrapper)rw).getRequest(); + else + rw=null; + } + + /* ------------------------------------------------------------ */ + pout.write("

    Response Wrappers

    \n"); + ServletResponse rsw=response; + w=0; + while (rsw !=null) + { + pout.write((w++)+": "+rsw.getClass().getName()+"
    "); + if (rsw instanceof HttpServletResponseWrapper) + rsw=((HttpServletResponseWrapper)rsw).getResponse(); + else if (rsw instanceof ServletResponseWrapper) + rsw=((ServletResponseWrapper)rsw).getResponse(); + else + rsw=null; + } + + pout.write("
    "); + pout.write("

    International Characters (UTF-8)

    "); + pout.write("LATIN LETTER SMALL CAPITAL AE
    \n"); + pout.write("Directly uni encoded(\\u1d01): \u1d01
    "); + pout.write("HTML reference (&AElig;): Æ
    "); + pout.write("Decimal (&#7425;): ᴁ
    "); + pout.write("Javascript unicode (\\u1d01) :
    "); + pout.write("
    "); + pout.write("

    Form to generate GET content

    "); + pout.write(""); + pout.write("TextField:
    \n"); + pout.write(""); + pout.write(""); + + pout.write("
    "); + + pout.write("

    Form to generate POST content

    "); + pout.write("
    "); + pout.write("TextField:
    \n"); + pout.write("Select:
    "); + pout.write("
    "); + pout.write("
    "); + pout.write("
    "); + + pout.write("

    Form to generate UPLOAD content

    "); + pout.write("
    "); + pout.write("TextField:
    \n"); + pout.write("File 1:
    \n"); + pout.write("File 2:
    \n"); + pout.write("
    "); + pout.write("
    "); + + pout.write("

    Form to set Cookie

    "); + pout.write("
    "); + pout.write("cookie:
    \n"); + pout.write("value:
    \n"); + pout.write(""); + pout.write("
    \n"); + + pout.write("

    Form to get Resource

    "); + pout.write("
    "); + pout.write("resource:
    \n"); + pout.write(""); + pout.write("
    \n"); + + + } + catch (Exception e) + { + getServletContext().log("dump", e); + } + + + if (request.getParameter("stream")!=null) + { + pout.flush(); + Continuation continuation = ContinuationSupport.getContinuation(request, null); + continuation.suspend(Long.parseLong(request.getParameter("stream"))); + } + + String lines= request.getParameter("lines"); + if (lines!=null) + { + char[] line = "A line of characters. Blah blah blah blah. blooble blooble
    \n".toCharArray(); + for (int l=Integer.parseInt(lines);l-->0;) + { + pout.write(""+l+" "); + pout.write(line); + } + } + + pout.write("\n\n"); + + pout.close(); + + if (pi != null) + { + if ("/ex4".equals(pi)) + throw new ServletException("test ex4", new Throwable()); + if ("/ex5".equals(pi)) + throw new IOException("test ex5"); + if ("/ex6".equals(pi)) + throw new UnavailableException("test ex6"); + } + + + } + + /* ------------------------------------------------------------ */ + public String getServletInfo() + { + return "Dump Servlet"; + } + + /* ------------------------------------------------------------ */ + public synchronized void destroy() + { + } + + /* ------------------------------------------------------------ */ + private String getURI(HttpServletRequest request) + { + String uri= (String)request.getAttribute("javax.servlet.forward.request_uri"); + if (uri == null) + uri= request.getRequestURI(); + return uri; + } + + /* ------------------------------------------------------------ */ + private static String toString(Object o) + { + if (o == null) + return null; + + try + { + if (o.getClass().isArray()) + { + StringBuffer sb = new StringBuffer(); + if (!o.getClass().getComponentType().isPrimitive()) + { + Object[] array= (Object[])o; + for (int i= 0; i < array.length; i++) + { + if (i > 0) + sb.append("\n"); + sb.append(array.getClass().getComponentType().getName()); + sb.append("["); + sb.append(i); + sb.append("]="); + sb.append(toString(array[i])); + } + return sb.toString(); + } + else + { + int length = Array.getLength(o); + for (int i=0;i 0) + sb.append("\n"); + sb.append(o.getClass().getComponentType().getName()); + sb.append("["); + sb.append(i); + sb.append("]="); + sb.append(toString(Array.get(o, i))); + } + return sb.toString(); + } + } + else + return o.toString(); + } + catch (Exception e) + { + return e.toString(); + } + } + + private String notag(String s) + { + if (s==null) + return "null"; + s=StringUtil.replace(s,"&","&"); + s=StringUtil.replace(s,"<","<"); + s=StringUtil.replace(s,">",">"); + return s; + } +} diff --git a/jetty-test-webapp/src/main/java/com/acme/HelloWorld.java b/jetty-test-webapp/src/main/java/com/acme/HelloWorld.java new file mode 100644 index 00000000000..16d0a4792db --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/HelloWorld.java @@ -0,0 +1,67 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; +import java.io.IOException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.SingleThreadModel; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/* ------------------------------------------------------------ */ +/** Dump Servlet Request. + * + */ +public class HelloWorld extends HttpServlet implements SingleThreadModel +{ + /* ------------------------------------------------------------ */ + public void init(ServletConfig config) throws ServletException + { + super.init(config); + } + + /* ------------------------------------------------------------ */ + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + doGet(request, response); + } + + /* ------------------------------------------------------------ */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("text/html"); + ServletOutputStream out = response.getOutputStream(); + out.println(""); + out.println("

    Hello World

    "); + out.println(""); + out.flush(); + + try + { + Thread.sleep(200); + } + catch (InterruptedException e) + { + getServletContext().log("exception",e); + } + } + + + + +} diff --git a/jetty-test-webapp/src/main/java/com/acme/SessionDump.java b/jetty-test-webapp/src/main/java/com/acme/SessionDump.java new file mode 100644 index 00000000000..8395c9b58be --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/SessionDump.java @@ -0,0 +1,176 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; +import java.util.Enumeration; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + + +/* ------------------------------------------------------------ */ +/** Test Servlet Sessions. + * + * + */ +public class SessionDump extends HttpServlet +{ + + int redirectCount=0; + /* ------------------------------------------------------------ */ + String pageType; + + /* ------------------------------------------------------------ */ + public void init(ServletConfig config) + throws ServletException + { + super.init(config); + } + + /* ------------------------------------------------------------ */ + protected void handleForm(HttpServletRequest request, + HttpServletResponse response) + { + HttpSession session = request.getSession(false); + String action = request.getParameter("Action"); + String name = request.getParameter("Name"); + String value = request.getParameter("Value"); + + if (action!=null) + { + if(action.equals("New Session")) + { + session = request.getSession(true); + session.setAttribute("test","value"); + } + else if (session!=null) + { + if (action.equals("Invalidate")) + session.invalidate(); + else if (action.equals("Set") && name!=null && name.length()>0) + session.setAttribute(name,value); + else if (action.equals("Remove")) + session.removeAttribute(name); + } + } + } + + /* ------------------------------------------------------------ */ + public void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + handleForm(request,response); + String nextUrl = getURI(request)+"?R="+redirectCount++; + String encodedUrl=response.encodeRedirectURL(nextUrl); + response.sendRedirect(encodedUrl); + } + + /* ------------------------------------------------------------ */ + public void doGet(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + handleForm(request,response); + + response.setContentType("text/html"); + + HttpSession session = request.getSession(getURI(request).indexOf("new")>0); + try + { + if (session!=null) + session.isNew(); + } + catch(IllegalStateException e) + { + session=null; + } + + PrintWriter out = response.getWriter(); + out.println("

    Session Dump Servlet:

    "); + out.println("
    "); + + if (session==null) + { + out.println("

    No Session

    "); + out.println(""); + } + else + { + try + { + out.println("ID: "+session.getId()+"
    "); + out.println("New: "+session.isNew()+"
    "); + out.println("Created: "+new Date(session.getCreationTime())+"
    "); + out.println("Last: "+new Date(session.getLastAccessedTime())+"
    "); + out.println("Max Inactive: "+session.getMaxInactiveInterval()+"
    "); + out.println("Context: "+session.getServletContext()+"
    "); + + + Enumeration keys=session.getAttributeNames(); + while(keys.hasMoreElements()) + { + String name=(String)keys.nextElement(); + String value=""+session.getAttribute(name); + + out.println(""+name+": "+value+"
    "); + } + + out.println("Name:
    "); + out.println("Value:
    "); + + out.println(""); + out.println(""); + out.println(""); + out.println("
    "); + + out.println("

    "); + + if (request.isRequestedSessionIdFromCookie()) + out.println("

    Turn off cookies in your browser to try url encoding
    "); + + if (request.isRequestedSessionIdFromURL()) + out.println("

    Turn on cookies in your browser to try cookie encoding
    "); + out.println("Encoded Link
    "); + + } + catch (IllegalStateException e) + { + e.printStackTrace(); + } + } + + } + + /* ------------------------------------------------------------ */ + public String getServletInfo() { + return "Session Dump Servlet"; + } + + /* ------------------------------------------------------------ */ + private String getURI(HttpServletRequest request) + { + String uri=(String)request.getAttribute("javax.servlet.forward.request_uri"); + if (uri==null) + uri=request.getRequestURI(); + return uri; + } + +} diff --git a/jetty-test-webapp/src/main/java/com/acme/TagListener.java b/jetty-test-webapp/src/main/java/com/acme/TagListener.java new file mode 100644 index 00000000000..bbafdeaca41 --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/TagListener.java @@ -0,0 +1,135 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpSessionActivationListener; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +public class TagListener implements HttpSessionListener, HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener +{ + public void attributeAdded(HttpSessionBindingEvent se) + { + //System.err.println("tagListener: attributedAdded "+se); + } + + public void attributeRemoved(HttpSessionBindingEvent se) + { + //System.err.println("tagListener: attributeRemoved "+se); + } + + public void attributeReplaced(HttpSessionBindingEvent se) + { + //System.err.println("tagListener: attributeReplaced "+se); + } + + public void sessionWillPassivate(HttpSessionEvent se) + { + //System.err.println("tagListener: sessionWillPassivate "+se); + } + + public void sessionDidActivate(HttpSessionEvent se) + { + //System.err.println("tagListener: sessionDidActivate "+se); + } + + public void contextInitialized(ServletContextEvent sce) + { + //System.err.println("tagListener: contextInitialized "+sce); + } + + public void contextDestroyed(ServletContextEvent sce) + { + //System.err.println("tagListener: contextDestroyed "+sce); + } + + public void attributeAdded(ServletContextAttributeEvent scab) + { + //System.err.println("tagListener: attributeAdded "+scab); + } + + public void attributeRemoved(ServletContextAttributeEvent scab) + { + //System.err.println("tagListener: attributeRemoved "+scab); + } + + public void attributeReplaced(ServletContextAttributeEvent scab) + { + //System.err.println("tagListener: attributeReplaced "+scab); + } + + public void requestDestroyed(ServletRequestEvent sre) + { + //System.err.println("tagListener: requestDestroyed "+sre); + } + + public void requestInitialized(ServletRequestEvent sre) + { + //System.err.println("tagListener: requestInitialized "+sre); + } + + public void attributeAdded(ServletRequestAttributeEvent srae) + { + //System.err.println("tagListener: attributeAdded "+srae); + } + + public void attributeRemoved(ServletRequestAttributeEvent srae) + { + //System.err.println("tagListener: attributeRemoved "+srae); + } + + public void attributeReplaced(ServletRequestAttributeEvent srae) + { + //System.err.println("tagListener: attributeReplaced "+srae); + } + + public void sessionCreated(HttpSessionEvent se) + { + //System.err.println("tagListener: sessionCreated "+se); + } + + public void sessionDestroyed(HttpSessionEvent se) + { + //System.err.println("tagListener: sessionDestroyed "+se); + } + + public void requestCompleted(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + + public void requestResumed(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + + public void requestSuspended(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + +} diff --git a/jetty-test-webapp/src/main/java/com/acme/TestFilter.java b/jetty-test-webapp/src/main/java/com/acme/TestFilter.java new file mode 100644 index 00000000000..5c2c367360b --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/TestFilter.java @@ -0,0 +1,91 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/* ------------------------------------------------------------ */ +/** TestFilter. + * + * + */ +public class TestFilter implements Filter +{ + private ServletContext _context; + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + public void init(FilterConfig filterConfig) throws ServletException + { + _context= filterConfig.getServletContext(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + Integer old_value=null; + ServletRequest r = request; + while (r instanceof ServletRequestWrapper) + r=((ServletRequestWrapper)r).getRequest(); + + try + { + old_value=(Integer)request.getAttribute("testFilter"); + + Integer value=(old_value==null)?new Integer(1):new Integer(old_value.intValue()+1); + + request.setAttribute("testFilter", value); + + String qString = ((HttpServletRequest)request).getQueryString(); + if (qString != null && qString.indexOf("wrap")>=0) + { + request=new HttpServletRequestWrapper((HttpServletRequest)request); + } + _context.setAttribute("request"+r.hashCode(),value); + + chain.doFilter(request, response); + } + finally + { + request.setAttribute("testFilter", old_value); + _context.setAttribute("request"+r.hashCode(),old_value); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.Filter#destroy() + */ + public void destroy() + { + } + +} diff --git a/jetty-test-webapp/src/main/java/com/acme/TestListener.java b/jetty-test-webapp/src/main/java/com/acme/TestListener.java new file mode 100644 index 00000000000..b0427086b1a --- /dev/null +++ b/jetty-test-webapp/src/main/java/com/acme/TestListener.java @@ -0,0 +1,152 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package com.acme; + +import java.util.EnumSet; + +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSessionActivationListener; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +public class TestListener implements HttpSessionListener, HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener +{ + public void attributeAdded(HttpSessionBindingEvent se) + { + // System.err.println("attributedAdded "+se); + } + + public void attributeRemoved(HttpSessionBindingEvent se) + { + // System.err.println("attributeRemoved "+se); + } + + public void attributeReplaced(HttpSessionBindingEvent se) + { + // System.err.println("attributeReplaced "+se); + } + + public void sessionWillPassivate(HttpSessionEvent se) + { + // System.err.println("sessionWillPassivate "+se); + } + + public void sessionDidActivate(HttpSessionEvent se) + { + // System.err.println("sessionDidActivate "+se); + } + + public void contextInitialized(ServletContextEvent sce) + { + ServletContext context=sce.getServletContext(); + + FilterRegistration registration=context.addFilter("TestFilter",TestFilter.class.getName()); + + registration.setAsyncSupported(true); + registration.addMappingForUrlPatterns( + EnumSet.of(DispatcherType.ERROR,DispatcherType.ASYNC,DispatcherType.FORWARD,DispatcherType.INCLUDE,DispatcherType.REQUEST), + true, + new String[]{"/dump/*","/dispatch/*","*.dump"}); + } + + public void contextDestroyed(ServletContextEvent sce) + { + // System.err.println("contextDestroyed "+sce); + } + + public void attributeAdded(ServletContextAttributeEvent scab) + { + // System.err.println("attributeAdded "+scab); + } + + public void attributeRemoved(ServletContextAttributeEvent scab) + { + // System.err.println("attributeRemoved "+scab); + } + + public void attributeReplaced(ServletContextAttributeEvent scab) + { + // System.err.println("attributeReplaced "+scab); + } + + public void requestDestroyed(ServletRequestEvent sre) + { + ((HttpServletRequest)sre.getServletRequest()).getSession(false); + sre.getServletRequest().setAttribute("requestInitialized",null); + // System.err.println("requestDestroyed "+sre); + } + + public void requestInitialized(ServletRequestEvent sre) + { + sre.getServletRequest().setAttribute("requestInitialized","'"+sre.getServletContext().getContextPath()+"'"); + // System.err.println("requestInitialized "+sre); + } + + public void attributeAdded(ServletRequestAttributeEvent srae) + { + // System.err.println("attributeAdded "+srae); + } + + public void attributeRemoved(ServletRequestAttributeEvent srae) + { + // System.err.println("attributeRemoved "+srae); + } + + public void attributeReplaced(ServletRequestAttributeEvent srae) + { + // System.err.println("attributeReplaced "+srae); + } + + public void sessionCreated(HttpSessionEvent se) + { + // System.err.println("sessionCreated "+se); + } + + public void sessionDestroyed(HttpSessionEvent se) + { + // System.err.println("sessionDestroyed "+se); + } + + public void requestCompleted(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + + public void requestResumed(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + + public void requestSuspended(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + +} diff --git a/jetty-test-webapp/src/main/webapp/META-INF/MANIFEST.MF b/jetty-test-webapp/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..5e9495128c0 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/jetty-test-webapp/src/main/webapp/WEB-INF/acme-taglib.tld b/jetty-test-webapp/src/main/webapp/WEB-INF/acme-taglib.tld new file mode 100644 index 00000000000..a9e9d3fe0a9 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/WEB-INF/acme-taglib.tld @@ -0,0 +1,28 @@ + + + + + + 1.0 + 1.2 + acme + http://www.acme.com/taglib + taglib example + + com.acme.TagListener + + + + date + com.acme.DateTag + TAGDEPENDENT + Display Date + + tz + false + + + + diff --git a/jetty-test-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld b/jetty-test-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld new file mode 100644 index 00000000000..3edb7bb14f3 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld @@ -0,0 +1,35 @@ + + + + Acme JSP2 tags + 1.0 + acme2 + http://www.acme.com/taglib2 + + Simple Date formatting + date2 + com.acme.Date2Tag + scriptless + + Day of the Month + day + + + Month of the Year + month + + + Year + year + + + format + true + true + + + + diff --git a/jetty-test-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/jetty-test-webapp/src/main/webapp/WEB-INF/jetty-web.xml new file mode 100644 index 00000000000..b0e838f07db --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/WEB-INF/jetty-web.xml @@ -0,0 +1,14 @@ + + + + + + + executing jetty-web.xml + + diff --git a/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Entries b/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Entries new file mode 100644 index 00000000000..e7449577fd7 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Entries @@ -0,0 +1,2 @@ +/panel.tag/1.1/Sat Nov 8 22:30:42 2003// +D diff --git a/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Repository b/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Repository new file mode 100644 index 00000000000..3981e37c04b --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Repository @@ -0,0 +1 @@ +core/training/osJ2eeWebTraining/exercises/webappsrc/day2jsp/WEB-INF/tags diff --git a/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Root b/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Root new file mode 100644 index 00000000000..3109ddb0e62 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/WEB-INF/tags/CVS/Root @@ -0,0 +1 @@ +:ext:jules@cvs.coredevelopers.net:/cvsroot diff --git a/jetty-test-webapp/src/main/webapp/WEB-INF/tags/panel.tag b/jetty-test-webapp/src/main/webapp/WEB-INF/tags/panel.tag new file mode 100644 index 00000000000..fa0540a61db --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/WEB-INF/tags/panel.tag @@ -0,0 +1,17 @@ +<%-- + - Copyright (c) 2002 The Apache Software Foundation. All rights + - reserved. +--%> +<%@ attribute name="color" %> +<%@ attribute name="bgcolor" %> +<%@ attribute name="title" %> + + + + + + + +
    ${title}
    + +
    diff --git a/jetty-test-webapp/src/main/webapp/WEB-INF/web.xml b/jetty-test-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..b6192c710b4 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,303 @@ + + + + Test WebApp + + + + com.acme.TestListener + + + + QoSFilter + org.eclipse.jetty.servlets.QoSFilter + true + + maxRequests + 20 + + + + + QoSFilter + /* + + + + MultiPart + org.eclipse.jetty.servlets.MultiPartFilter + true + + deleteFiles + true + + + + MultiPart + /dump/* + + + + GzipFilter + org.eclipse.jetty.servlets.IncludableGzipFilter + true + + bufferSize + 8192 + + + minGzipSize + 2048 + + + userAgent + (?:Mozilla[^\(]*\(compatible;\s*+([^;]*);.*)|(?:.*?([^\s]+/[^\s]+).*) + + + cacheSize + 1024 + + + excludedAgents + MSIE 6.0 + + + + GzipFilter + /* + + + + + + + + + Hello + com.acme.HelloWorld + 1 + + + + Hello + /hello/* + + + + Dump + com.acme.Dump + true + 1 + admin + + + + Dump + /dump/* + *.dump + + + + Session + com.acme.SessionDump + 1 + + + + Session + /session/* + + + + Cookie + com.acme.CookieDump + 1 + + + + Cookie + /cookie/* + + + + Dispatch + com.acme.DispatchServlet + true + 1 + + + + Dispatch + /dispatch/* + + + + JspSnoop + /snoop.jsp + 1 + + + + JspSnoop + /jspsnoop/* + + + + CGI + org.eclipse.jetty.servlets.CGI + 1 + + + + CGI + /cgi-bin/* + + + + Chat + com.acme.ChatServlet + true + 1 + + + + Chat + /chat/* + + + + 404 + /jspsnoop/ERROR/404 + + + + java.io.IOException + /jspsnoop/IOException + + + + + Any User + /dump/auth/* + *.htm + + + * + + + + + + relax + /dump/auth/relax/* + /auth/relax.txt + GET + HEAD + + + + + + Admin Role + /dump/auth/admin/* + + + admin + + + + + + Forbidden + /dump/auth/noaccess/* + /auth/* + + + + + + + SSL + /dump/auth/ssl/* + + + CONFIDENTIAL + + + + + + + + + FORM + Test Realm + + /logon.html?param=test + /logonError.html?param=test + + + + + + + http://www.acme.com/taglib + + + /WEB-INF/acme-taglib.tld + + + + + + http://www.acme.com/taglib2 + + + /WEB-INF/acme-taglib2.tld + + + + + + 5 + + + + admin + + + user + + + + + diff --git a/jetty-test-webapp/src/main/webapp/auth/file.txt b/jetty-test-webapp/src/main/webapp/auth/file.txt new file mode 100644 index 00000000000..cb74356f3b0 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/auth/file.txt @@ -0,0 +1,10 @@ +0000 0000000000000000000000000000000000000000000000000000000 +0001 0000000000000000000000000000000000000000000000000000000 +0002 0000000000000000000000000000000000000000000000000000000 +0003 0000000000000000000000000000000000000000000000000000000 +0004 0000000000000000000000000000000000000000000000000000000 +0005 0000000000000000000000000000000000000000000000000000000 +0006 0000000000000000000000000000000000000000000000000000000 +0007 0000000000000000000000000000000000000000000000000000000 +0008 0000000000000000000000000000000000000000000000000000000 +0009 0000000000000000000000000000000000000000000000000000000 diff --git a/jetty-test-webapp/src/main/webapp/auth/relax.txt b/jetty-test-webapp/src/main/webapp/auth/relax.txt new file mode 100644 index 00000000000..cb74356f3b0 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/auth/relax.txt @@ -0,0 +1,10 @@ +0000 0000000000000000000000000000000000000000000000000000000 +0001 0000000000000000000000000000000000000000000000000000000 +0002 0000000000000000000000000000000000000000000000000000000 +0003 0000000000000000000000000000000000000000000000000000000 +0004 0000000000000000000000000000000000000000000000000000000 +0005 0000000000000000000000000000000000000000000000000000000 +0006 0000000000000000000000000000000000000000000000000000000 +0007 0000000000000000000000000000000000000000000000000000000 +0008 0000000000000000000000000000000000000000000000000000000 +0009 0000000000000000000000000000000000000000000000000000000 diff --git a/jetty-test-webapp/src/main/webapp/cgi-bin/hello.sh b/jetty-test-webapp/src/main/webapp/cgi-bin/hello.sh new file mode 100755 index 00000000000..86660496a72 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/cgi-bin/hello.sh @@ -0,0 +1,4 @@ +#!/bin/sh +echo "Content-Type: text/html" +echo +echo "

    Hello World

    " diff --git a/jetty-test-webapp/src/main/webapp/d.txt b/jetty-test-webapp/src/main/webapp/d.txt new file mode 100644 index 00000000000..cb74356f3b0 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/d.txt @@ -0,0 +1,10 @@ +0000 0000000000000000000000000000000000000000000000000000000 +0001 0000000000000000000000000000000000000000000000000000000 +0002 0000000000000000000000000000000000000000000000000000000 +0003 0000000000000000000000000000000000000000000000000000000 +0004 0000000000000000000000000000000000000000000000000000000 +0005 0000000000000000000000000000000000000000000000000000000 +0006 0000000000000000000000000000000000000000000000000000000 +0007 0000000000000000000000000000000000000000000000000000000 +0008 0000000000000000000000000000000000000000000000000000000 +0009 0000000000000000000000000000000000000000000000000000000 diff --git a/jetty-test-webapp/src/main/webapp/da.txt b/jetty-test-webapp/src/main/webapp/da.txt new file mode 100644 index 00000000000..39101db7ccf --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/da.txt @@ -0,0 +1,1000 @@ +0000 1111111111111111111111111111111111111111111111111111111 +0001 1111111111111111111111111111111111111111111111111111111 +0002 1111111111111111111111111111111111111111111111111111111 +0003 1111111111111111111111111111111111111111111111111111111 +0004 1111111111111111111111111111111111111111111111111111111 +0005 1111111111111111111111111111111111111111111111111111111 +0006 1111111111111111111111111111111111111111111111111111111 +0007 1111111111111111111111111111111111111111111111111111111 +0008 1111111111111111111111111111111111111111111111111111111 +0009 1111111111111111111111111111111111111111111111111111111 +0010 1111111111111111111111111111111111111111111111111111111 +0011 1111111111111111111111111111111111111111111111111111111 +0012 1111111111111111111111111111111111111111111111111111111 +0013 1111111111111111111111111111111111111111111111111111111 +0014 1111111111111111111111111111111111111111111111111111111 +0015 1111111111111111111111111111111111111111111111111111111 +0016 1111111111111111111111111111111111111111111111111111111 +0017 1111111111111111111111111111111111111111111111111111111 +0018 1111111111111111111111111111111111111111111111111111111 +0019 1111111111111111111111111111111111111111111111111111111 +0020 1111111111111111111111111111111111111111111111111111111 +0021 1111111111111111111111111111111111111111111111111111111 +0022 1111111111111111111111111111111111111111111111111111111 +0023 1111111111111111111111111111111111111111111111111111111 +0024 1111111111111111111111111111111111111111111111111111111 +0025 1111111111111111111111111111111111111111111111111111111 +0026 1111111111111111111111111111111111111111111111111111111 +0027 1111111111111111111111111111111111111111111111111111111 +0028 1111111111111111111111111111111111111111111111111111111 +0029 1111111111111111111111111111111111111111111111111111111 +0030 1111111111111111111111111111111111111111111111111111111 +0031 1111111111111111111111111111111111111111111111111111111 +0032 1111111111111111111111111111111111111111111111111111111 +0033 1111111111111111111111111111111111111111111111111111111 +0034 1111111111111111111111111111111111111111111111111111111 +0035 1111111111111111111111111111111111111111111111111111111 +0036 1111111111111111111111111111111111111111111111111111111 +0037 1111111111111111111111111111111111111111111111111111111 +0038 1111111111111111111111111111111111111111111111111111111 +0039 1111111111111111111111111111111111111111111111111111111 +0040 1111111111111111111111111111111111111111111111111111111 +0041 1111111111111111111111111111111111111111111111111111111 +0042 1111111111111111111111111111111111111111111111111111111 +0043 1111111111111111111111111111111111111111111111111111111 +0044 1111111111111111111111111111111111111111111111111111111 +0045 1111111111111111111111111111111111111111111111111111111 +0046 1111111111111111111111111111111111111111111111111111111 +0047 1111111111111111111111111111111111111111111111111111111 +0048 1111111111111111111111111111111111111111111111111111111 +0049 1111111111111111111111111111111111111111111111111111111 +0050 1111111111111111111111111111111111111111111111111111111 +0051 1111111111111111111111111111111111111111111111111111111 +0052 1111111111111111111111111111111111111111111111111111111 +0053 1111111111111111111111111111111111111111111111111111111 +0054 1111111111111111111111111111111111111111111111111111111 +0055 1111111111111111111111111111111111111111111111111111111 +0056 1111111111111111111111111111111111111111111111111111111 +0057 1111111111111111111111111111111111111111111111111111111 +0058 1111111111111111111111111111111111111111111111111111111 +0059 1111111111111111111111111111111111111111111111111111111 +0060 1111111111111111111111111111111111111111111111111111111 +0061 1111111111111111111111111111111111111111111111111111111 +0062 1111111111111111111111111111111111111111111111111111111 +0063 1111111111111111111111111111111111111111111111111111111 +0064 1111111111111111111111111111111111111111111111111111111 +0065 1111111111111111111111111111111111111111111111111111111 +0066 1111111111111111111111111111111111111111111111111111111 +0067 1111111111111111111111111111111111111111111111111111111 +0068 1111111111111111111111111111111111111111111111111111111 +0069 1111111111111111111111111111111111111111111111111111111 +0070 1111111111111111111111111111111111111111111111111111111 +0071 1111111111111111111111111111111111111111111111111111111 +0072 1111111111111111111111111111111111111111111111111111111 +0073 1111111111111111111111111111111111111111111111111111111 +0074 1111111111111111111111111111111111111111111111111111111 +0075 1111111111111111111111111111111111111111111111111111111 +0076 1111111111111111111111111111111111111111111111111111111 +0077 1111111111111111111111111111111111111111111111111111111 +0078 1111111111111111111111111111111111111111111111111111111 +0079 1111111111111111111111111111111111111111111111111111111 +0080 1111111111111111111111111111111111111111111111111111111 +0081 1111111111111111111111111111111111111111111111111111111 +0082 1111111111111111111111111111111111111111111111111111111 +0083 1111111111111111111111111111111111111111111111111111111 +0084 1111111111111111111111111111111111111111111111111111111 +0085 1111111111111111111111111111111111111111111111111111111 +0086 1111111111111111111111111111111111111111111111111111111 +0087 1111111111111111111111111111111111111111111111111111111 +0088 1111111111111111111111111111111111111111111111111111111 +0089 1111111111111111111111111111111111111111111111111111111 +0090 1111111111111111111111111111111111111111111111111111111 +0091 1111111111111111111111111111111111111111111111111111111 +0092 1111111111111111111111111111111111111111111111111111111 +0093 1111111111111111111111111111111111111111111111111111111 +0094 1111111111111111111111111111111111111111111111111111111 +0095 1111111111111111111111111111111111111111111111111111111 +0096 1111111111111111111111111111111111111111111111111111111 +0097 1111111111111111111111111111111111111111111111111111111 +0098 1111111111111111111111111111111111111111111111111111111 +0099 1111111111111111111111111111111111111111111111111111111 +0100 1111111111111111111111111111111111111111111111111111111 +0101 1111111111111111111111111111111111111111111111111111111 +0102 1111111111111111111111111111111111111111111111111111111 +0103 1111111111111111111111111111111111111111111111111111111 +0104 1111111111111111111111111111111111111111111111111111111 +0105 1111111111111111111111111111111111111111111111111111111 +0106 1111111111111111111111111111111111111111111111111111111 +0107 1111111111111111111111111111111111111111111111111111111 +0108 1111111111111111111111111111111111111111111111111111111 +0109 1111111111111111111111111111111111111111111111111111111 +0110 1111111111111111111111111111111111111111111111111111111 +0111 1111111111111111111111111111111111111111111111111111111 +0112 1111111111111111111111111111111111111111111111111111111 +0113 1111111111111111111111111111111111111111111111111111111 +0114 1111111111111111111111111111111111111111111111111111111 +0115 1111111111111111111111111111111111111111111111111111111 +0116 1111111111111111111111111111111111111111111111111111111 +0117 1111111111111111111111111111111111111111111111111111111 +0118 1111111111111111111111111111111111111111111111111111111 +0119 1111111111111111111111111111111111111111111111111111111 +0120 1111111111111111111111111111111111111111111111111111111 +0121 1111111111111111111111111111111111111111111111111111111 +0122 1111111111111111111111111111111111111111111111111111111 +0123 1111111111111111111111111111111111111111111111111111111 +0124 1111111111111111111111111111111111111111111111111111111 +0125 1111111111111111111111111111111111111111111111111111111 +0126 1111111111111111111111111111111111111111111111111111111 +0127 1111111111111111111111111111111111111111111111111111111 +0128 1111111111111111111111111111111111111111111111111111111 +0129 1111111111111111111111111111111111111111111111111111111 +0130 1111111111111111111111111111111111111111111111111111111 +0131 1111111111111111111111111111111111111111111111111111111 +0132 1111111111111111111111111111111111111111111111111111111 +0133 1111111111111111111111111111111111111111111111111111111 +0134 1111111111111111111111111111111111111111111111111111111 +0135 1111111111111111111111111111111111111111111111111111111 +0136 1111111111111111111111111111111111111111111111111111111 +0137 1111111111111111111111111111111111111111111111111111111 +0138 1111111111111111111111111111111111111111111111111111111 +0139 1111111111111111111111111111111111111111111111111111111 +0140 1111111111111111111111111111111111111111111111111111111 +0141 1111111111111111111111111111111111111111111111111111111 +0142 1111111111111111111111111111111111111111111111111111111 +0143 1111111111111111111111111111111111111111111111111111111 +0144 1111111111111111111111111111111111111111111111111111111 +0145 1111111111111111111111111111111111111111111111111111111 +0146 1111111111111111111111111111111111111111111111111111111 +0147 1111111111111111111111111111111111111111111111111111111 +0148 1111111111111111111111111111111111111111111111111111111 +0149 1111111111111111111111111111111111111111111111111111111 +0150 1111111111111111111111111111111111111111111111111111111 +0151 1111111111111111111111111111111111111111111111111111111 +0152 1111111111111111111111111111111111111111111111111111111 +0153 1111111111111111111111111111111111111111111111111111111 +0154 1111111111111111111111111111111111111111111111111111111 +0155 1111111111111111111111111111111111111111111111111111111 +0156 1111111111111111111111111111111111111111111111111111111 +0157 1111111111111111111111111111111111111111111111111111111 +0158 1111111111111111111111111111111111111111111111111111111 +0159 1111111111111111111111111111111111111111111111111111111 +0160 1111111111111111111111111111111111111111111111111111111 +0161 1111111111111111111111111111111111111111111111111111111 +0162 1111111111111111111111111111111111111111111111111111111 +0163 1111111111111111111111111111111111111111111111111111111 +0164 1111111111111111111111111111111111111111111111111111111 +0165 1111111111111111111111111111111111111111111111111111111 +0166 1111111111111111111111111111111111111111111111111111111 +0167 1111111111111111111111111111111111111111111111111111111 +0168 1111111111111111111111111111111111111111111111111111111 +0169 1111111111111111111111111111111111111111111111111111111 +0170 1111111111111111111111111111111111111111111111111111111 +0171 1111111111111111111111111111111111111111111111111111111 +0172 1111111111111111111111111111111111111111111111111111111 +0173 1111111111111111111111111111111111111111111111111111111 +0174 1111111111111111111111111111111111111111111111111111111 +0175 1111111111111111111111111111111111111111111111111111111 +0176 1111111111111111111111111111111111111111111111111111111 +0177 1111111111111111111111111111111111111111111111111111111 +0178 1111111111111111111111111111111111111111111111111111111 +0179 1111111111111111111111111111111111111111111111111111111 +0180 1111111111111111111111111111111111111111111111111111111 +0181 1111111111111111111111111111111111111111111111111111111 +0182 1111111111111111111111111111111111111111111111111111111 +0183 1111111111111111111111111111111111111111111111111111111 +0184 1111111111111111111111111111111111111111111111111111111 +0185 1111111111111111111111111111111111111111111111111111111 +0186 1111111111111111111111111111111111111111111111111111111 +0187 1111111111111111111111111111111111111111111111111111111 +0188 1111111111111111111111111111111111111111111111111111111 +0189 1111111111111111111111111111111111111111111111111111111 +0190 1111111111111111111111111111111111111111111111111111111 +0191 1111111111111111111111111111111111111111111111111111111 +0192 1111111111111111111111111111111111111111111111111111111 +0193 1111111111111111111111111111111111111111111111111111111 +0194 1111111111111111111111111111111111111111111111111111111 +0195 1111111111111111111111111111111111111111111111111111111 +0196 1111111111111111111111111111111111111111111111111111111 +0197 1111111111111111111111111111111111111111111111111111111 +0198 1111111111111111111111111111111111111111111111111111111 +0199 1111111111111111111111111111111111111111111111111111111 +0200 1111111111111111111111111111111111111111111111111111111 +0201 1111111111111111111111111111111111111111111111111111111 +0202 1111111111111111111111111111111111111111111111111111111 +0203 1111111111111111111111111111111111111111111111111111111 +0204 1111111111111111111111111111111111111111111111111111111 +0205 1111111111111111111111111111111111111111111111111111111 +0206 1111111111111111111111111111111111111111111111111111111 +0207 1111111111111111111111111111111111111111111111111111111 +0208 1111111111111111111111111111111111111111111111111111111 +0209 1111111111111111111111111111111111111111111111111111111 +0210 1111111111111111111111111111111111111111111111111111111 +0211 1111111111111111111111111111111111111111111111111111111 +0212 1111111111111111111111111111111111111111111111111111111 +0213 1111111111111111111111111111111111111111111111111111111 +0214 1111111111111111111111111111111111111111111111111111111 +0215 1111111111111111111111111111111111111111111111111111111 +0216 1111111111111111111111111111111111111111111111111111111 +0217 1111111111111111111111111111111111111111111111111111111 +0218 1111111111111111111111111111111111111111111111111111111 +0219 1111111111111111111111111111111111111111111111111111111 +0220 1111111111111111111111111111111111111111111111111111111 +0221 1111111111111111111111111111111111111111111111111111111 +0222 1111111111111111111111111111111111111111111111111111111 +0223 1111111111111111111111111111111111111111111111111111111 +0224 1111111111111111111111111111111111111111111111111111111 +0225 1111111111111111111111111111111111111111111111111111111 +0226 1111111111111111111111111111111111111111111111111111111 +0227 1111111111111111111111111111111111111111111111111111111 +0228 1111111111111111111111111111111111111111111111111111111 +0229 1111111111111111111111111111111111111111111111111111111 +0230 1111111111111111111111111111111111111111111111111111111 +0231 1111111111111111111111111111111111111111111111111111111 +0232 1111111111111111111111111111111111111111111111111111111 +0233 1111111111111111111111111111111111111111111111111111111 +0234 1111111111111111111111111111111111111111111111111111111 +0235 1111111111111111111111111111111111111111111111111111111 +0236 1111111111111111111111111111111111111111111111111111111 +0237 1111111111111111111111111111111111111111111111111111111 +0238 1111111111111111111111111111111111111111111111111111111 +0239 1111111111111111111111111111111111111111111111111111111 +0240 1111111111111111111111111111111111111111111111111111111 +0241 1111111111111111111111111111111111111111111111111111111 +0242 1111111111111111111111111111111111111111111111111111111 +0243 1111111111111111111111111111111111111111111111111111111 +0244 1111111111111111111111111111111111111111111111111111111 +0245 1111111111111111111111111111111111111111111111111111111 +0246 1111111111111111111111111111111111111111111111111111111 +0247 1111111111111111111111111111111111111111111111111111111 +0248 1111111111111111111111111111111111111111111111111111111 +0249 1111111111111111111111111111111111111111111111111111111 +0250 1111111111111111111111111111111111111111111111111111111 +0251 1111111111111111111111111111111111111111111111111111111 +0252 1111111111111111111111111111111111111111111111111111111 +0253 1111111111111111111111111111111111111111111111111111111 +0254 1111111111111111111111111111111111111111111111111111111 +0255 1111111111111111111111111111111111111111111111111111111 +0256 1111111111111111111111111111111111111111111111111111111 +0257 1111111111111111111111111111111111111111111111111111111 +0258 1111111111111111111111111111111111111111111111111111111 +0259 1111111111111111111111111111111111111111111111111111111 +0260 1111111111111111111111111111111111111111111111111111111 +0261 1111111111111111111111111111111111111111111111111111111 +0262 1111111111111111111111111111111111111111111111111111111 +0263 1111111111111111111111111111111111111111111111111111111 +0264 1111111111111111111111111111111111111111111111111111111 +0265 1111111111111111111111111111111111111111111111111111111 +0266 1111111111111111111111111111111111111111111111111111111 +0267 1111111111111111111111111111111111111111111111111111111 +0268 1111111111111111111111111111111111111111111111111111111 +0269 1111111111111111111111111111111111111111111111111111111 +0270 1111111111111111111111111111111111111111111111111111111 +0271 1111111111111111111111111111111111111111111111111111111 +0272 1111111111111111111111111111111111111111111111111111111 +0273 1111111111111111111111111111111111111111111111111111111 +0274 1111111111111111111111111111111111111111111111111111111 +0275 1111111111111111111111111111111111111111111111111111111 +0276 1111111111111111111111111111111111111111111111111111111 +0277 1111111111111111111111111111111111111111111111111111111 +0278 1111111111111111111111111111111111111111111111111111111 +0279 1111111111111111111111111111111111111111111111111111111 +0280 1111111111111111111111111111111111111111111111111111111 +0281 1111111111111111111111111111111111111111111111111111111 +0282 1111111111111111111111111111111111111111111111111111111 +0283 1111111111111111111111111111111111111111111111111111111 +0284 1111111111111111111111111111111111111111111111111111111 +0285 1111111111111111111111111111111111111111111111111111111 +0286 1111111111111111111111111111111111111111111111111111111 +0287 1111111111111111111111111111111111111111111111111111111 +0288 1111111111111111111111111111111111111111111111111111111 +0289 1111111111111111111111111111111111111111111111111111111 +0290 1111111111111111111111111111111111111111111111111111111 +0291 1111111111111111111111111111111111111111111111111111111 +0292 1111111111111111111111111111111111111111111111111111111 +0293 1111111111111111111111111111111111111111111111111111111 +0294 1111111111111111111111111111111111111111111111111111111 +0295 1111111111111111111111111111111111111111111111111111111 +0296 1111111111111111111111111111111111111111111111111111111 +0297 1111111111111111111111111111111111111111111111111111111 +0298 1111111111111111111111111111111111111111111111111111111 +0299 1111111111111111111111111111111111111111111111111111111 +0300 1111111111111111111111111111111111111111111111111111111 +0301 1111111111111111111111111111111111111111111111111111111 +0302 1111111111111111111111111111111111111111111111111111111 +0303 1111111111111111111111111111111111111111111111111111111 +0304 1111111111111111111111111111111111111111111111111111111 +0305 1111111111111111111111111111111111111111111111111111111 +0306 1111111111111111111111111111111111111111111111111111111 +0307 1111111111111111111111111111111111111111111111111111111 +0308 1111111111111111111111111111111111111111111111111111111 +0309 1111111111111111111111111111111111111111111111111111111 +0310 1111111111111111111111111111111111111111111111111111111 +0311 1111111111111111111111111111111111111111111111111111111 +0312 1111111111111111111111111111111111111111111111111111111 +0313 1111111111111111111111111111111111111111111111111111111 +0314 1111111111111111111111111111111111111111111111111111111 +0315 1111111111111111111111111111111111111111111111111111111 +0316 1111111111111111111111111111111111111111111111111111111 +0317 1111111111111111111111111111111111111111111111111111111 +0318 1111111111111111111111111111111111111111111111111111111 +0319 1111111111111111111111111111111111111111111111111111111 +0320 1111111111111111111111111111111111111111111111111111111 +0321 1111111111111111111111111111111111111111111111111111111 +0322 1111111111111111111111111111111111111111111111111111111 +0323 1111111111111111111111111111111111111111111111111111111 +0324 1111111111111111111111111111111111111111111111111111111 +0325 1111111111111111111111111111111111111111111111111111111 +0326 1111111111111111111111111111111111111111111111111111111 +0327 1111111111111111111111111111111111111111111111111111111 +0328 1111111111111111111111111111111111111111111111111111111 +0329 1111111111111111111111111111111111111111111111111111111 +0330 1111111111111111111111111111111111111111111111111111111 +0331 1111111111111111111111111111111111111111111111111111111 +0332 1111111111111111111111111111111111111111111111111111111 +0333 1111111111111111111111111111111111111111111111111111111 +0334 1111111111111111111111111111111111111111111111111111111 +0335 1111111111111111111111111111111111111111111111111111111 +0336 1111111111111111111111111111111111111111111111111111111 +0337 1111111111111111111111111111111111111111111111111111111 +0338 1111111111111111111111111111111111111111111111111111111 +0339 1111111111111111111111111111111111111111111111111111111 +0340 1111111111111111111111111111111111111111111111111111111 +0341 1111111111111111111111111111111111111111111111111111111 +0342 1111111111111111111111111111111111111111111111111111111 +0343 1111111111111111111111111111111111111111111111111111111 +0344 1111111111111111111111111111111111111111111111111111111 +0345 1111111111111111111111111111111111111111111111111111111 +0346 1111111111111111111111111111111111111111111111111111111 +0347 1111111111111111111111111111111111111111111111111111111 +0348 1111111111111111111111111111111111111111111111111111111 +0349 1111111111111111111111111111111111111111111111111111111 +0350 1111111111111111111111111111111111111111111111111111111 +0351 1111111111111111111111111111111111111111111111111111111 +0352 1111111111111111111111111111111111111111111111111111111 +0353 1111111111111111111111111111111111111111111111111111111 +0354 1111111111111111111111111111111111111111111111111111111 +0355 1111111111111111111111111111111111111111111111111111111 +0356 1111111111111111111111111111111111111111111111111111111 +0357 1111111111111111111111111111111111111111111111111111111 +0358 1111111111111111111111111111111111111111111111111111111 +0359 1111111111111111111111111111111111111111111111111111111 +0360 1111111111111111111111111111111111111111111111111111111 +0361 1111111111111111111111111111111111111111111111111111111 +0362 1111111111111111111111111111111111111111111111111111111 +0363 1111111111111111111111111111111111111111111111111111111 +0364 1111111111111111111111111111111111111111111111111111111 +0365 1111111111111111111111111111111111111111111111111111111 +0366 1111111111111111111111111111111111111111111111111111111 +0367 1111111111111111111111111111111111111111111111111111111 +0368 1111111111111111111111111111111111111111111111111111111 +0369 1111111111111111111111111111111111111111111111111111111 +0370 1111111111111111111111111111111111111111111111111111111 +0371 1111111111111111111111111111111111111111111111111111111 +0372 1111111111111111111111111111111111111111111111111111111 +0373 1111111111111111111111111111111111111111111111111111111 +0374 1111111111111111111111111111111111111111111111111111111 +0375 1111111111111111111111111111111111111111111111111111111 +0376 1111111111111111111111111111111111111111111111111111111 +0377 1111111111111111111111111111111111111111111111111111111 +0378 1111111111111111111111111111111111111111111111111111111 +0379 1111111111111111111111111111111111111111111111111111111 +0380 1111111111111111111111111111111111111111111111111111111 +0381 1111111111111111111111111111111111111111111111111111111 +0382 1111111111111111111111111111111111111111111111111111111 +0383 1111111111111111111111111111111111111111111111111111111 +0384 1111111111111111111111111111111111111111111111111111111 +0385 1111111111111111111111111111111111111111111111111111111 +0386 1111111111111111111111111111111111111111111111111111111 +0387 1111111111111111111111111111111111111111111111111111111 +0388 1111111111111111111111111111111111111111111111111111111 +0389 1111111111111111111111111111111111111111111111111111111 +0390 1111111111111111111111111111111111111111111111111111111 +0391 1111111111111111111111111111111111111111111111111111111 +0392 1111111111111111111111111111111111111111111111111111111 +0393 1111111111111111111111111111111111111111111111111111111 +0394 1111111111111111111111111111111111111111111111111111111 +0395 1111111111111111111111111111111111111111111111111111111 +0396 1111111111111111111111111111111111111111111111111111111 +0397 1111111111111111111111111111111111111111111111111111111 +0398 1111111111111111111111111111111111111111111111111111111 +0399 1111111111111111111111111111111111111111111111111111111 +0400 1111111111111111111111111111111111111111111111111111111 +0401 1111111111111111111111111111111111111111111111111111111 +0402 1111111111111111111111111111111111111111111111111111111 +0403 1111111111111111111111111111111111111111111111111111111 +0404 1111111111111111111111111111111111111111111111111111111 +0405 1111111111111111111111111111111111111111111111111111111 +0406 1111111111111111111111111111111111111111111111111111111 +0407 1111111111111111111111111111111111111111111111111111111 +0408 1111111111111111111111111111111111111111111111111111111 +0409 1111111111111111111111111111111111111111111111111111111 +0410 1111111111111111111111111111111111111111111111111111111 +0411 1111111111111111111111111111111111111111111111111111111 +0412 1111111111111111111111111111111111111111111111111111111 +0413 1111111111111111111111111111111111111111111111111111111 +0414 1111111111111111111111111111111111111111111111111111111 +0415 1111111111111111111111111111111111111111111111111111111 +0416 1111111111111111111111111111111111111111111111111111111 +0417 1111111111111111111111111111111111111111111111111111111 +0418 1111111111111111111111111111111111111111111111111111111 +0419 1111111111111111111111111111111111111111111111111111111 +0420 1111111111111111111111111111111111111111111111111111111 +0421 1111111111111111111111111111111111111111111111111111111 +0422 1111111111111111111111111111111111111111111111111111111 +0423 1111111111111111111111111111111111111111111111111111111 +0424 1111111111111111111111111111111111111111111111111111111 +0425 1111111111111111111111111111111111111111111111111111111 +0426 1111111111111111111111111111111111111111111111111111111 +0427 1111111111111111111111111111111111111111111111111111111 +0428 1111111111111111111111111111111111111111111111111111111 +0429 1111111111111111111111111111111111111111111111111111111 +0430 1111111111111111111111111111111111111111111111111111111 +0431 1111111111111111111111111111111111111111111111111111111 +0432 1111111111111111111111111111111111111111111111111111111 +0433 1111111111111111111111111111111111111111111111111111111 +0434 1111111111111111111111111111111111111111111111111111111 +0435 1111111111111111111111111111111111111111111111111111111 +0436 1111111111111111111111111111111111111111111111111111111 +0437 1111111111111111111111111111111111111111111111111111111 +0438 1111111111111111111111111111111111111111111111111111111 +0439 1111111111111111111111111111111111111111111111111111111 +0440 1111111111111111111111111111111111111111111111111111111 +0441 1111111111111111111111111111111111111111111111111111111 +0442 1111111111111111111111111111111111111111111111111111111 +0443 1111111111111111111111111111111111111111111111111111111 +0444 1111111111111111111111111111111111111111111111111111111 +0445 1111111111111111111111111111111111111111111111111111111 +0446 1111111111111111111111111111111111111111111111111111111 +0447 1111111111111111111111111111111111111111111111111111111 +0448 1111111111111111111111111111111111111111111111111111111 +0449 1111111111111111111111111111111111111111111111111111111 +0450 1111111111111111111111111111111111111111111111111111111 +0451 1111111111111111111111111111111111111111111111111111111 +0452 1111111111111111111111111111111111111111111111111111111 +0453 1111111111111111111111111111111111111111111111111111111 +0454 1111111111111111111111111111111111111111111111111111111 +0455 1111111111111111111111111111111111111111111111111111111 +0456 1111111111111111111111111111111111111111111111111111111 +0457 1111111111111111111111111111111111111111111111111111111 +0458 1111111111111111111111111111111111111111111111111111111 +0459 1111111111111111111111111111111111111111111111111111111 +0460 1111111111111111111111111111111111111111111111111111111 +0461 1111111111111111111111111111111111111111111111111111111 +0462 1111111111111111111111111111111111111111111111111111111 +0463 1111111111111111111111111111111111111111111111111111111 +0464 1111111111111111111111111111111111111111111111111111111 +0465 1111111111111111111111111111111111111111111111111111111 +0466 1111111111111111111111111111111111111111111111111111111 +0467 1111111111111111111111111111111111111111111111111111111 +0468 1111111111111111111111111111111111111111111111111111111 +0469 1111111111111111111111111111111111111111111111111111111 +0470 1111111111111111111111111111111111111111111111111111111 +0471 1111111111111111111111111111111111111111111111111111111 +0472 1111111111111111111111111111111111111111111111111111111 +0473 1111111111111111111111111111111111111111111111111111111 +0474 1111111111111111111111111111111111111111111111111111111 +0475 1111111111111111111111111111111111111111111111111111111 +0476 1111111111111111111111111111111111111111111111111111111 +0477 1111111111111111111111111111111111111111111111111111111 +0478 1111111111111111111111111111111111111111111111111111111 +0479 1111111111111111111111111111111111111111111111111111111 +0480 1111111111111111111111111111111111111111111111111111111 +0481 1111111111111111111111111111111111111111111111111111111 +0482 1111111111111111111111111111111111111111111111111111111 +0483 1111111111111111111111111111111111111111111111111111111 +0484 1111111111111111111111111111111111111111111111111111111 +0485 1111111111111111111111111111111111111111111111111111111 +0486 1111111111111111111111111111111111111111111111111111111 +0487 1111111111111111111111111111111111111111111111111111111 +0488 1111111111111111111111111111111111111111111111111111111 +0489 1111111111111111111111111111111111111111111111111111111 +0490 1111111111111111111111111111111111111111111111111111111 +0491 1111111111111111111111111111111111111111111111111111111 +0492 1111111111111111111111111111111111111111111111111111111 +0493 1111111111111111111111111111111111111111111111111111111 +0494 1111111111111111111111111111111111111111111111111111111 +0495 1111111111111111111111111111111111111111111111111111111 +0496 1111111111111111111111111111111111111111111111111111111 +0497 1111111111111111111111111111111111111111111111111111111 +0498 1111111111111111111111111111111111111111111111111111111 +0499 1111111111111111111111111111111111111111111111111111111 +0500 1111111111111111111111111111111111111111111111111111111 +0501 1111111111111111111111111111111111111111111111111111111 +0502 1111111111111111111111111111111111111111111111111111111 +0503 1111111111111111111111111111111111111111111111111111111 +0504 1111111111111111111111111111111111111111111111111111111 +0505 1111111111111111111111111111111111111111111111111111111 +0506 1111111111111111111111111111111111111111111111111111111 +0507 1111111111111111111111111111111111111111111111111111111 +0508 1111111111111111111111111111111111111111111111111111111 +0509 1111111111111111111111111111111111111111111111111111111 +0510 1111111111111111111111111111111111111111111111111111111 +0511 1111111111111111111111111111111111111111111111111111111 +0512 1111111111111111111111111111111111111111111111111111111 +0513 1111111111111111111111111111111111111111111111111111111 +0514 1111111111111111111111111111111111111111111111111111111 +0515 1111111111111111111111111111111111111111111111111111111 +0516 1111111111111111111111111111111111111111111111111111111 +0517 1111111111111111111111111111111111111111111111111111111 +0518 1111111111111111111111111111111111111111111111111111111 +0519 1111111111111111111111111111111111111111111111111111111 +0520 1111111111111111111111111111111111111111111111111111111 +0521 1111111111111111111111111111111111111111111111111111111 +0522 1111111111111111111111111111111111111111111111111111111 +0523 1111111111111111111111111111111111111111111111111111111 +0524 1111111111111111111111111111111111111111111111111111111 +0525 1111111111111111111111111111111111111111111111111111111 +0526 1111111111111111111111111111111111111111111111111111111 +0527 1111111111111111111111111111111111111111111111111111111 +0528 1111111111111111111111111111111111111111111111111111111 +0529 1111111111111111111111111111111111111111111111111111111 +0530 1111111111111111111111111111111111111111111111111111111 +0531 1111111111111111111111111111111111111111111111111111111 +0532 1111111111111111111111111111111111111111111111111111111 +0533 1111111111111111111111111111111111111111111111111111111 +0534 1111111111111111111111111111111111111111111111111111111 +0535 1111111111111111111111111111111111111111111111111111111 +0536 1111111111111111111111111111111111111111111111111111111 +0537 1111111111111111111111111111111111111111111111111111111 +0538 1111111111111111111111111111111111111111111111111111111 +0539 1111111111111111111111111111111111111111111111111111111 +0540 1111111111111111111111111111111111111111111111111111111 +0541 1111111111111111111111111111111111111111111111111111111 +0542 1111111111111111111111111111111111111111111111111111111 +0543 1111111111111111111111111111111111111111111111111111111 +0544 1111111111111111111111111111111111111111111111111111111 +0545 1111111111111111111111111111111111111111111111111111111 +0546 1111111111111111111111111111111111111111111111111111111 +0547 1111111111111111111111111111111111111111111111111111111 +0548 1111111111111111111111111111111111111111111111111111111 +0549 1111111111111111111111111111111111111111111111111111111 +0550 1111111111111111111111111111111111111111111111111111111 +0551 1111111111111111111111111111111111111111111111111111111 +0552 1111111111111111111111111111111111111111111111111111111 +0553 1111111111111111111111111111111111111111111111111111111 +0554 1111111111111111111111111111111111111111111111111111111 +0555 1111111111111111111111111111111111111111111111111111111 +0556 1111111111111111111111111111111111111111111111111111111 +0557 1111111111111111111111111111111111111111111111111111111 +0558 1111111111111111111111111111111111111111111111111111111 +0559 1111111111111111111111111111111111111111111111111111111 +0560 1111111111111111111111111111111111111111111111111111111 +0561 1111111111111111111111111111111111111111111111111111111 +0562 1111111111111111111111111111111111111111111111111111111 +0563 1111111111111111111111111111111111111111111111111111111 +0564 1111111111111111111111111111111111111111111111111111111 +0565 1111111111111111111111111111111111111111111111111111111 +0566 1111111111111111111111111111111111111111111111111111111 +0567 1111111111111111111111111111111111111111111111111111111 +0568 1111111111111111111111111111111111111111111111111111111 +0569 1111111111111111111111111111111111111111111111111111111 +0570 1111111111111111111111111111111111111111111111111111111 +0571 1111111111111111111111111111111111111111111111111111111 +0572 1111111111111111111111111111111111111111111111111111111 +0573 1111111111111111111111111111111111111111111111111111111 +0574 1111111111111111111111111111111111111111111111111111111 +0575 1111111111111111111111111111111111111111111111111111111 +0576 1111111111111111111111111111111111111111111111111111111 +0577 1111111111111111111111111111111111111111111111111111111 +0578 1111111111111111111111111111111111111111111111111111111 +0579 1111111111111111111111111111111111111111111111111111111 +0580 1111111111111111111111111111111111111111111111111111111 +0581 1111111111111111111111111111111111111111111111111111111 +0582 1111111111111111111111111111111111111111111111111111111 +0583 1111111111111111111111111111111111111111111111111111111 +0584 1111111111111111111111111111111111111111111111111111111 +0585 1111111111111111111111111111111111111111111111111111111 +0586 1111111111111111111111111111111111111111111111111111111 +0587 1111111111111111111111111111111111111111111111111111111 +0588 1111111111111111111111111111111111111111111111111111111 +0589 1111111111111111111111111111111111111111111111111111111 +0590 1111111111111111111111111111111111111111111111111111111 +0591 1111111111111111111111111111111111111111111111111111111 +0592 1111111111111111111111111111111111111111111111111111111 +0593 1111111111111111111111111111111111111111111111111111111 +0594 1111111111111111111111111111111111111111111111111111111 +0595 1111111111111111111111111111111111111111111111111111111 +0596 1111111111111111111111111111111111111111111111111111111 +0597 1111111111111111111111111111111111111111111111111111111 +0598 1111111111111111111111111111111111111111111111111111111 +0599 1111111111111111111111111111111111111111111111111111111 +0600 1111111111111111111111111111111111111111111111111111111 +0601 1111111111111111111111111111111111111111111111111111111 +0602 1111111111111111111111111111111111111111111111111111111 +0603 1111111111111111111111111111111111111111111111111111111 +0604 1111111111111111111111111111111111111111111111111111111 +0605 1111111111111111111111111111111111111111111111111111111 +0606 1111111111111111111111111111111111111111111111111111111 +0607 1111111111111111111111111111111111111111111111111111111 +0608 1111111111111111111111111111111111111111111111111111111 +0609 1111111111111111111111111111111111111111111111111111111 +0610 1111111111111111111111111111111111111111111111111111111 +0611 1111111111111111111111111111111111111111111111111111111 +0612 1111111111111111111111111111111111111111111111111111111 +0613 1111111111111111111111111111111111111111111111111111111 +0614 1111111111111111111111111111111111111111111111111111111 +0615 1111111111111111111111111111111111111111111111111111111 +0616 1111111111111111111111111111111111111111111111111111111 +0617 1111111111111111111111111111111111111111111111111111111 +0618 1111111111111111111111111111111111111111111111111111111 +0619 1111111111111111111111111111111111111111111111111111111 +0620 1111111111111111111111111111111111111111111111111111111 +0621 1111111111111111111111111111111111111111111111111111111 +0622 1111111111111111111111111111111111111111111111111111111 +0623 1111111111111111111111111111111111111111111111111111111 +0624 1111111111111111111111111111111111111111111111111111111 +0625 1111111111111111111111111111111111111111111111111111111 +0626 1111111111111111111111111111111111111111111111111111111 +0627 1111111111111111111111111111111111111111111111111111111 +0628 1111111111111111111111111111111111111111111111111111111 +0629 1111111111111111111111111111111111111111111111111111111 +0630 1111111111111111111111111111111111111111111111111111111 +0631 1111111111111111111111111111111111111111111111111111111 +0632 1111111111111111111111111111111111111111111111111111111 +0633 1111111111111111111111111111111111111111111111111111111 +0634 1111111111111111111111111111111111111111111111111111111 +0635 1111111111111111111111111111111111111111111111111111111 +0636 1111111111111111111111111111111111111111111111111111111 +0637 1111111111111111111111111111111111111111111111111111111 +0638 1111111111111111111111111111111111111111111111111111111 +0639 1111111111111111111111111111111111111111111111111111111 +0640 1111111111111111111111111111111111111111111111111111111 +0641 1111111111111111111111111111111111111111111111111111111 +0642 1111111111111111111111111111111111111111111111111111111 +0643 1111111111111111111111111111111111111111111111111111111 +0644 1111111111111111111111111111111111111111111111111111111 +0645 1111111111111111111111111111111111111111111111111111111 +0646 1111111111111111111111111111111111111111111111111111111 +0647 1111111111111111111111111111111111111111111111111111111 +0648 1111111111111111111111111111111111111111111111111111111 +0649 1111111111111111111111111111111111111111111111111111111 +0650 1111111111111111111111111111111111111111111111111111111 +0651 1111111111111111111111111111111111111111111111111111111 +0652 1111111111111111111111111111111111111111111111111111111 +0653 1111111111111111111111111111111111111111111111111111111 +0654 1111111111111111111111111111111111111111111111111111111 +0655 1111111111111111111111111111111111111111111111111111111 +0656 1111111111111111111111111111111111111111111111111111111 +0657 1111111111111111111111111111111111111111111111111111111 +0658 1111111111111111111111111111111111111111111111111111111 +0659 1111111111111111111111111111111111111111111111111111111 +0660 1111111111111111111111111111111111111111111111111111111 +0661 1111111111111111111111111111111111111111111111111111111 +0662 1111111111111111111111111111111111111111111111111111111 +0663 1111111111111111111111111111111111111111111111111111111 +0664 1111111111111111111111111111111111111111111111111111111 +0665 1111111111111111111111111111111111111111111111111111111 +0666 1111111111111111111111111111111111111111111111111111111 +0667 1111111111111111111111111111111111111111111111111111111 +0668 1111111111111111111111111111111111111111111111111111111 +0669 1111111111111111111111111111111111111111111111111111111 +0670 1111111111111111111111111111111111111111111111111111111 +0671 1111111111111111111111111111111111111111111111111111111 +0672 1111111111111111111111111111111111111111111111111111111 +0673 1111111111111111111111111111111111111111111111111111111 +0674 1111111111111111111111111111111111111111111111111111111 +0675 1111111111111111111111111111111111111111111111111111111 +0676 1111111111111111111111111111111111111111111111111111111 +0677 1111111111111111111111111111111111111111111111111111111 +0678 1111111111111111111111111111111111111111111111111111111 +0679 1111111111111111111111111111111111111111111111111111111 +0680 1111111111111111111111111111111111111111111111111111111 +0681 1111111111111111111111111111111111111111111111111111111 +0682 1111111111111111111111111111111111111111111111111111111 +0683 1111111111111111111111111111111111111111111111111111111 +0684 1111111111111111111111111111111111111111111111111111111 +0685 1111111111111111111111111111111111111111111111111111111 +0686 1111111111111111111111111111111111111111111111111111111 +0687 1111111111111111111111111111111111111111111111111111111 +0688 1111111111111111111111111111111111111111111111111111111 +0689 1111111111111111111111111111111111111111111111111111111 +0690 1111111111111111111111111111111111111111111111111111111 +0691 1111111111111111111111111111111111111111111111111111111 +0692 1111111111111111111111111111111111111111111111111111111 +0693 1111111111111111111111111111111111111111111111111111111 +0694 1111111111111111111111111111111111111111111111111111111 +0695 1111111111111111111111111111111111111111111111111111111 +0696 1111111111111111111111111111111111111111111111111111111 +0697 1111111111111111111111111111111111111111111111111111111 +0698 1111111111111111111111111111111111111111111111111111111 +0699 1111111111111111111111111111111111111111111111111111111 +0700 1111111111111111111111111111111111111111111111111111111 +0701 1111111111111111111111111111111111111111111111111111111 +0702 1111111111111111111111111111111111111111111111111111111 +0703 1111111111111111111111111111111111111111111111111111111 +0704 1111111111111111111111111111111111111111111111111111111 +0705 1111111111111111111111111111111111111111111111111111111 +0706 1111111111111111111111111111111111111111111111111111111 +0707 1111111111111111111111111111111111111111111111111111111 +0708 1111111111111111111111111111111111111111111111111111111 +0709 1111111111111111111111111111111111111111111111111111111 +0710 1111111111111111111111111111111111111111111111111111111 +0711 1111111111111111111111111111111111111111111111111111111 +0712 1111111111111111111111111111111111111111111111111111111 +0713 1111111111111111111111111111111111111111111111111111111 +0714 1111111111111111111111111111111111111111111111111111111 +0715 1111111111111111111111111111111111111111111111111111111 +0716 1111111111111111111111111111111111111111111111111111111 +0717 1111111111111111111111111111111111111111111111111111111 +0718 1111111111111111111111111111111111111111111111111111111 +0719 1111111111111111111111111111111111111111111111111111111 +0720 1111111111111111111111111111111111111111111111111111111 +0721 1111111111111111111111111111111111111111111111111111111 +0722 1111111111111111111111111111111111111111111111111111111 +0723 1111111111111111111111111111111111111111111111111111111 +0724 1111111111111111111111111111111111111111111111111111111 +0725 1111111111111111111111111111111111111111111111111111111 +0726 1111111111111111111111111111111111111111111111111111111 +0727 1111111111111111111111111111111111111111111111111111111 +0728 1111111111111111111111111111111111111111111111111111111 +0729 1111111111111111111111111111111111111111111111111111111 +0730 1111111111111111111111111111111111111111111111111111111 +0731 1111111111111111111111111111111111111111111111111111111 +0732 1111111111111111111111111111111111111111111111111111111 +0733 1111111111111111111111111111111111111111111111111111111 +0734 1111111111111111111111111111111111111111111111111111111 +0735 1111111111111111111111111111111111111111111111111111111 +0736 1111111111111111111111111111111111111111111111111111111 +0737 1111111111111111111111111111111111111111111111111111111 +0738 1111111111111111111111111111111111111111111111111111111 +0739 1111111111111111111111111111111111111111111111111111111 +0740 1111111111111111111111111111111111111111111111111111111 +0741 1111111111111111111111111111111111111111111111111111111 +0742 1111111111111111111111111111111111111111111111111111111 +0743 1111111111111111111111111111111111111111111111111111111 +0744 1111111111111111111111111111111111111111111111111111111 +0745 1111111111111111111111111111111111111111111111111111111 +0746 1111111111111111111111111111111111111111111111111111111 +0747 1111111111111111111111111111111111111111111111111111111 +0748 1111111111111111111111111111111111111111111111111111111 +0749 1111111111111111111111111111111111111111111111111111111 +0750 1111111111111111111111111111111111111111111111111111111 +0751 1111111111111111111111111111111111111111111111111111111 +0752 1111111111111111111111111111111111111111111111111111111 +0753 1111111111111111111111111111111111111111111111111111111 +0754 1111111111111111111111111111111111111111111111111111111 +0755 1111111111111111111111111111111111111111111111111111111 +0756 1111111111111111111111111111111111111111111111111111111 +0757 1111111111111111111111111111111111111111111111111111111 +0758 1111111111111111111111111111111111111111111111111111111 +0759 1111111111111111111111111111111111111111111111111111111 +0760 1111111111111111111111111111111111111111111111111111111 +0761 1111111111111111111111111111111111111111111111111111111 +0762 1111111111111111111111111111111111111111111111111111111 +0763 1111111111111111111111111111111111111111111111111111111 +0764 1111111111111111111111111111111111111111111111111111111 +0765 1111111111111111111111111111111111111111111111111111111 +0766 1111111111111111111111111111111111111111111111111111111 +0767 1111111111111111111111111111111111111111111111111111111 +0768 1111111111111111111111111111111111111111111111111111111 +0769 1111111111111111111111111111111111111111111111111111111 +0770 1111111111111111111111111111111111111111111111111111111 +0771 1111111111111111111111111111111111111111111111111111111 +0772 1111111111111111111111111111111111111111111111111111111 +0773 1111111111111111111111111111111111111111111111111111111 +0774 1111111111111111111111111111111111111111111111111111111 +0775 1111111111111111111111111111111111111111111111111111111 +0776 1111111111111111111111111111111111111111111111111111111 +0777 1111111111111111111111111111111111111111111111111111111 +0778 1111111111111111111111111111111111111111111111111111111 +0779 1111111111111111111111111111111111111111111111111111111 +0780 1111111111111111111111111111111111111111111111111111111 +0781 1111111111111111111111111111111111111111111111111111111 +0782 1111111111111111111111111111111111111111111111111111111 +0783 1111111111111111111111111111111111111111111111111111111 +0784 1111111111111111111111111111111111111111111111111111111 +0785 1111111111111111111111111111111111111111111111111111111 +0786 1111111111111111111111111111111111111111111111111111111 +0787 1111111111111111111111111111111111111111111111111111111 +0788 1111111111111111111111111111111111111111111111111111111 +0789 1111111111111111111111111111111111111111111111111111111 +0790 1111111111111111111111111111111111111111111111111111111 +0791 1111111111111111111111111111111111111111111111111111111 +0792 1111111111111111111111111111111111111111111111111111111 +0793 1111111111111111111111111111111111111111111111111111111 +0794 1111111111111111111111111111111111111111111111111111111 +0795 1111111111111111111111111111111111111111111111111111111 +0796 1111111111111111111111111111111111111111111111111111111 +0797 1111111111111111111111111111111111111111111111111111111 +0798 1111111111111111111111111111111111111111111111111111111 +0799 1111111111111111111111111111111111111111111111111111111 +0800 1111111111111111111111111111111111111111111111111111111 +0801 1111111111111111111111111111111111111111111111111111111 +0802 1111111111111111111111111111111111111111111111111111111 +0803 1111111111111111111111111111111111111111111111111111111 +0804 1111111111111111111111111111111111111111111111111111111 +0805 1111111111111111111111111111111111111111111111111111111 +0806 1111111111111111111111111111111111111111111111111111111 +0807 1111111111111111111111111111111111111111111111111111111 +0808 1111111111111111111111111111111111111111111111111111111 +0809 1111111111111111111111111111111111111111111111111111111 +0810 1111111111111111111111111111111111111111111111111111111 +0811 1111111111111111111111111111111111111111111111111111111 +0812 1111111111111111111111111111111111111111111111111111111 +0813 1111111111111111111111111111111111111111111111111111111 +0814 1111111111111111111111111111111111111111111111111111111 +0815 1111111111111111111111111111111111111111111111111111111 +0816 1111111111111111111111111111111111111111111111111111111 +0817 1111111111111111111111111111111111111111111111111111111 +0818 1111111111111111111111111111111111111111111111111111111 +0819 1111111111111111111111111111111111111111111111111111111 +0820 1111111111111111111111111111111111111111111111111111111 +0821 1111111111111111111111111111111111111111111111111111111 +0822 1111111111111111111111111111111111111111111111111111111 +0823 1111111111111111111111111111111111111111111111111111111 +0824 1111111111111111111111111111111111111111111111111111111 +0825 1111111111111111111111111111111111111111111111111111111 +0826 1111111111111111111111111111111111111111111111111111111 +0827 1111111111111111111111111111111111111111111111111111111 +0828 1111111111111111111111111111111111111111111111111111111 +0829 1111111111111111111111111111111111111111111111111111111 +0830 1111111111111111111111111111111111111111111111111111111 +0831 1111111111111111111111111111111111111111111111111111111 +0832 1111111111111111111111111111111111111111111111111111111 +0833 1111111111111111111111111111111111111111111111111111111 +0834 1111111111111111111111111111111111111111111111111111111 +0835 1111111111111111111111111111111111111111111111111111111 +0836 1111111111111111111111111111111111111111111111111111111 +0837 1111111111111111111111111111111111111111111111111111111 +0838 1111111111111111111111111111111111111111111111111111111 +0839 1111111111111111111111111111111111111111111111111111111 +0840 1111111111111111111111111111111111111111111111111111111 +0841 1111111111111111111111111111111111111111111111111111111 +0842 1111111111111111111111111111111111111111111111111111111 +0843 1111111111111111111111111111111111111111111111111111111 +0844 1111111111111111111111111111111111111111111111111111111 +0845 1111111111111111111111111111111111111111111111111111111 +0846 1111111111111111111111111111111111111111111111111111111 +0847 1111111111111111111111111111111111111111111111111111111 +0848 1111111111111111111111111111111111111111111111111111111 +0849 1111111111111111111111111111111111111111111111111111111 +0850 1111111111111111111111111111111111111111111111111111111 +0851 1111111111111111111111111111111111111111111111111111111 +0852 1111111111111111111111111111111111111111111111111111111 +0853 1111111111111111111111111111111111111111111111111111111 +0854 1111111111111111111111111111111111111111111111111111111 +0855 1111111111111111111111111111111111111111111111111111111 +0856 1111111111111111111111111111111111111111111111111111111 +0857 1111111111111111111111111111111111111111111111111111111 +0858 1111111111111111111111111111111111111111111111111111111 +0859 1111111111111111111111111111111111111111111111111111111 +0860 1111111111111111111111111111111111111111111111111111111 +0861 1111111111111111111111111111111111111111111111111111111 +0862 1111111111111111111111111111111111111111111111111111111 +0863 1111111111111111111111111111111111111111111111111111111 +0864 1111111111111111111111111111111111111111111111111111111 +0865 1111111111111111111111111111111111111111111111111111111 +0866 1111111111111111111111111111111111111111111111111111111 +0867 1111111111111111111111111111111111111111111111111111111 +0868 1111111111111111111111111111111111111111111111111111111 +0869 1111111111111111111111111111111111111111111111111111111 +0870 1111111111111111111111111111111111111111111111111111111 +0871 1111111111111111111111111111111111111111111111111111111 +0872 1111111111111111111111111111111111111111111111111111111 +0873 1111111111111111111111111111111111111111111111111111111 +0874 1111111111111111111111111111111111111111111111111111111 +0875 1111111111111111111111111111111111111111111111111111111 +0876 1111111111111111111111111111111111111111111111111111111 +0877 1111111111111111111111111111111111111111111111111111111 +0878 1111111111111111111111111111111111111111111111111111111 +0879 1111111111111111111111111111111111111111111111111111111 +0880 1111111111111111111111111111111111111111111111111111111 +0881 1111111111111111111111111111111111111111111111111111111 +0882 1111111111111111111111111111111111111111111111111111111 +0883 1111111111111111111111111111111111111111111111111111111 +0884 1111111111111111111111111111111111111111111111111111111 +0885 1111111111111111111111111111111111111111111111111111111 +0886 1111111111111111111111111111111111111111111111111111111 +0887 1111111111111111111111111111111111111111111111111111111 +0888 1111111111111111111111111111111111111111111111111111111 +0889 1111111111111111111111111111111111111111111111111111111 +0890 1111111111111111111111111111111111111111111111111111111 +0891 1111111111111111111111111111111111111111111111111111111 +0892 1111111111111111111111111111111111111111111111111111111 +0893 1111111111111111111111111111111111111111111111111111111 +0894 1111111111111111111111111111111111111111111111111111111 +0895 1111111111111111111111111111111111111111111111111111111 +0896 1111111111111111111111111111111111111111111111111111111 +0897 1111111111111111111111111111111111111111111111111111111 +0898 1111111111111111111111111111111111111111111111111111111 +0899 1111111111111111111111111111111111111111111111111111111 +0900 1111111111111111111111111111111111111111111111111111111 +0901 1111111111111111111111111111111111111111111111111111111 +0902 1111111111111111111111111111111111111111111111111111111 +0903 1111111111111111111111111111111111111111111111111111111 +0904 1111111111111111111111111111111111111111111111111111111 +0905 1111111111111111111111111111111111111111111111111111111 +0906 1111111111111111111111111111111111111111111111111111111 +0907 1111111111111111111111111111111111111111111111111111111 +0908 1111111111111111111111111111111111111111111111111111111 +0909 1111111111111111111111111111111111111111111111111111111 +0910 1111111111111111111111111111111111111111111111111111111 +0911 1111111111111111111111111111111111111111111111111111111 +0912 1111111111111111111111111111111111111111111111111111111 +0913 1111111111111111111111111111111111111111111111111111111 +0914 1111111111111111111111111111111111111111111111111111111 +0915 1111111111111111111111111111111111111111111111111111111 +0916 1111111111111111111111111111111111111111111111111111111 +0917 1111111111111111111111111111111111111111111111111111111 +0918 1111111111111111111111111111111111111111111111111111111 +0919 1111111111111111111111111111111111111111111111111111111 +0920 1111111111111111111111111111111111111111111111111111111 +0921 1111111111111111111111111111111111111111111111111111111 +0922 1111111111111111111111111111111111111111111111111111111 +0923 1111111111111111111111111111111111111111111111111111111 +0924 1111111111111111111111111111111111111111111111111111111 +0925 1111111111111111111111111111111111111111111111111111111 +0926 1111111111111111111111111111111111111111111111111111111 +0927 1111111111111111111111111111111111111111111111111111111 +0928 1111111111111111111111111111111111111111111111111111111 +0929 1111111111111111111111111111111111111111111111111111111 +0930 1111111111111111111111111111111111111111111111111111111 +0931 1111111111111111111111111111111111111111111111111111111 +0932 1111111111111111111111111111111111111111111111111111111 +0933 1111111111111111111111111111111111111111111111111111111 +0934 1111111111111111111111111111111111111111111111111111111 +0935 1111111111111111111111111111111111111111111111111111111 +0936 1111111111111111111111111111111111111111111111111111111 +0937 1111111111111111111111111111111111111111111111111111111 +0938 1111111111111111111111111111111111111111111111111111111 +0939 1111111111111111111111111111111111111111111111111111111 +0940 1111111111111111111111111111111111111111111111111111111 +0941 1111111111111111111111111111111111111111111111111111111 +0942 1111111111111111111111111111111111111111111111111111111 +0943 1111111111111111111111111111111111111111111111111111111 +0944 1111111111111111111111111111111111111111111111111111111 +0945 1111111111111111111111111111111111111111111111111111111 +0946 1111111111111111111111111111111111111111111111111111111 +0947 1111111111111111111111111111111111111111111111111111111 +0948 1111111111111111111111111111111111111111111111111111111 +0949 1111111111111111111111111111111111111111111111111111111 +0950 1111111111111111111111111111111111111111111111111111111 +0951 1111111111111111111111111111111111111111111111111111111 +0952 1111111111111111111111111111111111111111111111111111111 +0953 1111111111111111111111111111111111111111111111111111111 +0954 1111111111111111111111111111111111111111111111111111111 +0955 1111111111111111111111111111111111111111111111111111111 +0956 1111111111111111111111111111111111111111111111111111111 +0957 1111111111111111111111111111111111111111111111111111111 +0958 1111111111111111111111111111111111111111111111111111111 +0959 1111111111111111111111111111111111111111111111111111111 +0960 1111111111111111111111111111111111111111111111111111111 +0961 1111111111111111111111111111111111111111111111111111111 +0962 1111111111111111111111111111111111111111111111111111111 +0963 1111111111111111111111111111111111111111111111111111111 +0964 1111111111111111111111111111111111111111111111111111111 +0965 1111111111111111111111111111111111111111111111111111111 +0966 1111111111111111111111111111111111111111111111111111111 +0967 1111111111111111111111111111111111111111111111111111111 +0968 1111111111111111111111111111111111111111111111111111111 +0969 1111111111111111111111111111111111111111111111111111111 +0970 1111111111111111111111111111111111111111111111111111111 +0971 1111111111111111111111111111111111111111111111111111111 +0972 1111111111111111111111111111111111111111111111111111111 +0973 1111111111111111111111111111111111111111111111111111111 +0974 1111111111111111111111111111111111111111111111111111111 +0975 1111111111111111111111111111111111111111111111111111111 +0976 1111111111111111111111111111111111111111111111111111111 +0977 1111111111111111111111111111111111111111111111111111111 +0978 1111111111111111111111111111111111111111111111111111111 +0979 1111111111111111111111111111111111111111111111111111111 +0980 1111111111111111111111111111111111111111111111111111111 +0981 1111111111111111111111111111111111111111111111111111111 +0982 1111111111111111111111111111111111111111111111111111111 +0983 1111111111111111111111111111111111111111111111111111111 +0984 1111111111111111111111111111111111111111111111111111111 +0985 1111111111111111111111111111111111111111111111111111111 +0986 1111111111111111111111111111111111111111111111111111111 +0987 1111111111111111111111111111111111111111111111111111111 +0988 1111111111111111111111111111111111111111111111111111111 +0989 1111111111111111111111111111111111111111111111111111111 +0990 1111111111111111111111111111111111111111111111111111111 +0991 1111111111111111111111111111111111111111111111111111111 +0992 1111111111111111111111111111111111111111111111111111111 +0993 1111111111111111111111111111111111111111111111111111111 +0994 1111111111111111111111111111111111111111111111111111111 +0995 1111111111111111111111111111111111111111111111111111111 +0996 1111111111111111111111111111111111111111111111111111111 +0997 1111111111111111111111111111111111111111111111111111111 +0998 1111111111111111111111111111111111111111111111111111111 +0999 1111111111111111111111111111111111111111111111111111111 diff --git a/jetty-test-webapp/src/main/webapp/da.txt.gz b/jetty-test-webapp/src/main/webapp/da.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..9ee9be82f0e9aa29ee3fbc58eb477e797fb08c28 GIT binary patch literal 2565 zcmb2|=3uxyE7*;JdFc)7LOS?dR;zCmeF(;?~qzvH^wRt*Ntg!<0ioT&RpmhrGD7HPx-s?8I+3!a8?;+vHQ#`Qs)KG!J~U6{+ma3Ht+twPJQMvvLd}PqeC&^W zaUO|8u#f%GFC&uh_St{r%ZMbRefA&yG9n3gpZX(TMkEpLQ-Ac!h$Q-F{1d!vpY+S! z=YPjbdBeZT%YbQMyv)@`~2^G cX>a&fecAsbUq+Z9zt4Yhn}dvn9`6_!05KAx-2eap literal 0 HcmV?d00001 diff --git a/jetty-test-webapp/src/main/webapp/dat.txt b/jetty-test-webapp/src/main/webapp/dat.txt new file mode 100644 index 00000000000..c6f093c6931 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/dat.txt @@ -0,0 +1,4000 @@ +0000 2222222222222222222222222222222222222222222222222222222 +0001 2222222222222222222222222222222222222222222222222222222 +0002 2222222222222222222222222222222222222222222222222222222 +0003 2222222222222222222222222222222222222222222222222222222 +0004 2222222222222222222222222222222222222222222222222222222 +0005 2222222222222222222222222222222222222222222222222222222 +0006 2222222222222222222222222222222222222222222222222222222 +0007 2222222222222222222222222222222222222222222222222222222 +0008 2222222222222222222222222222222222222222222222222222222 +0009 2222222222222222222222222222222222222222222222222222222 +0010 2222222222222222222222222222222222222222222222222222222 +0011 2222222222222222222222222222222222222222222222222222222 +0012 2222222222222222222222222222222222222222222222222222222 +0013 2222222222222222222222222222222222222222222222222222222 +0014 2222222222222222222222222222222222222222222222222222222 +0015 2222222222222222222222222222222222222222222222222222222 +0016 2222222222222222222222222222222222222222222222222222222 +0017 2222222222222222222222222222222222222222222222222222222 +0018 2222222222222222222222222222222222222222222222222222222 +0019 2222222222222222222222222222222222222222222222222222222 +0020 2222222222222222222222222222222222222222222222222222222 +0021 2222222222222222222222222222222222222222222222222222222 +0022 2222222222222222222222222222222222222222222222222222222 +0023 2222222222222222222222222222222222222222222222222222222 +0024 2222222222222222222222222222222222222222222222222222222 +0025 2222222222222222222222222222222222222222222222222222222 +0026 2222222222222222222222222222222222222222222222222222222 +0027 2222222222222222222222222222222222222222222222222222222 +0028 2222222222222222222222222222222222222222222222222222222 +0029 2222222222222222222222222222222222222222222222222222222 +0030 2222222222222222222222222222222222222222222222222222222 +0031 2222222222222222222222222222222222222222222222222222222 +0032 2222222222222222222222222222222222222222222222222222222 +0033 2222222222222222222222222222222222222222222222222222222 +0034 2222222222222222222222222222222222222222222222222222222 +0035 2222222222222222222222222222222222222222222222222222222 +0036 2222222222222222222222222222222222222222222222222222222 +0037 2222222222222222222222222222222222222222222222222222222 +0038 2222222222222222222222222222222222222222222222222222222 +0039 2222222222222222222222222222222222222222222222222222222 +0040 2222222222222222222222222222222222222222222222222222222 +0041 2222222222222222222222222222222222222222222222222222222 +0042 2222222222222222222222222222222222222222222222222222222 +0043 2222222222222222222222222222222222222222222222222222222 +0044 2222222222222222222222222222222222222222222222222222222 +0045 2222222222222222222222222222222222222222222222222222222 +0046 2222222222222222222222222222222222222222222222222222222 +0047 2222222222222222222222222222222222222222222222222222222 +0048 2222222222222222222222222222222222222222222222222222222 +0049 2222222222222222222222222222222222222222222222222222222 +0050 2222222222222222222222222222222222222222222222222222222 +0051 2222222222222222222222222222222222222222222222222222222 +0052 2222222222222222222222222222222222222222222222222222222 +0053 2222222222222222222222222222222222222222222222222222222 +0054 2222222222222222222222222222222222222222222222222222222 +0055 2222222222222222222222222222222222222222222222222222222 +0056 2222222222222222222222222222222222222222222222222222222 +0057 2222222222222222222222222222222222222222222222222222222 +0058 2222222222222222222222222222222222222222222222222222222 +0059 2222222222222222222222222222222222222222222222222222222 +0060 2222222222222222222222222222222222222222222222222222222 +0061 2222222222222222222222222222222222222222222222222222222 +0062 2222222222222222222222222222222222222222222222222222222 +0063 2222222222222222222222222222222222222222222222222222222 +0064 2222222222222222222222222222222222222222222222222222222 +0065 2222222222222222222222222222222222222222222222222222222 +0066 2222222222222222222222222222222222222222222222222222222 +0067 2222222222222222222222222222222222222222222222222222222 +0068 2222222222222222222222222222222222222222222222222222222 +0069 2222222222222222222222222222222222222222222222222222222 +0070 2222222222222222222222222222222222222222222222222222222 +0071 2222222222222222222222222222222222222222222222222222222 +0072 2222222222222222222222222222222222222222222222222222222 +0073 2222222222222222222222222222222222222222222222222222222 +0074 2222222222222222222222222222222222222222222222222222222 +0075 2222222222222222222222222222222222222222222222222222222 +0076 2222222222222222222222222222222222222222222222222222222 +0077 2222222222222222222222222222222222222222222222222222222 +0078 2222222222222222222222222222222222222222222222222222222 +0079 2222222222222222222222222222222222222222222222222222222 +0080 2222222222222222222222222222222222222222222222222222222 +0081 2222222222222222222222222222222222222222222222222222222 +0082 2222222222222222222222222222222222222222222222222222222 +0083 2222222222222222222222222222222222222222222222222222222 +0084 2222222222222222222222222222222222222222222222222222222 +0085 2222222222222222222222222222222222222222222222222222222 +0086 2222222222222222222222222222222222222222222222222222222 +0087 2222222222222222222222222222222222222222222222222222222 +0088 2222222222222222222222222222222222222222222222222222222 +0089 2222222222222222222222222222222222222222222222222222222 +0090 2222222222222222222222222222222222222222222222222222222 +0091 2222222222222222222222222222222222222222222222222222222 +0092 2222222222222222222222222222222222222222222222222222222 +0093 2222222222222222222222222222222222222222222222222222222 +0094 2222222222222222222222222222222222222222222222222222222 +0095 2222222222222222222222222222222222222222222222222222222 +0096 2222222222222222222222222222222222222222222222222222222 +0097 2222222222222222222222222222222222222222222222222222222 +0098 2222222222222222222222222222222222222222222222222222222 +0099 2222222222222222222222222222222222222222222222222222222 +0100 2222222222222222222222222222222222222222222222222222222 +0101 2222222222222222222222222222222222222222222222222222222 +0102 2222222222222222222222222222222222222222222222222222222 +0103 2222222222222222222222222222222222222222222222222222222 +0104 2222222222222222222222222222222222222222222222222222222 +0105 2222222222222222222222222222222222222222222222222222222 +0106 2222222222222222222222222222222222222222222222222222222 +0107 2222222222222222222222222222222222222222222222222222222 +0108 2222222222222222222222222222222222222222222222222222222 +0109 2222222222222222222222222222222222222222222222222222222 +0110 2222222222222222222222222222222222222222222222222222222 +0111 2222222222222222222222222222222222222222222222222222222 +0112 2222222222222222222222222222222222222222222222222222222 +0113 2222222222222222222222222222222222222222222222222222222 +0114 2222222222222222222222222222222222222222222222222222222 +0115 2222222222222222222222222222222222222222222222222222222 +0116 2222222222222222222222222222222222222222222222222222222 +0117 2222222222222222222222222222222222222222222222222222222 +0118 2222222222222222222222222222222222222222222222222222222 +0119 2222222222222222222222222222222222222222222222222222222 +0120 2222222222222222222222222222222222222222222222222222222 +0121 2222222222222222222222222222222222222222222222222222222 +0122 2222222222222222222222222222222222222222222222222222222 +0123 2222222222222222222222222222222222222222222222222222222 +0124 2222222222222222222222222222222222222222222222222222222 +0125 2222222222222222222222222222222222222222222222222222222 +0126 2222222222222222222222222222222222222222222222222222222 +0127 2222222222222222222222222222222222222222222222222222222 +0128 2222222222222222222222222222222222222222222222222222222 +0129 2222222222222222222222222222222222222222222222222222222 +0130 2222222222222222222222222222222222222222222222222222222 +0131 2222222222222222222222222222222222222222222222222222222 +0132 2222222222222222222222222222222222222222222222222222222 +0133 2222222222222222222222222222222222222222222222222222222 +0134 2222222222222222222222222222222222222222222222222222222 +0135 2222222222222222222222222222222222222222222222222222222 +0136 2222222222222222222222222222222222222222222222222222222 +0137 2222222222222222222222222222222222222222222222222222222 +0138 2222222222222222222222222222222222222222222222222222222 +0139 2222222222222222222222222222222222222222222222222222222 +0140 2222222222222222222222222222222222222222222222222222222 +0141 2222222222222222222222222222222222222222222222222222222 +0142 2222222222222222222222222222222222222222222222222222222 +0143 2222222222222222222222222222222222222222222222222222222 +0144 2222222222222222222222222222222222222222222222222222222 +0145 2222222222222222222222222222222222222222222222222222222 +0146 2222222222222222222222222222222222222222222222222222222 +0147 2222222222222222222222222222222222222222222222222222222 +0148 2222222222222222222222222222222222222222222222222222222 +0149 2222222222222222222222222222222222222222222222222222222 +0150 2222222222222222222222222222222222222222222222222222222 +0151 2222222222222222222222222222222222222222222222222222222 +0152 2222222222222222222222222222222222222222222222222222222 +0153 2222222222222222222222222222222222222222222222222222222 +0154 2222222222222222222222222222222222222222222222222222222 +0155 2222222222222222222222222222222222222222222222222222222 +0156 2222222222222222222222222222222222222222222222222222222 +0157 2222222222222222222222222222222222222222222222222222222 +0158 2222222222222222222222222222222222222222222222222222222 +0159 2222222222222222222222222222222222222222222222222222222 +0160 2222222222222222222222222222222222222222222222222222222 +0161 2222222222222222222222222222222222222222222222222222222 +0162 2222222222222222222222222222222222222222222222222222222 +0163 2222222222222222222222222222222222222222222222222222222 +0164 2222222222222222222222222222222222222222222222222222222 +0165 2222222222222222222222222222222222222222222222222222222 +0166 2222222222222222222222222222222222222222222222222222222 +0167 2222222222222222222222222222222222222222222222222222222 +0168 2222222222222222222222222222222222222222222222222222222 +0169 2222222222222222222222222222222222222222222222222222222 +0170 2222222222222222222222222222222222222222222222222222222 +0171 2222222222222222222222222222222222222222222222222222222 +0172 2222222222222222222222222222222222222222222222222222222 +0173 2222222222222222222222222222222222222222222222222222222 +0174 2222222222222222222222222222222222222222222222222222222 +0175 2222222222222222222222222222222222222222222222222222222 +0176 2222222222222222222222222222222222222222222222222222222 +0177 2222222222222222222222222222222222222222222222222222222 +0178 2222222222222222222222222222222222222222222222222222222 +0179 2222222222222222222222222222222222222222222222222222222 +0180 2222222222222222222222222222222222222222222222222222222 +0181 2222222222222222222222222222222222222222222222222222222 +0182 2222222222222222222222222222222222222222222222222222222 +0183 2222222222222222222222222222222222222222222222222222222 +0184 2222222222222222222222222222222222222222222222222222222 +0185 2222222222222222222222222222222222222222222222222222222 +0186 2222222222222222222222222222222222222222222222222222222 +0187 2222222222222222222222222222222222222222222222222222222 +0188 2222222222222222222222222222222222222222222222222222222 +0189 2222222222222222222222222222222222222222222222222222222 +0190 2222222222222222222222222222222222222222222222222222222 +0191 2222222222222222222222222222222222222222222222222222222 +0192 2222222222222222222222222222222222222222222222222222222 +0193 2222222222222222222222222222222222222222222222222222222 +0194 2222222222222222222222222222222222222222222222222222222 +0195 2222222222222222222222222222222222222222222222222222222 +0196 2222222222222222222222222222222222222222222222222222222 +0197 2222222222222222222222222222222222222222222222222222222 +0198 2222222222222222222222222222222222222222222222222222222 +0199 2222222222222222222222222222222222222222222222222222222 +0200 2222222222222222222222222222222222222222222222222222222 +0201 2222222222222222222222222222222222222222222222222222222 +0202 2222222222222222222222222222222222222222222222222222222 +0203 2222222222222222222222222222222222222222222222222222222 +0204 2222222222222222222222222222222222222222222222222222222 +0205 2222222222222222222222222222222222222222222222222222222 +0206 2222222222222222222222222222222222222222222222222222222 +0207 2222222222222222222222222222222222222222222222222222222 +0208 2222222222222222222222222222222222222222222222222222222 +0209 2222222222222222222222222222222222222222222222222222222 +0210 2222222222222222222222222222222222222222222222222222222 +0211 2222222222222222222222222222222222222222222222222222222 +0212 2222222222222222222222222222222222222222222222222222222 +0213 2222222222222222222222222222222222222222222222222222222 +0214 2222222222222222222222222222222222222222222222222222222 +0215 2222222222222222222222222222222222222222222222222222222 +0216 2222222222222222222222222222222222222222222222222222222 +0217 2222222222222222222222222222222222222222222222222222222 +0218 2222222222222222222222222222222222222222222222222222222 +0219 2222222222222222222222222222222222222222222222222222222 +0220 2222222222222222222222222222222222222222222222222222222 +0221 2222222222222222222222222222222222222222222222222222222 +0222 2222222222222222222222222222222222222222222222222222222 +0223 2222222222222222222222222222222222222222222222222222222 +0224 2222222222222222222222222222222222222222222222222222222 +0225 2222222222222222222222222222222222222222222222222222222 +0226 2222222222222222222222222222222222222222222222222222222 +0227 2222222222222222222222222222222222222222222222222222222 +0228 2222222222222222222222222222222222222222222222222222222 +0229 2222222222222222222222222222222222222222222222222222222 +0230 2222222222222222222222222222222222222222222222222222222 +0231 2222222222222222222222222222222222222222222222222222222 +0232 2222222222222222222222222222222222222222222222222222222 +0233 2222222222222222222222222222222222222222222222222222222 +0234 2222222222222222222222222222222222222222222222222222222 +0235 2222222222222222222222222222222222222222222222222222222 +0236 2222222222222222222222222222222222222222222222222222222 +0237 2222222222222222222222222222222222222222222222222222222 +0238 2222222222222222222222222222222222222222222222222222222 +0239 2222222222222222222222222222222222222222222222222222222 +0240 2222222222222222222222222222222222222222222222222222222 +0241 2222222222222222222222222222222222222222222222222222222 +0242 2222222222222222222222222222222222222222222222222222222 +0243 2222222222222222222222222222222222222222222222222222222 +0244 2222222222222222222222222222222222222222222222222222222 +0245 2222222222222222222222222222222222222222222222222222222 +0246 2222222222222222222222222222222222222222222222222222222 +0247 2222222222222222222222222222222222222222222222222222222 +0248 2222222222222222222222222222222222222222222222222222222 +0249 2222222222222222222222222222222222222222222222222222222 +0250 2222222222222222222222222222222222222222222222222222222 +0251 2222222222222222222222222222222222222222222222222222222 +0252 2222222222222222222222222222222222222222222222222222222 +0253 2222222222222222222222222222222222222222222222222222222 +0254 2222222222222222222222222222222222222222222222222222222 +0255 2222222222222222222222222222222222222222222222222222222 +0256 2222222222222222222222222222222222222222222222222222222 +0257 2222222222222222222222222222222222222222222222222222222 +0258 2222222222222222222222222222222222222222222222222222222 +0259 2222222222222222222222222222222222222222222222222222222 +0260 2222222222222222222222222222222222222222222222222222222 +0261 2222222222222222222222222222222222222222222222222222222 +0262 2222222222222222222222222222222222222222222222222222222 +0263 2222222222222222222222222222222222222222222222222222222 +0264 2222222222222222222222222222222222222222222222222222222 +0265 2222222222222222222222222222222222222222222222222222222 +0266 2222222222222222222222222222222222222222222222222222222 +0267 2222222222222222222222222222222222222222222222222222222 +0268 2222222222222222222222222222222222222222222222222222222 +0269 2222222222222222222222222222222222222222222222222222222 +0270 2222222222222222222222222222222222222222222222222222222 +0271 2222222222222222222222222222222222222222222222222222222 +0272 2222222222222222222222222222222222222222222222222222222 +0273 2222222222222222222222222222222222222222222222222222222 +0274 2222222222222222222222222222222222222222222222222222222 +0275 2222222222222222222222222222222222222222222222222222222 +0276 2222222222222222222222222222222222222222222222222222222 +0277 2222222222222222222222222222222222222222222222222222222 +0278 2222222222222222222222222222222222222222222222222222222 +0279 2222222222222222222222222222222222222222222222222222222 +0280 2222222222222222222222222222222222222222222222222222222 +0281 2222222222222222222222222222222222222222222222222222222 +0282 2222222222222222222222222222222222222222222222222222222 +0283 2222222222222222222222222222222222222222222222222222222 +0284 2222222222222222222222222222222222222222222222222222222 +0285 2222222222222222222222222222222222222222222222222222222 +0286 2222222222222222222222222222222222222222222222222222222 +0287 2222222222222222222222222222222222222222222222222222222 +0288 2222222222222222222222222222222222222222222222222222222 +0289 2222222222222222222222222222222222222222222222222222222 +0290 2222222222222222222222222222222222222222222222222222222 +0291 2222222222222222222222222222222222222222222222222222222 +0292 2222222222222222222222222222222222222222222222222222222 +0293 2222222222222222222222222222222222222222222222222222222 +0294 2222222222222222222222222222222222222222222222222222222 +0295 2222222222222222222222222222222222222222222222222222222 +0296 2222222222222222222222222222222222222222222222222222222 +0297 2222222222222222222222222222222222222222222222222222222 +0298 2222222222222222222222222222222222222222222222222222222 +0299 2222222222222222222222222222222222222222222222222222222 +0300 2222222222222222222222222222222222222222222222222222222 +0301 2222222222222222222222222222222222222222222222222222222 +0302 2222222222222222222222222222222222222222222222222222222 +0303 2222222222222222222222222222222222222222222222222222222 +0304 2222222222222222222222222222222222222222222222222222222 +0305 2222222222222222222222222222222222222222222222222222222 +0306 2222222222222222222222222222222222222222222222222222222 +0307 2222222222222222222222222222222222222222222222222222222 +0308 2222222222222222222222222222222222222222222222222222222 +0309 2222222222222222222222222222222222222222222222222222222 +0310 2222222222222222222222222222222222222222222222222222222 +0311 2222222222222222222222222222222222222222222222222222222 +0312 2222222222222222222222222222222222222222222222222222222 +0313 2222222222222222222222222222222222222222222222222222222 +0314 2222222222222222222222222222222222222222222222222222222 +0315 2222222222222222222222222222222222222222222222222222222 +0316 2222222222222222222222222222222222222222222222222222222 +0317 2222222222222222222222222222222222222222222222222222222 +0318 2222222222222222222222222222222222222222222222222222222 +0319 2222222222222222222222222222222222222222222222222222222 +0320 2222222222222222222222222222222222222222222222222222222 +0321 2222222222222222222222222222222222222222222222222222222 +0322 2222222222222222222222222222222222222222222222222222222 +0323 2222222222222222222222222222222222222222222222222222222 +0324 2222222222222222222222222222222222222222222222222222222 +0325 2222222222222222222222222222222222222222222222222222222 +0326 2222222222222222222222222222222222222222222222222222222 +0327 2222222222222222222222222222222222222222222222222222222 +0328 2222222222222222222222222222222222222222222222222222222 +0329 2222222222222222222222222222222222222222222222222222222 +0330 2222222222222222222222222222222222222222222222222222222 +0331 2222222222222222222222222222222222222222222222222222222 +0332 2222222222222222222222222222222222222222222222222222222 +0333 2222222222222222222222222222222222222222222222222222222 +0334 2222222222222222222222222222222222222222222222222222222 +0335 2222222222222222222222222222222222222222222222222222222 +0336 2222222222222222222222222222222222222222222222222222222 +0337 2222222222222222222222222222222222222222222222222222222 +0338 2222222222222222222222222222222222222222222222222222222 +0339 2222222222222222222222222222222222222222222222222222222 +0340 2222222222222222222222222222222222222222222222222222222 +0341 2222222222222222222222222222222222222222222222222222222 +0342 2222222222222222222222222222222222222222222222222222222 +0343 2222222222222222222222222222222222222222222222222222222 +0344 2222222222222222222222222222222222222222222222222222222 +0345 2222222222222222222222222222222222222222222222222222222 +0346 2222222222222222222222222222222222222222222222222222222 +0347 2222222222222222222222222222222222222222222222222222222 +0348 2222222222222222222222222222222222222222222222222222222 +0349 2222222222222222222222222222222222222222222222222222222 +0350 2222222222222222222222222222222222222222222222222222222 +0351 2222222222222222222222222222222222222222222222222222222 +0352 2222222222222222222222222222222222222222222222222222222 +0353 2222222222222222222222222222222222222222222222222222222 +0354 2222222222222222222222222222222222222222222222222222222 +0355 2222222222222222222222222222222222222222222222222222222 +0356 2222222222222222222222222222222222222222222222222222222 +0357 2222222222222222222222222222222222222222222222222222222 +0358 2222222222222222222222222222222222222222222222222222222 +0359 2222222222222222222222222222222222222222222222222222222 +0360 2222222222222222222222222222222222222222222222222222222 +0361 2222222222222222222222222222222222222222222222222222222 +0362 2222222222222222222222222222222222222222222222222222222 +0363 2222222222222222222222222222222222222222222222222222222 +0364 2222222222222222222222222222222222222222222222222222222 +0365 2222222222222222222222222222222222222222222222222222222 +0366 2222222222222222222222222222222222222222222222222222222 +0367 2222222222222222222222222222222222222222222222222222222 +0368 2222222222222222222222222222222222222222222222222222222 +0369 2222222222222222222222222222222222222222222222222222222 +0370 2222222222222222222222222222222222222222222222222222222 +0371 2222222222222222222222222222222222222222222222222222222 +0372 2222222222222222222222222222222222222222222222222222222 +0373 2222222222222222222222222222222222222222222222222222222 +0374 2222222222222222222222222222222222222222222222222222222 +0375 2222222222222222222222222222222222222222222222222222222 +0376 2222222222222222222222222222222222222222222222222222222 +0377 2222222222222222222222222222222222222222222222222222222 +0378 2222222222222222222222222222222222222222222222222222222 +0379 2222222222222222222222222222222222222222222222222222222 +0380 2222222222222222222222222222222222222222222222222222222 +0381 2222222222222222222222222222222222222222222222222222222 +0382 2222222222222222222222222222222222222222222222222222222 +0383 2222222222222222222222222222222222222222222222222222222 +0384 2222222222222222222222222222222222222222222222222222222 +0385 2222222222222222222222222222222222222222222222222222222 +0386 2222222222222222222222222222222222222222222222222222222 +0387 2222222222222222222222222222222222222222222222222222222 +0388 2222222222222222222222222222222222222222222222222222222 +0389 2222222222222222222222222222222222222222222222222222222 +0390 2222222222222222222222222222222222222222222222222222222 +0391 2222222222222222222222222222222222222222222222222222222 +0392 2222222222222222222222222222222222222222222222222222222 +0393 2222222222222222222222222222222222222222222222222222222 +0394 2222222222222222222222222222222222222222222222222222222 +0395 2222222222222222222222222222222222222222222222222222222 +0396 2222222222222222222222222222222222222222222222222222222 +0397 2222222222222222222222222222222222222222222222222222222 +0398 2222222222222222222222222222222222222222222222222222222 +0399 2222222222222222222222222222222222222222222222222222222 +0400 2222222222222222222222222222222222222222222222222222222 +0401 2222222222222222222222222222222222222222222222222222222 +0402 2222222222222222222222222222222222222222222222222222222 +0403 2222222222222222222222222222222222222222222222222222222 +0404 2222222222222222222222222222222222222222222222222222222 +0405 2222222222222222222222222222222222222222222222222222222 +0406 2222222222222222222222222222222222222222222222222222222 +0407 2222222222222222222222222222222222222222222222222222222 +0408 2222222222222222222222222222222222222222222222222222222 +0409 2222222222222222222222222222222222222222222222222222222 +0410 2222222222222222222222222222222222222222222222222222222 +0411 2222222222222222222222222222222222222222222222222222222 +0412 2222222222222222222222222222222222222222222222222222222 +0413 2222222222222222222222222222222222222222222222222222222 +0414 2222222222222222222222222222222222222222222222222222222 +0415 2222222222222222222222222222222222222222222222222222222 +0416 2222222222222222222222222222222222222222222222222222222 +0417 2222222222222222222222222222222222222222222222222222222 +0418 2222222222222222222222222222222222222222222222222222222 +0419 2222222222222222222222222222222222222222222222222222222 +0420 2222222222222222222222222222222222222222222222222222222 +0421 2222222222222222222222222222222222222222222222222222222 +0422 2222222222222222222222222222222222222222222222222222222 +0423 2222222222222222222222222222222222222222222222222222222 +0424 2222222222222222222222222222222222222222222222222222222 +0425 2222222222222222222222222222222222222222222222222222222 +0426 2222222222222222222222222222222222222222222222222222222 +0427 2222222222222222222222222222222222222222222222222222222 +0428 2222222222222222222222222222222222222222222222222222222 +0429 2222222222222222222222222222222222222222222222222222222 +0430 2222222222222222222222222222222222222222222222222222222 +0431 2222222222222222222222222222222222222222222222222222222 +0432 2222222222222222222222222222222222222222222222222222222 +0433 2222222222222222222222222222222222222222222222222222222 +0434 2222222222222222222222222222222222222222222222222222222 +0435 2222222222222222222222222222222222222222222222222222222 +0436 2222222222222222222222222222222222222222222222222222222 +0437 2222222222222222222222222222222222222222222222222222222 +0438 2222222222222222222222222222222222222222222222222222222 +0439 2222222222222222222222222222222222222222222222222222222 +0440 2222222222222222222222222222222222222222222222222222222 +0441 2222222222222222222222222222222222222222222222222222222 +0442 2222222222222222222222222222222222222222222222222222222 +0443 2222222222222222222222222222222222222222222222222222222 +0444 2222222222222222222222222222222222222222222222222222222 +0445 2222222222222222222222222222222222222222222222222222222 +0446 2222222222222222222222222222222222222222222222222222222 +0447 2222222222222222222222222222222222222222222222222222222 +0448 2222222222222222222222222222222222222222222222222222222 +0449 2222222222222222222222222222222222222222222222222222222 +0450 2222222222222222222222222222222222222222222222222222222 +0451 2222222222222222222222222222222222222222222222222222222 +0452 2222222222222222222222222222222222222222222222222222222 +0453 2222222222222222222222222222222222222222222222222222222 +0454 2222222222222222222222222222222222222222222222222222222 +0455 2222222222222222222222222222222222222222222222222222222 +0456 2222222222222222222222222222222222222222222222222222222 +0457 2222222222222222222222222222222222222222222222222222222 +0458 2222222222222222222222222222222222222222222222222222222 +0459 2222222222222222222222222222222222222222222222222222222 +0460 2222222222222222222222222222222222222222222222222222222 +0461 2222222222222222222222222222222222222222222222222222222 +0462 2222222222222222222222222222222222222222222222222222222 +0463 2222222222222222222222222222222222222222222222222222222 +0464 2222222222222222222222222222222222222222222222222222222 +0465 2222222222222222222222222222222222222222222222222222222 +0466 2222222222222222222222222222222222222222222222222222222 +0467 2222222222222222222222222222222222222222222222222222222 +0468 2222222222222222222222222222222222222222222222222222222 +0469 2222222222222222222222222222222222222222222222222222222 +0470 2222222222222222222222222222222222222222222222222222222 +0471 2222222222222222222222222222222222222222222222222222222 +0472 2222222222222222222222222222222222222222222222222222222 +0473 2222222222222222222222222222222222222222222222222222222 +0474 2222222222222222222222222222222222222222222222222222222 +0475 2222222222222222222222222222222222222222222222222222222 +0476 2222222222222222222222222222222222222222222222222222222 +0477 2222222222222222222222222222222222222222222222222222222 +0478 2222222222222222222222222222222222222222222222222222222 +0479 2222222222222222222222222222222222222222222222222222222 +0480 2222222222222222222222222222222222222222222222222222222 +0481 2222222222222222222222222222222222222222222222222222222 +0482 2222222222222222222222222222222222222222222222222222222 +0483 2222222222222222222222222222222222222222222222222222222 +0484 2222222222222222222222222222222222222222222222222222222 +0485 2222222222222222222222222222222222222222222222222222222 +0486 2222222222222222222222222222222222222222222222222222222 +0487 2222222222222222222222222222222222222222222222222222222 +0488 2222222222222222222222222222222222222222222222222222222 +0489 2222222222222222222222222222222222222222222222222222222 +0490 2222222222222222222222222222222222222222222222222222222 +0491 2222222222222222222222222222222222222222222222222222222 +0492 2222222222222222222222222222222222222222222222222222222 +0493 2222222222222222222222222222222222222222222222222222222 +0494 2222222222222222222222222222222222222222222222222222222 +0495 2222222222222222222222222222222222222222222222222222222 +0496 2222222222222222222222222222222222222222222222222222222 +0497 2222222222222222222222222222222222222222222222222222222 +0498 2222222222222222222222222222222222222222222222222222222 +0499 2222222222222222222222222222222222222222222222222222222 +0500 2222222222222222222222222222222222222222222222222222222 +0501 2222222222222222222222222222222222222222222222222222222 +0502 2222222222222222222222222222222222222222222222222222222 +0503 2222222222222222222222222222222222222222222222222222222 +0504 2222222222222222222222222222222222222222222222222222222 +0505 2222222222222222222222222222222222222222222222222222222 +0506 2222222222222222222222222222222222222222222222222222222 +0507 2222222222222222222222222222222222222222222222222222222 +0508 2222222222222222222222222222222222222222222222222222222 +0509 2222222222222222222222222222222222222222222222222222222 +0510 2222222222222222222222222222222222222222222222222222222 +0511 2222222222222222222222222222222222222222222222222222222 +0512 2222222222222222222222222222222222222222222222222222222 +0513 2222222222222222222222222222222222222222222222222222222 +0514 2222222222222222222222222222222222222222222222222222222 +0515 2222222222222222222222222222222222222222222222222222222 +0516 2222222222222222222222222222222222222222222222222222222 +0517 2222222222222222222222222222222222222222222222222222222 +0518 2222222222222222222222222222222222222222222222222222222 +0519 2222222222222222222222222222222222222222222222222222222 +0520 2222222222222222222222222222222222222222222222222222222 +0521 2222222222222222222222222222222222222222222222222222222 +0522 2222222222222222222222222222222222222222222222222222222 +0523 2222222222222222222222222222222222222222222222222222222 +0524 2222222222222222222222222222222222222222222222222222222 +0525 2222222222222222222222222222222222222222222222222222222 +0526 2222222222222222222222222222222222222222222222222222222 +0527 2222222222222222222222222222222222222222222222222222222 +0528 2222222222222222222222222222222222222222222222222222222 +0529 2222222222222222222222222222222222222222222222222222222 +0530 2222222222222222222222222222222222222222222222222222222 +0531 2222222222222222222222222222222222222222222222222222222 +0532 2222222222222222222222222222222222222222222222222222222 +0533 2222222222222222222222222222222222222222222222222222222 +0534 2222222222222222222222222222222222222222222222222222222 +0535 2222222222222222222222222222222222222222222222222222222 +0536 2222222222222222222222222222222222222222222222222222222 +0537 2222222222222222222222222222222222222222222222222222222 +0538 2222222222222222222222222222222222222222222222222222222 +0539 2222222222222222222222222222222222222222222222222222222 +0540 2222222222222222222222222222222222222222222222222222222 +0541 2222222222222222222222222222222222222222222222222222222 +0542 2222222222222222222222222222222222222222222222222222222 +0543 2222222222222222222222222222222222222222222222222222222 +0544 2222222222222222222222222222222222222222222222222222222 +0545 2222222222222222222222222222222222222222222222222222222 +0546 2222222222222222222222222222222222222222222222222222222 +0547 2222222222222222222222222222222222222222222222222222222 +0548 2222222222222222222222222222222222222222222222222222222 +0549 2222222222222222222222222222222222222222222222222222222 +0550 2222222222222222222222222222222222222222222222222222222 +0551 2222222222222222222222222222222222222222222222222222222 +0552 2222222222222222222222222222222222222222222222222222222 +0553 2222222222222222222222222222222222222222222222222222222 +0554 2222222222222222222222222222222222222222222222222222222 +0555 2222222222222222222222222222222222222222222222222222222 +0556 2222222222222222222222222222222222222222222222222222222 +0557 2222222222222222222222222222222222222222222222222222222 +0558 2222222222222222222222222222222222222222222222222222222 +0559 2222222222222222222222222222222222222222222222222222222 +0560 2222222222222222222222222222222222222222222222222222222 +0561 2222222222222222222222222222222222222222222222222222222 +0562 2222222222222222222222222222222222222222222222222222222 +0563 2222222222222222222222222222222222222222222222222222222 +0564 2222222222222222222222222222222222222222222222222222222 +0565 2222222222222222222222222222222222222222222222222222222 +0566 2222222222222222222222222222222222222222222222222222222 +0567 2222222222222222222222222222222222222222222222222222222 +0568 2222222222222222222222222222222222222222222222222222222 +0569 2222222222222222222222222222222222222222222222222222222 +0570 2222222222222222222222222222222222222222222222222222222 +0571 2222222222222222222222222222222222222222222222222222222 +0572 2222222222222222222222222222222222222222222222222222222 +0573 2222222222222222222222222222222222222222222222222222222 +0574 2222222222222222222222222222222222222222222222222222222 +0575 2222222222222222222222222222222222222222222222222222222 +0576 2222222222222222222222222222222222222222222222222222222 +0577 2222222222222222222222222222222222222222222222222222222 +0578 2222222222222222222222222222222222222222222222222222222 +0579 2222222222222222222222222222222222222222222222222222222 +0580 2222222222222222222222222222222222222222222222222222222 +0581 2222222222222222222222222222222222222222222222222222222 +0582 2222222222222222222222222222222222222222222222222222222 +0583 2222222222222222222222222222222222222222222222222222222 +0584 2222222222222222222222222222222222222222222222222222222 +0585 2222222222222222222222222222222222222222222222222222222 +0586 2222222222222222222222222222222222222222222222222222222 +0587 2222222222222222222222222222222222222222222222222222222 +0588 2222222222222222222222222222222222222222222222222222222 +0589 2222222222222222222222222222222222222222222222222222222 +0590 2222222222222222222222222222222222222222222222222222222 +0591 2222222222222222222222222222222222222222222222222222222 +0592 2222222222222222222222222222222222222222222222222222222 +0593 2222222222222222222222222222222222222222222222222222222 +0594 2222222222222222222222222222222222222222222222222222222 +0595 2222222222222222222222222222222222222222222222222222222 +0596 2222222222222222222222222222222222222222222222222222222 +0597 2222222222222222222222222222222222222222222222222222222 +0598 2222222222222222222222222222222222222222222222222222222 +0599 2222222222222222222222222222222222222222222222222222222 +0600 2222222222222222222222222222222222222222222222222222222 +0601 2222222222222222222222222222222222222222222222222222222 +0602 2222222222222222222222222222222222222222222222222222222 +0603 2222222222222222222222222222222222222222222222222222222 +0604 2222222222222222222222222222222222222222222222222222222 +0605 2222222222222222222222222222222222222222222222222222222 +0606 2222222222222222222222222222222222222222222222222222222 +0607 2222222222222222222222222222222222222222222222222222222 +0608 2222222222222222222222222222222222222222222222222222222 +0609 2222222222222222222222222222222222222222222222222222222 +0610 2222222222222222222222222222222222222222222222222222222 +0611 2222222222222222222222222222222222222222222222222222222 +0612 2222222222222222222222222222222222222222222222222222222 +0613 2222222222222222222222222222222222222222222222222222222 +0614 2222222222222222222222222222222222222222222222222222222 +0615 2222222222222222222222222222222222222222222222222222222 +0616 2222222222222222222222222222222222222222222222222222222 +0617 2222222222222222222222222222222222222222222222222222222 +0618 2222222222222222222222222222222222222222222222222222222 +0619 2222222222222222222222222222222222222222222222222222222 +0620 2222222222222222222222222222222222222222222222222222222 +0621 2222222222222222222222222222222222222222222222222222222 +0622 2222222222222222222222222222222222222222222222222222222 +0623 2222222222222222222222222222222222222222222222222222222 +0624 2222222222222222222222222222222222222222222222222222222 +0625 2222222222222222222222222222222222222222222222222222222 +0626 2222222222222222222222222222222222222222222222222222222 +0627 2222222222222222222222222222222222222222222222222222222 +0628 2222222222222222222222222222222222222222222222222222222 +0629 2222222222222222222222222222222222222222222222222222222 +0630 2222222222222222222222222222222222222222222222222222222 +0631 2222222222222222222222222222222222222222222222222222222 +0632 2222222222222222222222222222222222222222222222222222222 +0633 2222222222222222222222222222222222222222222222222222222 +0634 2222222222222222222222222222222222222222222222222222222 +0635 2222222222222222222222222222222222222222222222222222222 +0636 2222222222222222222222222222222222222222222222222222222 +0637 2222222222222222222222222222222222222222222222222222222 +0638 2222222222222222222222222222222222222222222222222222222 +0639 2222222222222222222222222222222222222222222222222222222 +0640 2222222222222222222222222222222222222222222222222222222 +0641 2222222222222222222222222222222222222222222222222222222 +0642 2222222222222222222222222222222222222222222222222222222 +0643 2222222222222222222222222222222222222222222222222222222 +0644 2222222222222222222222222222222222222222222222222222222 +0645 2222222222222222222222222222222222222222222222222222222 +0646 2222222222222222222222222222222222222222222222222222222 +0647 2222222222222222222222222222222222222222222222222222222 +0648 2222222222222222222222222222222222222222222222222222222 +0649 2222222222222222222222222222222222222222222222222222222 +0650 2222222222222222222222222222222222222222222222222222222 +0651 2222222222222222222222222222222222222222222222222222222 +0652 2222222222222222222222222222222222222222222222222222222 +0653 2222222222222222222222222222222222222222222222222222222 +0654 2222222222222222222222222222222222222222222222222222222 +0655 2222222222222222222222222222222222222222222222222222222 +0656 2222222222222222222222222222222222222222222222222222222 +0657 2222222222222222222222222222222222222222222222222222222 +0658 2222222222222222222222222222222222222222222222222222222 +0659 2222222222222222222222222222222222222222222222222222222 +0660 2222222222222222222222222222222222222222222222222222222 +0661 2222222222222222222222222222222222222222222222222222222 +0662 2222222222222222222222222222222222222222222222222222222 +0663 2222222222222222222222222222222222222222222222222222222 +0664 2222222222222222222222222222222222222222222222222222222 +0665 2222222222222222222222222222222222222222222222222222222 +0666 2222222222222222222222222222222222222222222222222222222 +0667 2222222222222222222222222222222222222222222222222222222 +0668 2222222222222222222222222222222222222222222222222222222 +0669 2222222222222222222222222222222222222222222222222222222 +0670 2222222222222222222222222222222222222222222222222222222 +0671 2222222222222222222222222222222222222222222222222222222 +0672 2222222222222222222222222222222222222222222222222222222 +0673 2222222222222222222222222222222222222222222222222222222 +0674 2222222222222222222222222222222222222222222222222222222 +0675 2222222222222222222222222222222222222222222222222222222 +0676 2222222222222222222222222222222222222222222222222222222 +0677 2222222222222222222222222222222222222222222222222222222 +0678 2222222222222222222222222222222222222222222222222222222 +0679 2222222222222222222222222222222222222222222222222222222 +0680 2222222222222222222222222222222222222222222222222222222 +0681 2222222222222222222222222222222222222222222222222222222 +0682 2222222222222222222222222222222222222222222222222222222 +0683 2222222222222222222222222222222222222222222222222222222 +0684 2222222222222222222222222222222222222222222222222222222 +0685 2222222222222222222222222222222222222222222222222222222 +0686 2222222222222222222222222222222222222222222222222222222 +0687 2222222222222222222222222222222222222222222222222222222 +0688 2222222222222222222222222222222222222222222222222222222 +0689 2222222222222222222222222222222222222222222222222222222 +0690 2222222222222222222222222222222222222222222222222222222 +0691 2222222222222222222222222222222222222222222222222222222 +0692 2222222222222222222222222222222222222222222222222222222 +0693 2222222222222222222222222222222222222222222222222222222 +0694 2222222222222222222222222222222222222222222222222222222 +0695 2222222222222222222222222222222222222222222222222222222 +0696 2222222222222222222222222222222222222222222222222222222 +0697 2222222222222222222222222222222222222222222222222222222 +0698 2222222222222222222222222222222222222222222222222222222 +0699 2222222222222222222222222222222222222222222222222222222 +0700 2222222222222222222222222222222222222222222222222222222 +0701 2222222222222222222222222222222222222222222222222222222 +0702 2222222222222222222222222222222222222222222222222222222 +0703 2222222222222222222222222222222222222222222222222222222 +0704 2222222222222222222222222222222222222222222222222222222 +0705 2222222222222222222222222222222222222222222222222222222 +0706 2222222222222222222222222222222222222222222222222222222 +0707 2222222222222222222222222222222222222222222222222222222 +0708 2222222222222222222222222222222222222222222222222222222 +0709 2222222222222222222222222222222222222222222222222222222 +0710 2222222222222222222222222222222222222222222222222222222 +0711 2222222222222222222222222222222222222222222222222222222 +0712 2222222222222222222222222222222222222222222222222222222 +0713 2222222222222222222222222222222222222222222222222222222 +0714 2222222222222222222222222222222222222222222222222222222 +0715 2222222222222222222222222222222222222222222222222222222 +0716 2222222222222222222222222222222222222222222222222222222 +0717 2222222222222222222222222222222222222222222222222222222 +0718 2222222222222222222222222222222222222222222222222222222 +0719 2222222222222222222222222222222222222222222222222222222 +0720 2222222222222222222222222222222222222222222222222222222 +0721 2222222222222222222222222222222222222222222222222222222 +0722 2222222222222222222222222222222222222222222222222222222 +0723 2222222222222222222222222222222222222222222222222222222 +0724 2222222222222222222222222222222222222222222222222222222 +0725 2222222222222222222222222222222222222222222222222222222 +0726 2222222222222222222222222222222222222222222222222222222 +0727 2222222222222222222222222222222222222222222222222222222 +0728 2222222222222222222222222222222222222222222222222222222 +0729 2222222222222222222222222222222222222222222222222222222 +0730 2222222222222222222222222222222222222222222222222222222 +0731 2222222222222222222222222222222222222222222222222222222 +0732 2222222222222222222222222222222222222222222222222222222 +0733 2222222222222222222222222222222222222222222222222222222 +0734 2222222222222222222222222222222222222222222222222222222 +0735 2222222222222222222222222222222222222222222222222222222 +0736 2222222222222222222222222222222222222222222222222222222 +0737 2222222222222222222222222222222222222222222222222222222 +0738 2222222222222222222222222222222222222222222222222222222 +0739 2222222222222222222222222222222222222222222222222222222 +0740 2222222222222222222222222222222222222222222222222222222 +0741 2222222222222222222222222222222222222222222222222222222 +0742 2222222222222222222222222222222222222222222222222222222 +0743 2222222222222222222222222222222222222222222222222222222 +0744 2222222222222222222222222222222222222222222222222222222 +0745 2222222222222222222222222222222222222222222222222222222 +0746 2222222222222222222222222222222222222222222222222222222 +0747 2222222222222222222222222222222222222222222222222222222 +0748 2222222222222222222222222222222222222222222222222222222 +0749 2222222222222222222222222222222222222222222222222222222 +0750 2222222222222222222222222222222222222222222222222222222 +0751 2222222222222222222222222222222222222222222222222222222 +0752 2222222222222222222222222222222222222222222222222222222 +0753 2222222222222222222222222222222222222222222222222222222 +0754 2222222222222222222222222222222222222222222222222222222 +0755 2222222222222222222222222222222222222222222222222222222 +0756 2222222222222222222222222222222222222222222222222222222 +0757 2222222222222222222222222222222222222222222222222222222 +0758 2222222222222222222222222222222222222222222222222222222 +0759 2222222222222222222222222222222222222222222222222222222 +0760 2222222222222222222222222222222222222222222222222222222 +0761 2222222222222222222222222222222222222222222222222222222 +0762 2222222222222222222222222222222222222222222222222222222 +0763 2222222222222222222222222222222222222222222222222222222 +0764 2222222222222222222222222222222222222222222222222222222 +0765 2222222222222222222222222222222222222222222222222222222 +0766 2222222222222222222222222222222222222222222222222222222 +0767 2222222222222222222222222222222222222222222222222222222 +0768 2222222222222222222222222222222222222222222222222222222 +0769 2222222222222222222222222222222222222222222222222222222 +0770 2222222222222222222222222222222222222222222222222222222 +0771 2222222222222222222222222222222222222222222222222222222 +0772 2222222222222222222222222222222222222222222222222222222 +0773 2222222222222222222222222222222222222222222222222222222 +0774 2222222222222222222222222222222222222222222222222222222 +0775 2222222222222222222222222222222222222222222222222222222 +0776 2222222222222222222222222222222222222222222222222222222 +0777 2222222222222222222222222222222222222222222222222222222 +0778 2222222222222222222222222222222222222222222222222222222 +0779 2222222222222222222222222222222222222222222222222222222 +0780 2222222222222222222222222222222222222222222222222222222 +0781 2222222222222222222222222222222222222222222222222222222 +0782 2222222222222222222222222222222222222222222222222222222 +0783 2222222222222222222222222222222222222222222222222222222 +0784 2222222222222222222222222222222222222222222222222222222 +0785 2222222222222222222222222222222222222222222222222222222 +0786 2222222222222222222222222222222222222222222222222222222 +0787 2222222222222222222222222222222222222222222222222222222 +0788 2222222222222222222222222222222222222222222222222222222 +0789 2222222222222222222222222222222222222222222222222222222 +0790 2222222222222222222222222222222222222222222222222222222 +0791 2222222222222222222222222222222222222222222222222222222 +0792 2222222222222222222222222222222222222222222222222222222 +0793 2222222222222222222222222222222222222222222222222222222 +0794 2222222222222222222222222222222222222222222222222222222 +0795 2222222222222222222222222222222222222222222222222222222 +0796 2222222222222222222222222222222222222222222222222222222 +0797 2222222222222222222222222222222222222222222222222222222 +0798 2222222222222222222222222222222222222222222222222222222 +0799 2222222222222222222222222222222222222222222222222222222 +0800 2222222222222222222222222222222222222222222222222222222 +0801 2222222222222222222222222222222222222222222222222222222 +0802 2222222222222222222222222222222222222222222222222222222 +0803 2222222222222222222222222222222222222222222222222222222 +0804 2222222222222222222222222222222222222222222222222222222 +0805 2222222222222222222222222222222222222222222222222222222 +0806 2222222222222222222222222222222222222222222222222222222 +0807 2222222222222222222222222222222222222222222222222222222 +0808 2222222222222222222222222222222222222222222222222222222 +0809 2222222222222222222222222222222222222222222222222222222 +0810 2222222222222222222222222222222222222222222222222222222 +0811 2222222222222222222222222222222222222222222222222222222 +0812 2222222222222222222222222222222222222222222222222222222 +0813 2222222222222222222222222222222222222222222222222222222 +0814 2222222222222222222222222222222222222222222222222222222 +0815 2222222222222222222222222222222222222222222222222222222 +0816 2222222222222222222222222222222222222222222222222222222 +0817 2222222222222222222222222222222222222222222222222222222 +0818 2222222222222222222222222222222222222222222222222222222 +0819 2222222222222222222222222222222222222222222222222222222 +0820 2222222222222222222222222222222222222222222222222222222 +0821 2222222222222222222222222222222222222222222222222222222 +0822 2222222222222222222222222222222222222222222222222222222 +0823 2222222222222222222222222222222222222222222222222222222 +0824 2222222222222222222222222222222222222222222222222222222 +0825 2222222222222222222222222222222222222222222222222222222 +0826 2222222222222222222222222222222222222222222222222222222 +0827 2222222222222222222222222222222222222222222222222222222 +0828 2222222222222222222222222222222222222222222222222222222 +0829 2222222222222222222222222222222222222222222222222222222 +0830 2222222222222222222222222222222222222222222222222222222 +0831 2222222222222222222222222222222222222222222222222222222 +0832 2222222222222222222222222222222222222222222222222222222 +0833 2222222222222222222222222222222222222222222222222222222 +0834 2222222222222222222222222222222222222222222222222222222 +0835 2222222222222222222222222222222222222222222222222222222 +0836 2222222222222222222222222222222222222222222222222222222 +0837 2222222222222222222222222222222222222222222222222222222 +0838 2222222222222222222222222222222222222222222222222222222 +0839 2222222222222222222222222222222222222222222222222222222 +0840 2222222222222222222222222222222222222222222222222222222 +0841 2222222222222222222222222222222222222222222222222222222 +0842 2222222222222222222222222222222222222222222222222222222 +0843 2222222222222222222222222222222222222222222222222222222 +0844 2222222222222222222222222222222222222222222222222222222 +0845 2222222222222222222222222222222222222222222222222222222 +0846 2222222222222222222222222222222222222222222222222222222 +0847 2222222222222222222222222222222222222222222222222222222 +0848 2222222222222222222222222222222222222222222222222222222 +0849 2222222222222222222222222222222222222222222222222222222 +0850 2222222222222222222222222222222222222222222222222222222 +0851 2222222222222222222222222222222222222222222222222222222 +0852 2222222222222222222222222222222222222222222222222222222 +0853 2222222222222222222222222222222222222222222222222222222 +0854 2222222222222222222222222222222222222222222222222222222 +0855 2222222222222222222222222222222222222222222222222222222 +0856 2222222222222222222222222222222222222222222222222222222 +0857 2222222222222222222222222222222222222222222222222222222 +0858 2222222222222222222222222222222222222222222222222222222 +0859 2222222222222222222222222222222222222222222222222222222 +0860 2222222222222222222222222222222222222222222222222222222 +0861 2222222222222222222222222222222222222222222222222222222 +0862 2222222222222222222222222222222222222222222222222222222 +0863 2222222222222222222222222222222222222222222222222222222 +0864 2222222222222222222222222222222222222222222222222222222 +0865 2222222222222222222222222222222222222222222222222222222 +0866 2222222222222222222222222222222222222222222222222222222 +0867 2222222222222222222222222222222222222222222222222222222 +0868 2222222222222222222222222222222222222222222222222222222 +0869 2222222222222222222222222222222222222222222222222222222 +0870 2222222222222222222222222222222222222222222222222222222 +0871 2222222222222222222222222222222222222222222222222222222 +0872 2222222222222222222222222222222222222222222222222222222 +0873 2222222222222222222222222222222222222222222222222222222 +0874 2222222222222222222222222222222222222222222222222222222 +0875 2222222222222222222222222222222222222222222222222222222 +0876 2222222222222222222222222222222222222222222222222222222 +0877 2222222222222222222222222222222222222222222222222222222 +0878 2222222222222222222222222222222222222222222222222222222 +0879 2222222222222222222222222222222222222222222222222222222 +0880 2222222222222222222222222222222222222222222222222222222 +0881 2222222222222222222222222222222222222222222222222222222 +0882 2222222222222222222222222222222222222222222222222222222 +0883 2222222222222222222222222222222222222222222222222222222 +0884 2222222222222222222222222222222222222222222222222222222 +0885 2222222222222222222222222222222222222222222222222222222 +0886 2222222222222222222222222222222222222222222222222222222 +0887 2222222222222222222222222222222222222222222222222222222 +0888 2222222222222222222222222222222222222222222222222222222 +0889 2222222222222222222222222222222222222222222222222222222 +0890 2222222222222222222222222222222222222222222222222222222 +0891 2222222222222222222222222222222222222222222222222222222 +0892 2222222222222222222222222222222222222222222222222222222 +0893 2222222222222222222222222222222222222222222222222222222 +0894 2222222222222222222222222222222222222222222222222222222 +0895 2222222222222222222222222222222222222222222222222222222 +0896 2222222222222222222222222222222222222222222222222222222 +0897 2222222222222222222222222222222222222222222222222222222 +0898 2222222222222222222222222222222222222222222222222222222 +0899 2222222222222222222222222222222222222222222222222222222 +0900 2222222222222222222222222222222222222222222222222222222 +0901 2222222222222222222222222222222222222222222222222222222 +0902 2222222222222222222222222222222222222222222222222222222 +0903 2222222222222222222222222222222222222222222222222222222 +0904 2222222222222222222222222222222222222222222222222222222 +0905 2222222222222222222222222222222222222222222222222222222 +0906 2222222222222222222222222222222222222222222222222222222 +0907 2222222222222222222222222222222222222222222222222222222 +0908 2222222222222222222222222222222222222222222222222222222 +0909 2222222222222222222222222222222222222222222222222222222 +0910 2222222222222222222222222222222222222222222222222222222 +0911 2222222222222222222222222222222222222222222222222222222 +0912 2222222222222222222222222222222222222222222222222222222 +0913 2222222222222222222222222222222222222222222222222222222 +0914 2222222222222222222222222222222222222222222222222222222 +0915 2222222222222222222222222222222222222222222222222222222 +0916 2222222222222222222222222222222222222222222222222222222 +0917 2222222222222222222222222222222222222222222222222222222 +0918 2222222222222222222222222222222222222222222222222222222 +0919 2222222222222222222222222222222222222222222222222222222 +0920 2222222222222222222222222222222222222222222222222222222 +0921 2222222222222222222222222222222222222222222222222222222 +0922 2222222222222222222222222222222222222222222222222222222 +0923 2222222222222222222222222222222222222222222222222222222 +0924 2222222222222222222222222222222222222222222222222222222 +0925 2222222222222222222222222222222222222222222222222222222 +0926 2222222222222222222222222222222222222222222222222222222 +0927 2222222222222222222222222222222222222222222222222222222 +0928 2222222222222222222222222222222222222222222222222222222 +0929 2222222222222222222222222222222222222222222222222222222 +0930 2222222222222222222222222222222222222222222222222222222 +0931 2222222222222222222222222222222222222222222222222222222 +0932 2222222222222222222222222222222222222222222222222222222 +0933 2222222222222222222222222222222222222222222222222222222 +0934 2222222222222222222222222222222222222222222222222222222 +0935 2222222222222222222222222222222222222222222222222222222 +0936 2222222222222222222222222222222222222222222222222222222 +0937 2222222222222222222222222222222222222222222222222222222 +0938 2222222222222222222222222222222222222222222222222222222 +0939 2222222222222222222222222222222222222222222222222222222 +0940 2222222222222222222222222222222222222222222222222222222 +0941 2222222222222222222222222222222222222222222222222222222 +0942 2222222222222222222222222222222222222222222222222222222 +0943 2222222222222222222222222222222222222222222222222222222 +0944 2222222222222222222222222222222222222222222222222222222 +0945 2222222222222222222222222222222222222222222222222222222 +0946 2222222222222222222222222222222222222222222222222222222 +0947 2222222222222222222222222222222222222222222222222222222 +0948 2222222222222222222222222222222222222222222222222222222 +0949 2222222222222222222222222222222222222222222222222222222 +0950 2222222222222222222222222222222222222222222222222222222 +0951 2222222222222222222222222222222222222222222222222222222 +0952 2222222222222222222222222222222222222222222222222222222 +0953 2222222222222222222222222222222222222222222222222222222 +0954 2222222222222222222222222222222222222222222222222222222 +0955 2222222222222222222222222222222222222222222222222222222 +0956 2222222222222222222222222222222222222222222222222222222 +0957 2222222222222222222222222222222222222222222222222222222 +0958 2222222222222222222222222222222222222222222222222222222 +0959 2222222222222222222222222222222222222222222222222222222 +0960 2222222222222222222222222222222222222222222222222222222 +0961 2222222222222222222222222222222222222222222222222222222 +0962 2222222222222222222222222222222222222222222222222222222 +0963 2222222222222222222222222222222222222222222222222222222 +0964 2222222222222222222222222222222222222222222222222222222 +0965 2222222222222222222222222222222222222222222222222222222 +0966 2222222222222222222222222222222222222222222222222222222 +0967 2222222222222222222222222222222222222222222222222222222 +0968 2222222222222222222222222222222222222222222222222222222 +0969 2222222222222222222222222222222222222222222222222222222 +0970 2222222222222222222222222222222222222222222222222222222 +0971 2222222222222222222222222222222222222222222222222222222 +0972 2222222222222222222222222222222222222222222222222222222 +0973 2222222222222222222222222222222222222222222222222222222 +0974 2222222222222222222222222222222222222222222222222222222 +0975 2222222222222222222222222222222222222222222222222222222 +0976 2222222222222222222222222222222222222222222222222222222 +0977 2222222222222222222222222222222222222222222222222222222 +0978 2222222222222222222222222222222222222222222222222222222 +0979 2222222222222222222222222222222222222222222222222222222 +0980 2222222222222222222222222222222222222222222222222222222 +0981 2222222222222222222222222222222222222222222222222222222 +0982 2222222222222222222222222222222222222222222222222222222 +0983 2222222222222222222222222222222222222222222222222222222 +0984 2222222222222222222222222222222222222222222222222222222 +0985 2222222222222222222222222222222222222222222222222222222 +0986 2222222222222222222222222222222222222222222222222222222 +0987 2222222222222222222222222222222222222222222222222222222 +0988 2222222222222222222222222222222222222222222222222222222 +0989 2222222222222222222222222222222222222222222222222222222 +0990 2222222222222222222222222222222222222222222222222222222 +0991 2222222222222222222222222222222222222222222222222222222 +0992 2222222222222222222222222222222222222222222222222222222 +0993 2222222222222222222222222222222222222222222222222222222 +0994 2222222222222222222222222222222222222222222222222222222 +0995 2222222222222222222222222222222222222222222222222222222 +0996 2222222222222222222222222222222222222222222222222222222 +0997 2222222222222222222222222222222222222222222222222222222 +0998 2222222222222222222222222222222222222222222222222222222 +0999 2222222222222222222222222222222222222222222222222222222 +1000 2222222222222222222222222222222222222222222222222222222 +1001 2222222222222222222222222222222222222222222222222222222 +1002 2222222222222222222222222222222222222222222222222222222 +1003 2222222222222222222222222222222222222222222222222222222 +1004 2222222222222222222222222222222222222222222222222222222 +1005 2222222222222222222222222222222222222222222222222222222 +1006 2222222222222222222222222222222222222222222222222222222 +1007 2222222222222222222222222222222222222222222222222222222 +1008 2222222222222222222222222222222222222222222222222222222 +1009 2222222222222222222222222222222222222222222222222222222 +1010 2222222222222222222222222222222222222222222222222222222 +1011 2222222222222222222222222222222222222222222222222222222 +1012 2222222222222222222222222222222222222222222222222222222 +1013 2222222222222222222222222222222222222222222222222222222 +1014 2222222222222222222222222222222222222222222222222222222 +1015 2222222222222222222222222222222222222222222222222222222 +1016 2222222222222222222222222222222222222222222222222222222 +1017 2222222222222222222222222222222222222222222222222222222 +1018 2222222222222222222222222222222222222222222222222222222 +1019 2222222222222222222222222222222222222222222222222222222 +1020 2222222222222222222222222222222222222222222222222222222 +1021 2222222222222222222222222222222222222222222222222222222 +1022 2222222222222222222222222222222222222222222222222222222 +1023 2222222222222222222222222222222222222222222222222222222 +1024 2222222222222222222222222222222222222222222222222222222 +1025 2222222222222222222222222222222222222222222222222222222 +1026 2222222222222222222222222222222222222222222222222222222 +1027 2222222222222222222222222222222222222222222222222222222 +1028 2222222222222222222222222222222222222222222222222222222 +1029 2222222222222222222222222222222222222222222222222222222 +1030 2222222222222222222222222222222222222222222222222222222 +1031 2222222222222222222222222222222222222222222222222222222 +1032 2222222222222222222222222222222222222222222222222222222 +1033 2222222222222222222222222222222222222222222222222222222 +1034 2222222222222222222222222222222222222222222222222222222 +1035 2222222222222222222222222222222222222222222222222222222 +1036 2222222222222222222222222222222222222222222222222222222 +1037 2222222222222222222222222222222222222222222222222222222 +1038 2222222222222222222222222222222222222222222222222222222 +1039 2222222222222222222222222222222222222222222222222222222 +1040 2222222222222222222222222222222222222222222222222222222 +1041 2222222222222222222222222222222222222222222222222222222 +1042 2222222222222222222222222222222222222222222222222222222 +1043 2222222222222222222222222222222222222222222222222222222 +1044 2222222222222222222222222222222222222222222222222222222 +1045 2222222222222222222222222222222222222222222222222222222 +1046 2222222222222222222222222222222222222222222222222222222 +1047 2222222222222222222222222222222222222222222222222222222 +1048 2222222222222222222222222222222222222222222222222222222 +1049 2222222222222222222222222222222222222222222222222222222 +1050 2222222222222222222222222222222222222222222222222222222 +1051 2222222222222222222222222222222222222222222222222222222 +1052 2222222222222222222222222222222222222222222222222222222 +1053 2222222222222222222222222222222222222222222222222222222 +1054 2222222222222222222222222222222222222222222222222222222 +1055 2222222222222222222222222222222222222222222222222222222 +1056 2222222222222222222222222222222222222222222222222222222 +1057 2222222222222222222222222222222222222222222222222222222 +1058 2222222222222222222222222222222222222222222222222222222 +1059 2222222222222222222222222222222222222222222222222222222 +1060 2222222222222222222222222222222222222222222222222222222 +1061 2222222222222222222222222222222222222222222222222222222 +1062 2222222222222222222222222222222222222222222222222222222 +1063 2222222222222222222222222222222222222222222222222222222 +1064 2222222222222222222222222222222222222222222222222222222 +1065 2222222222222222222222222222222222222222222222222222222 +1066 2222222222222222222222222222222222222222222222222222222 +1067 2222222222222222222222222222222222222222222222222222222 +1068 2222222222222222222222222222222222222222222222222222222 +1069 2222222222222222222222222222222222222222222222222222222 +1070 2222222222222222222222222222222222222222222222222222222 +1071 2222222222222222222222222222222222222222222222222222222 +1072 2222222222222222222222222222222222222222222222222222222 +1073 2222222222222222222222222222222222222222222222222222222 +1074 2222222222222222222222222222222222222222222222222222222 +1075 2222222222222222222222222222222222222222222222222222222 +1076 2222222222222222222222222222222222222222222222222222222 +1077 2222222222222222222222222222222222222222222222222222222 +1078 2222222222222222222222222222222222222222222222222222222 +1079 2222222222222222222222222222222222222222222222222222222 +1080 2222222222222222222222222222222222222222222222222222222 +1081 2222222222222222222222222222222222222222222222222222222 +1082 2222222222222222222222222222222222222222222222222222222 +1083 2222222222222222222222222222222222222222222222222222222 +1084 2222222222222222222222222222222222222222222222222222222 +1085 2222222222222222222222222222222222222222222222222222222 +1086 2222222222222222222222222222222222222222222222222222222 +1087 2222222222222222222222222222222222222222222222222222222 +1088 2222222222222222222222222222222222222222222222222222222 +1089 2222222222222222222222222222222222222222222222222222222 +1090 2222222222222222222222222222222222222222222222222222222 +1091 2222222222222222222222222222222222222222222222222222222 +1092 2222222222222222222222222222222222222222222222222222222 +1093 2222222222222222222222222222222222222222222222222222222 +1094 2222222222222222222222222222222222222222222222222222222 +1095 2222222222222222222222222222222222222222222222222222222 +1096 2222222222222222222222222222222222222222222222222222222 +1097 2222222222222222222222222222222222222222222222222222222 +1098 2222222222222222222222222222222222222222222222222222222 +1099 2222222222222222222222222222222222222222222222222222222 +1100 2222222222222222222222222222222222222222222222222222222 +1101 2222222222222222222222222222222222222222222222222222222 +1102 2222222222222222222222222222222222222222222222222222222 +1103 2222222222222222222222222222222222222222222222222222222 +1104 2222222222222222222222222222222222222222222222222222222 +1105 2222222222222222222222222222222222222222222222222222222 +1106 2222222222222222222222222222222222222222222222222222222 +1107 2222222222222222222222222222222222222222222222222222222 +1108 2222222222222222222222222222222222222222222222222222222 +1109 2222222222222222222222222222222222222222222222222222222 +1110 2222222222222222222222222222222222222222222222222222222 +1111 2222222222222222222222222222222222222222222222222222222 +1112 2222222222222222222222222222222222222222222222222222222 +1113 2222222222222222222222222222222222222222222222222222222 +1114 2222222222222222222222222222222222222222222222222222222 +1115 2222222222222222222222222222222222222222222222222222222 +1116 2222222222222222222222222222222222222222222222222222222 +1117 2222222222222222222222222222222222222222222222222222222 +1118 2222222222222222222222222222222222222222222222222222222 +1119 2222222222222222222222222222222222222222222222222222222 +1120 2222222222222222222222222222222222222222222222222222222 +1121 2222222222222222222222222222222222222222222222222222222 +1122 2222222222222222222222222222222222222222222222222222222 +1123 2222222222222222222222222222222222222222222222222222222 +1124 2222222222222222222222222222222222222222222222222222222 +1125 2222222222222222222222222222222222222222222222222222222 +1126 2222222222222222222222222222222222222222222222222222222 +1127 2222222222222222222222222222222222222222222222222222222 +1128 2222222222222222222222222222222222222222222222222222222 +1129 2222222222222222222222222222222222222222222222222222222 +1130 2222222222222222222222222222222222222222222222222222222 +1131 2222222222222222222222222222222222222222222222222222222 +1132 2222222222222222222222222222222222222222222222222222222 +1133 2222222222222222222222222222222222222222222222222222222 +1134 2222222222222222222222222222222222222222222222222222222 +1135 2222222222222222222222222222222222222222222222222222222 +1136 2222222222222222222222222222222222222222222222222222222 +1137 2222222222222222222222222222222222222222222222222222222 +1138 2222222222222222222222222222222222222222222222222222222 +1139 2222222222222222222222222222222222222222222222222222222 +1140 2222222222222222222222222222222222222222222222222222222 +1141 2222222222222222222222222222222222222222222222222222222 +1142 2222222222222222222222222222222222222222222222222222222 +1143 2222222222222222222222222222222222222222222222222222222 +1144 2222222222222222222222222222222222222222222222222222222 +1145 2222222222222222222222222222222222222222222222222222222 +1146 2222222222222222222222222222222222222222222222222222222 +1147 2222222222222222222222222222222222222222222222222222222 +1148 2222222222222222222222222222222222222222222222222222222 +1149 2222222222222222222222222222222222222222222222222222222 +1150 2222222222222222222222222222222222222222222222222222222 +1151 2222222222222222222222222222222222222222222222222222222 +1152 2222222222222222222222222222222222222222222222222222222 +1153 2222222222222222222222222222222222222222222222222222222 +1154 2222222222222222222222222222222222222222222222222222222 +1155 2222222222222222222222222222222222222222222222222222222 +1156 2222222222222222222222222222222222222222222222222222222 +1157 2222222222222222222222222222222222222222222222222222222 +1158 2222222222222222222222222222222222222222222222222222222 +1159 2222222222222222222222222222222222222222222222222222222 +1160 2222222222222222222222222222222222222222222222222222222 +1161 2222222222222222222222222222222222222222222222222222222 +1162 2222222222222222222222222222222222222222222222222222222 +1163 2222222222222222222222222222222222222222222222222222222 +1164 2222222222222222222222222222222222222222222222222222222 +1165 2222222222222222222222222222222222222222222222222222222 +1166 2222222222222222222222222222222222222222222222222222222 +1167 2222222222222222222222222222222222222222222222222222222 +1168 2222222222222222222222222222222222222222222222222222222 +1169 2222222222222222222222222222222222222222222222222222222 +1170 2222222222222222222222222222222222222222222222222222222 +1171 2222222222222222222222222222222222222222222222222222222 +1172 2222222222222222222222222222222222222222222222222222222 +1173 2222222222222222222222222222222222222222222222222222222 +1174 2222222222222222222222222222222222222222222222222222222 +1175 2222222222222222222222222222222222222222222222222222222 +1176 2222222222222222222222222222222222222222222222222222222 +1177 2222222222222222222222222222222222222222222222222222222 +1178 2222222222222222222222222222222222222222222222222222222 +1179 2222222222222222222222222222222222222222222222222222222 +1180 2222222222222222222222222222222222222222222222222222222 +1181 2222222222222222222222222222222222222222222222222222222 +1182 2222222222222222222222222222222222222222222222222222222 +1183 2222222222222222222222222222222222222222222222222222222 +1184 2222222222222222222222222222222222222222222222222222222 +1185 2222222222222222222222222222222222222222222222222222222 +1186 2222222222222222222222222222222222222222222222222222222 +1187 2222222222222222222222222222222222222222222222222222222 +1188 2222222222222222222222222222222222222222222222222222222 +1189 2222222222222222222222222222222222222222222222222222222 +1190 2222222222222222222222222222222222222222222222222222222 +1191 2222222222222222222222222222222222222222222222222222222 +1192 2222222222222222222222222222222222222222222222222222222 +1193 2222222222222222222222222222222222222222222222222222222 +1194 2222222222222222222222222222222222222222222222222222222 +1195 2222222222222222222222222222222222222222222222222222222 +1196 2222222222222222222222222222222222222222222222222222222 +1197 2222222222222222222222222222222222222222222222222222222 +1198 2222222222222222222222222222222222222222222222222222222 +1199 2222222222222222222222222222222222222222222222222222222 +1200 2222222222222222222222222222222222222222222222222222222 +1201 2222222222222222222222222222222222222222222222222222222 +1202 2222222222222222222222222222222222222222222222222222222 +1203 2222222222222222222222222222222222222222222222222222222 +1204 2222222222222222222222222222222222222222222222222222222 +1205 2222222222222222222222222222222222222222222222222222222 +1206 2222222222222222222222222222222222222222222222222222222 +1207 2222222222222222222222222222222222222222222222222222222 +1208 2222222222222222222222222222222222222222222222222222222 +1209 2222222222222222222222222222222222222222222222222222222 +1210 2222222222222222222222222222222222222222222222222222222 +1211 2222222222222222222222222222222222222222222222222222222 +1212 2222222222222222222222222222222222222222222222222222222 +1213 2222222222222222222222222222222222222222222222222222222 +1214 2222222222222222222222222222222222222222222222222222222 +1215 2222222222222222222222222222222222222222222222222222222 +1216 2222222222222222222222222222222222222222222222222222222 +1217 2222222222222222222222222222222222222222222222222222222 +1218 2222222222222222222222222222222222222222222222222222222 +1219 2222222222222222222222222222222222222222222222222222222 +1220 2222222222222222222222222222222222222222222222222222222 +1221 2222222222222222222222222222222222222222222222222222222 +1222 2222222222222222222222222222222222222222222222222222222 +1223 2222222222222222222222222222222222222222222222222222222 +1224 2222222222222222222222222222222222222222222222222222222 +1225 2222222222222222222222222222222222222222222222222222222 +1226 2222222222222222222222222222222222222222222222222222222 +1227 2222222222222222222222222222222222222222222222222222222 +1228 2222222222222222222222222222222222222222222222222222222 +1229 2222222222222222222222222222222222222222222222222222222 +1230 2222222222222222222222222222222222222222222222222222222 +1231 2222222222222222222222222222222222222222222222222222222 +1232 2222222222222222222222222222222222222222222222222222222 +1233 2222222222222222222222222222222222222222222222222222222 +1234 2222222222222222222222222222222222222222222222222222222 +1235 2222222222222222222222222222222222222222222222222222222 +1236 2222222222222222222222222222222222222222222222222222222 +1237 2222222222222222222222222222222222222222222222222222222 +1238 2222222222222222222222222222222222222222222222222222222 +1239 2222222222222222222222222222222222222222222222222222222 +1240 2222222222222222222222222222222222222222222222222222222 +1241 2222222222222222222222222222222222222222222222222222222 +1242 2222222222222222222222222222222222222222222222222222222 +1243 2222222222222222222222222222222222222222222222222222222 +1244 2222222222222222222222222222222222222222222222222222222 +1245 2222222222222222222222222222222222222222222222222222222 +1246 2222222222222222222222222222222222222222222222222222222 +1247 2222222222222222222222222222222222222222222222222222222 +1248 2222222222222222222222222222222222222222222222222222222 +1249 2222222222222222222222222222222222222222222222222222222 +1250 2222222222222222222222222222222222222222222222222222222 +1251 2222222222222222222222222222222222222222222222222222222 +1252 2222222222222222222222222222222222222222222222222222222 +1253 2222222222222222222222222222222222222222222222222222222 +1254 2222222222222222222222222222222222222222222222222222222 +1255 2222222222222222222222222222222222222222222222222222222 +1256 2222222222222222222222222222222222222222222222222222222 +1257 2222222222222222222222222222222222222222222222222222222 +1258 2222222222222222222222222222222222222222222222222222222 +1259 2222222222222222222222222222222222222222222222222222222 +1260 2222222222222222222222222222222222222222222222222222222 +1261 2222222222222222222222222222222222222222222222222222222 +1262 2222222222222222222222222222222222222222222222222222222 +1263 2222222222222222222222222222222222222222222222222222222 +1264 2222222222222222222222222222222222222222222222222222222 +1265 2222222222222222222222222222222222222222222222222222222 +1266 2222222222222222222222222222222222222222222222222222222 +1267 2222222222222222222222222222222222222222222222222222222 +1268 2222222222222222222222222222222222222222222222222222222 +1269 2222222222222222222222222222222222222222222222222222222 +1270 2222222222222222222222222222222222222222222222222222222 +1271 2222222222222222222222222222222222222222222222222222222 +1272 2222222222222222222222222222222222222222222222222222222 +1273 2222222222222222222222222222222222222222222222222222222 +1274 2222222222222222222222222222222222222222222222222222222 +1275 2222222222222222222222222222222222222222222222222222222 +1276 2222222222222222222222222222222222222222222222222222222 +1277 2222222222222222222222222222222222222222222222222222222 +1278 2222222222222222222222222222222222222222222222222222222 +1279 2222222222222222222222222222222222222222222222222222222 +1280 2222222222222222222222222222222222222222222222222222222 +1281 2222222222222222222222222222222222222222222222222222222 +1282 2222222222222222222222222222222222222222222222222222222 +1283 2222222222222222222222222222222222222222222222222222222 +1284 2222222222222222222222222222222222222222222222222222222 +1285 2222222222222222222222222222222222222222222222222222222 +1286 2222222222222222222222222222222222222222222222222222222 +1287 2222222222222222222222222222222222222222222222222222222 +1288 2222222222222222222222222222222222222222222222222222222 +1289 2222222222222222222222222222222222222222222222222222222 +1290 2222222222222222222222222222222222222222222222222222222 +1291 2222222222222222222222222222222222222222222222222222222 +1292 2222222222222222222222222222222222222222222222222222222 +1293 2222222222222222222222222222222222222222222222222222222 +1294 2222222222222222222222222222222222222222222222222222222 +1295 2222222222222222222222222222222222222222222222222222222 +1296 2222222222222222222222222222222222222222222222222222222 +1297 2222222222222222222222222222222222222222222222222222222 +1298 2222222222222222222222222222222222222222222222222222222 +1299 2222222222222222222222222222222222222222222222222222222 +1300 2222222222222222222222222222222222222222222222222222222 +1301 2222222222222222222222222222222222222222222222222222222 +1302 2222222222222222222222222222222222222222222222222222222 +1303 2222222222222222222222222222222222222222222222222222222 +1304 2222222222222222222222222222222222222222222222222222222 +1305 2222222222222222222222222222222222222222222222222222222 +1306 2222222222222222222222222222222222222222222222222222222 +1307 2222222222222222222222222222222222222222222222222222222 +1308 2222222222222222222222222222222222222222222222222222222 +1309 2222222222222222222222222222222222222222222222222222222 +1310 2222222222222222222222222222222222222222222222222222222 +1311 2222222222222222222222222222222222222222222222222222222 +1312 2222222222222222222222222222222222222222222222222222222 +1313 2222222222222222222222222222222222222222222222222222222 +1314 2222222222222222222222222222222222222222222222222222222 +1315 2222222222222222222222222222222222222222222222222222222 +1316 2222222222222222222222222222222222222222222222222222222 +1317 2222222222222222222222222222222222222222222222222222222 +1318 2222222222222222222222222222222222222222222222222222222 +1319 2222222222222222222222222222222222222222222222222222222 +1320 2222222222222222222222222222222222222222222222222222222 +1321 2222222222222222222222222222222222222222222222222222222 +1322 2222222222222222222222222222222222222222222222222222222 +1323 2222222222222222222222222222222222222222222222222222222 +1324 2222222222222222222222222222222222222222222222222222222 +1325 2222222222222222222222222222222222222222222222222222222 +1326 2222222222222222222222222222222222222222222222222222222 +1327 2222222222222222222222222222222222222222222222222222222 +1328 2222222222222222222222222222222222222222222222222222222 +1329 2222222222222222222222222222222222222222222222222222222 +1330 2222222222222222222222222222222222222222222222222222222 +1331 2222222222222222222222222222222222222222222222222222222 +1332 2222222222222222222222222222222222222222222222222222222 +1333 2222222222222222222222222222222222222222222222222222222 +1334 2222222222222222222222222222222222222222222222222222222 +1335 2222222222222222222222222222222222222222222222222222222 +1336 2222222222222222222222222222222222222222222222222222222 +1337 2222222222222222222222222222222222222222222222222222222 +1338 2222222222222222222222222222222222222222222222222222222 +1339 2222222222222222222222222222222222222222222222222222222 +1340 2222222222222222222222222222222222222222222222222222222 +1341 2222222222222222222222222222222222222222222222222222222 +1342 2222222222222222222222222222222222222222222222222222222 +1343 2222222222222222222222222222222222222222222222222222222 +1344 2222222222222222222222222222222222222222222222222222222 +1345 2222222222222222222222222222222222222222222222222222222 +1346 2222222222222222222222222222222222222222222222222222222 +1347 2222222222222222222222222222222222222222222222222222222 +1348 2222222222222222222222222222222222222222222222222222222 +1349 2222222222222222222222222222222222222222222222222222222 +1350 2222222222222222222222222222222222222222222222222222222 +1351 2222222222222222222222222222222222222222222222222222222 +1352 2222222222222222222222222222222222222222222222222222222 +1353 2222222222222222222222222222222222222222222222222222222 +1354 2222222222222222222222222222222222222222222222222222222 +1355 2222222222222222222222222222222222222222222222222222222 +1356 2222222222222222222222222222222222222222222222222222222 +1357 2222222222222222222222222222222222222222222222222222222 +1358 2222222222222222222222222222222222222222222222222222222 +1359 2222222222222222222222222222222222222222222222222222222 +1360 2222222222222222222222222222222222222222222222222222222 +1361 2222222222222222222222222222222222222222222222222222222 +1362 2222222222222222222222222222222222222222222222222222222 +1363 2222222222222222222222222222222222222222222222222222222 +1364 2222222222222222222222222222222222222222222222222222222 +1365 2222222222222222222222222222222222222222222222222222222 +1366 2222222222222222222222222222222222222222222222222222222 +1367 2222222222222222222222222222222222222222222222222222222 +1368 2222222222222222222222222222222222222222222222222222222 +1369 2222222222222222222222222222222222222222222222222222222 +1370 2222222222222222222222222222222222222222222222222222222 +1371 2222222222222222222222222222222222222222222222222222222 +1372 2222222222222222222222222222222222222222222222222222222 +1373 2222222222222222222222222222222222222222222222222222222 +1374 2222222222222222222222222222222222222222222222222222222 +1375 2222222222222222222222222222222222222222222222222222222 +1376 2222222222222222222222222222222222222222222222222222222 +1377 2222222222222222222222222222222222222222222222222222222 +1378 2222222222222222222222222222222222222222222222222222222 +1379 2222222222222222222222222222222222222222222222222222222 +1380 2222222222222222222222222222222222222222222222222222222 +1381 2222222222222222222222222222222222222222222222222222222 +1382 2222222222222222222222222222222222222222222222222222222 +1383 2222222222222222222222222222222222222222222222222222222 +1384 2222222222222222222222222222222222222222222222222222222 +1385 2222222222222222222222222222222222222222222222222222222 +1386 2222222222222222222222222222222222222222222222222222222 +1387 2222222222222222222222222222222222222222222222222222222 +1388 2222222222222222222222222222222222222222222222222222222 +1389 2222222222222222222222222222222222222222222222222222222 +1390 2222222222222222222222222222222222222222222222222222222 +1391 2222222222222222222222222222222222222222222222222222222 +1392 2222222222222222222222222222222222222222222222222222222 +1393 2222222222222222222222222222222222222222222222222222222 +1394 2222222222222222222222222222222222222222222222222222222 +1395 2222222222222222222222222222222222222222222222222222222 +1396 2222222222222222222222222222222222222222222222222222222 +1397 2222222222222222222222222222222222222222222222222222222 +1398 2222222222222222222222222222222222222222222222222222222 +1399 2222222222222222222222222222222222222222222222222222222 +1400 2222222222222222222222222222222222222222222222222222222 +1401 2222222222222222222222222222222222222222222222222222222 +1402 2222222222222222222222222222222222222222222222222222222 +1403 2222222222222222222222222222222222222222222222222222222 +1404 2222222222222222222222222222222222222222222222222222222 +1405 2222222222222222222222222222222222222222222222222222222 +1406 2222222222222222222222222222222222222222222222222222222 +1407 2222222222222222222222222222222222222222222222222222222 +1408 2222222222222222222222222222222222222222222222222222222 +1409 2222222222222222222222222222222222222222222222222222222 +1410 2222222222222222222222222222222222222222222222222222222 +1411 2222222222222222222222222222222222222222222222222222222 +1412 2222222222222222222222222222222222222222222222222222222 +1413 2222222222222222222222222222222222222222222222222222222 +1414 2222222222222222222222222222222222222222222222222222222 +1415 2222222222222222222222222222222222222222222222222222222 +1416 2222222222222222222222222222222222222222222222222222222 +1417 2222222222222222222222222222222222222222222222222222222 +1418 2222222222222222222222222222222222222222222222222222222 +1419 2222222222222222222222222222222222222222222222222222222 +1420 2222222222222222222222222222222222222222222222222222222 +1421 2222222222222222222222222222222222222222222222222222222 +1422 2222222222222222222222222222222222222222222222222222222 +1423 2222222222222222222222222222222222222222222222222222222 +1424 2222222222222222222222222222222222222222222222222222222 +1425 2222222222222222222222222222222222222222222222222222222 +1426 2222222222222222222222222222222222222222222222222222222 +1427 2222222222222222222222222222222222222222222222222222222 +1428 2222222222222222222222222222222222222222222222222222222 +1429 2222222222222222222222222222222222222222222222222222222 +1430 2222222222222222222222222222222222222222222222222222222 +1431 2222222222222222222222222222222222222222222222222222222 +1432 2222222222222222222222222222222222222222222222222222222 +1433 2222222222222222222222222222222222222222222222222222222 +1434 2222222222222222222222222222222222222222222222222222222 +1435 2222222222222222222222222222222222222222222222222222222 +1436 2222222222222222222222222222222222222222222222222222222 +1437 2222222222222222222222222222222222222222222222222222222 +1438 2222222222222222222222222222222222222222222222222222222 +1439 2222222222222222222222222222222222222222222222222222222 +1440 2222222222222222222222222222222222222222222222222222222 +1441 2222222222222222222222222222222222222222222222222222222 +1442 2222222222222222222222222222222222222222222222222222222 +1443 2222222222222222222222222222222222222222222222222222222 +1444 2222222222222222222222222222222222222222222222222222222 +1445 2222222222222222222222222222222222222222222222222222222 +1446 2222222222222222222222222222222222222222222222222222222 +1447 2222222222222222222222222222222222222222222222222222222 +1448 2222222222222222222222222222222222222222222222222222222 +1449 2222222222222222222222222222222222222222222222222222222 +1450 2222222222222222222222222222222222222222222222222222222 +1451 2222222222222222222222222222222222222222222222222222222 +1452 2222222222222222222222222222222222222222222222222222222 +1453 2222222222222222222222222222222222222222222222222222222 +1454 2222222222222222222222222222222222222222222222222222222 +1455 2222222222222222222222222222222222222222222222222222222 +1456 2222222222222222222222222222222222222222222222222222222 +1457 2222222222222222222222222222222222222222222222222222222 +1458 2222222222222222222222222222222222222222222222222222222 +1459 2222222222222222222222222222222222222222222222222222222 +1460 2222222222222222222222222222222222222222222222222222222 +1461 2222222222222222222222222222222222222222222222222222222 +1462 2222222222222222222222222222222222222222222222222222222 +1463 2222222222222222222222222222222222222222222222222222222 +1464 2222222222222222222222222222222222222222222222222222222 +1465 2222222222222222222222222222222222222222222222222222222 +1466 2222222222222222222222222222222222222222222222222222222 +1467 2222222222222222222222222222222222222222222222222222222 +1468 2222222222222222222222222222222222222222222222222222222 +1469 2222222222222222222222222222222222222222222222222222222 +1470 2222222222222222222222222222222222222222222222222222222 +1471 2222222222222222222222222222222222222222222222222222222 +1472 2222222222222222222222222222222222222222222222222222222 +1473 2222222222222222222222222222222222222222222222222222222 +1474 2222222222222222222222222222222222222222222222222222222 +1475 2222222222222222222222222222222222222222222222222222222 +1476 2222222222222222222222222222222222222222222222222222222 +1477 2222222222222222222222222222222222222222222222222222222 +1478 2222222222222222222222222222222222222222222222222222222 +1479 2222222222222222222222222222222222222222222222222222222 +1480 2222222222222222222222222222222222222222222222222222222 +1481 2222222222222222222222222222222222222222222222222222222 +1482 2222222222222222222222222222222222222222222222222222222 +1483 2222222222222222222222222222222222222222222222222222222 +1484 2222222222222222222222222222222222222222222222222222222 +1485 2222222222222222222222222222222222222222222222222222222 +1486 2222222222222222222222222222222222222222222222222222222 +1487 2222222222222222222222222222222222222222222222222222222 +1488 2222222222222222222222222222222222222222222222222222222 +1489 2222222222222222222222222222222222222222222222222222222 +1490 2222222222222222222222222222222222222222222222222222222 +1491 2222222222222222222222222222222222222222222222222222222 +1492 2222222222222222222222222222222222222222222222222222222 +1493 2222222222222222222222222222222222222222222222222222222 +1494 2222222222222222222222222222222222222222222222222222222 +1495 2222222222222222222222222222222222222222222222222222222 +1496 2222222222222222222222222222222222222222222222222222222 +1497 2222222222222222222222222222222222222222222222222222222 +1498 2222222222222222222222222222222222222222222222222222222 +1499 2222222222222222222222222222222222222222222222222222222 +1500 2222222222222222222222222222222222222222222222222222222 +1501 2222222222222222222222222222222222222222222222222222222 +1502 2222222222222222222222222222222222222222222222222222222 +1503 2222222222222222222222222222222222222222222222222222222 +1504 2222222222222222222222222222222222222222222222222222222 +1505 2222222222222222222222222222222222222222222222222222222 +1506 2222222222222222222222222222222222222222222222222222222 +1507 2222222222222222222222222222222222222222222222222222222 +1508 2222222222222222222222222222222222222222222222222222222 +1509 2222222222222222222222222222222222222222222222222222222 +1510 2222222222222222222222222222222222222222222222222222222 +1511 2222222222222222222222222222222222222222222222222222222 +1512 2222222222222222222222222222222222222222222222222222222 +1513 2222222222222222222222222222222222222222222222222222222 +1514 2222222222222222222222222222222222222222222222222222222 +1515 2222222222222222222222222222222222222222222222222222222 +1516 2222222222222222222222222222222222222222222222222222222 +1517 2222222222222222222222222222222222222222222222222222222 +1518 2222222222222222222222222222222222222222222222222222222 +1519 2222222222222222222222222222222222222222222222222222222 +1520 2222222222222222222222222222222222222222222222222222222 +1521 2222222222222222222222222222222222222222222222222222222 +1522 2222222222222222222222222222222222222222222222222222222 +1523 2222222222222222222222222222222222222222222222222222222 +1524 2222222222222222222222222222222222222222222222222222222 +1525 2222222222222222222222222222222222222222222222222222222 +1526 2222222222222222222222222222222222222222222222222222222 +1527 2222222222222222222222222222222222222222222222222222222 +1528 2222222222222222222222222222222222222222222222222222222 +1529 2222222222222222222222222222222222222222222222222222222 +1530 2222222222222222222222222222222222222222222222222222222 +1531 2222222222222222222222222222222222222222222222222222222 +1532 2222222222222222222222222222222222222222222222222222222 +1533 2222222222222222222222222222222222222222222222222222222 +1534 2222222222222222222222222222222222222222222222222222222 +1535 2222222222222222222222222222222222222222222222222222222 +1536 2222222222222222222222222222222222222222222222222222222 +1537 2222222222222222222222222222222222222222222222222222222 +1538 2222222222222222222222222222222222222222222222222222222 +1539 2222222222222222222222222222222222222222222222222222222 +1540 2222222222222222222222222222222222222222222222222222222 +1541 2222222222222222222222222222222222222222222222222222222 +1542 2222222222222222222222222222222222222222222222222222222 +1543 2222222222222222222222222222222222222222222222222222222 +1544 2222222222222222222222222222222222222222222222222222222 +1545 2222222222222222222222222222222222222222222222222222222 +1546 2222222222222222222222222222222222222222222222222222222 +1547 2222222222222222222222222222222222222222222222222222222 +1548 2222222222222222222222222222222222222222222222222222222 +1549 2222222222222222222222222222222222222222222222222222222 +1550 2222222222222222222222222222222222222222222222222222222 +1551 2222222222222222222222222222222222222222222222222222222 +1552 2222222222222222222222222222222222222222222222222222222 +1553 2222222222222222222222222222222222222222222222222222222 +1554 2222222222222222222222222222222222222222222222222222222 +1555 2222222222222222222222222222222222222222222222222222222 +1556 2222222222222222222222222222222222222222222222222222222 +1557 2222222222222222222222222222222222222222222222222222222 +1558 2222222222222222222222222222222222222222222222222222222 +1559 2222222222222222222222222222222222222222222222222222222 +1560 2222222222222222222222222222222222222222222222222222222 +1561 2222222222222222222222222222222222222222222222222222222 +1562 2222222222222222222222222222222222222222222222222222222 +1563 2222222222222222222222222222222222222222222222222222222 +1564 2222222222222222222222222222222222222222222222222222222 +1565 2222222222222222222222222222222222222222222222222222222 +1566 2222222222222222222222222222222222222222222222222222222 +1567 2222222222222222222222222222222222222222222222222222222 +1568 2222222222222222222222222222222222222222222222222222222 +1569 2222222222222222222222222222222222222222222222222222222 +1570 2222222222222222222222222222222222222222222222222222222 +1571 2222222222222222222222222222222222222222222222222222222 +1572 2222222222222222222222222222222222222222222222222222222 +1573 2222222222222222222222222222222222222222222222222222222 +1574 2222222222222222222222222222222222222222222222222222222 +1575 2222222222222222222222222222222222222222222222222222222 +1576 2222222222222222222222222222222222222222222222222222222 +1577 2222222222222222222222222222222222222222222222222222222 +1578 2222222222222222222222222222222222222222222222222222222 +1579 2222222222222222222222222222222222222222222222222222222 +1580 2222222222222222222222222222222222222222222222222222222 +1581 2222222222222222222222222222222222222222222222222222222 +1582 2222222222222222222222222222222222222222222222222222222 +1583 2222222222222222222222222222222222222222222222222222222 +1584 2222222222222222222222222222222222222222222222222222222 +1585 2222222222222222222222222222222222222222222222222222222 +1586 2222222222222222222222222222222222222222222222222222222 +1587 2222222222222222222222222222222222222222222222222222222 +1588 2222222222222222222222222222222222222222222222222222222 +1589 2222222222222222222222222222222222222222222222222222222 +1590 2222222222222222222222222222222222222222222222222222222 +1591 2222222222222222222222222222222222222222222222222222222 +1592 2222222222222222222222222222222222222222222222222222222 +1593 2222222222222222222222222222222222222222222222222222222 +1594 2222222222222222222222222222222222222222222222222222222 +1595 2222222222222222222222222222222222222222222222222222222 +1596 2222222222222222222222222222222222222222222222222222222 +1597 2222222222222222222222222222222222222222222222222222222 +1598 2222222222222222222222222222222222222222222222222222222 +1599 2222222222222222222222222222222222222222222222222222222 +1600 2222222222222222222222222222222222222222222222222222222 +1601 2222222222222222222222222222222222222222222222222222222 +1602 2222222222222222222222222222222222222222222222222222222 +1603 2222222222222222222222222222222222222222222222222222222 +1604 2222222222222222222222222222222222222222222222222222222 +1605 2222222222222222222222222222222222222222222222222222222 +1606 2222222222222222222222222222222222222222222222222222222 +1607 2222222222222222222222222222222222222222222222222222222 +1608 2222222222222222222222222222222222222222222222222222222 +1609 2222222222222222222222222222222222222222222222222222222 +1610 2222222222222222222222222222222222222222222222222222222 +1611 2222222222222222222222222222222222222222222222222222222 +1612 2222222222222222222222222222222222222222222222222222222 +1613 2222222222222222222222222222222222222222222222222222222 +1614 2222222222222222222222222222222222222222222222222222222 +1615 2222222222222222222222222222222222222222222222222222222 +1616 2222222222222222222222222222222222222222222222222222222 +1617 2222222222222222222222222222222222222222222222222222222 +1618 2222222222222222222222222222222222222222222222222222222 +1619 2222222222222222222222222222222222222222222222222222222 +1620 2222222222222222222222222222222222222222222222222222222 +1621 2222222222222222222222222222222222222222222222222222222 +1622 2222222222222222222222222222222222222222222222222222222 +1623 2222222222222222222222222222222222222222222222222222222 +1624 2222222222222222222222222222222222222222222222222222222 +1625 2222222222222222222222222222222222222222222222222222222 +1626 2222222222222222222222222222222222222222222222222222222 +1627 2222222222222222222222222222222222222222222222222222222 +1628 2222222222222222222222222222222222222222222222222222222 +1629 2222222222222222222222222222222222222222222222222222222 +1630 2222222222222222222222222222222222222222222222222222222 +1631 2222222222222222222222222222222222222222222222222222222 +1632 2222222222222222222222222222222222222222222222222222222 +1633 2222222222222222222222222222222222222222222222222222222 +1634 2222222222222222222222222222222222222222222222222222222 +1635 2222222222222222222222222222222222222222222222222222222 +1636 2222222222222222222222222222222222222222222222222222222 +1637 2222222222222222222222222222222222222222222222222222222 +1638 2222222222222222222222222222222222222222222222222222222 +1639 2222222222222222222222222222222222222222222222222222222 +1640 2222222222222222222222222222222222222222222222222222222 +1641 2222222222222222222222222222222222222222222222222222222 +1642 2222222222222222222222222222222222222222222222222222222 +1643 2222222222222222222222222222222222222222222222222222222 +1644 2222222222222222222222222222222222222222222222222222222 +1645 2222222222222222222222222222222222222222222222222222222 +1646 2222222222222222222222222222222222222222222222222222222 +1647 2222222222222222222222222222222222222222222222222222222 +1648 2222222222222222222222222222222222222222222222222222222 +1649 2222222222222222222222222222222222222222222222222222222 +1650 2222222222222222222222222222222222222222222222222222222 +1651 2222222222222222222222222222222222222222222222222222222 +1652 2222222222222222222222222222222222222222222222222222222 +1653 2222222222222222222222222222222222222222222222222222222 +1654 2222222222222222222222222222222222222222222222222222222 +1655 2222222222222222222222222222222222222222222222222222222 +1656 2222222222222222222222222222222222222222222222222222222 +1657 2222222222222222222222222222222222222222222222222222222 +1658 2222222222222222222222222222222222222222222222222222222 +1659 2222222222222222222222222222222222222222222222222222222 +1660 2222222222222222222222222222222222222222222222222222222 +1661 2222222222222222222222222222222222222222222222222222222 +1662 2222222222222222222222222222222222222222222222222222222 +1663 2222222222222222222222222222222222222222222222222222222 +1664 2222222222222222222222222222222222222222222222222222222 +1665 2222222222222222222222222222222222222222222222222222222 +1666 2222222222222222222222222222222222222222222222222222222 +1667 2222222222222222222222222222222222222222222222222222222 +1668 2222222222222222222222222222222222222222222222222222222 +1669 2222222222222222222222222222222222222222222222222222222 +1670 2222222222222222222222222222222222222222222222222222222 +1671 2222222222222222222222222222222222222222222222222222222 +1672 2222222222222222222222222222222222222222222222222222222 +1673 2222222222222222222222222222222222222222222222222222222 +1674 2222222222222222222222222222222222222222222222222222222 +1675 2222222222222222222222222222222222222222222222222222222 +1676 2222222222222222222222222222222222222222222222222222222 +1677 2222222222222222222222222222222222222222222222222222222 +1678 2222222222222222222222222222222222222222222222222222222 +1679 2222222222222222222222222222222222222222222222222222222 +1680 2222222222222222222222222222222222222222222222222222222 +1681 2222222222222222222222222222222222222222222222222222222 +1682 2222222222222222222222222222222222222222222222222222222 +1683 2222222222222222222222222222222222222222222222222222222 +1684 2222222222222222222222222222222222222222222222222222222 +1685 2222222222222222222222222222222222222222222222222222222 +1686 2222222222222222222222222222222222222222222222222222222 +1687 2222222222222222222222222222222222222222222222222222222 +1688 2222222222222222222222222222222222222222222222222222222 +1689 2222222222222222222222222222222222222222222222222222222 +1690 2222222222222222222222222222222222222222222222222222222 +1691 2222222222222222222222222222222222222222222222222222222 +1692 2222222222222222222222222222222222222222222222222222222 +1693 2222222222222222222222222222222222222222222222222222222 +1694 2222222222222222222222222222222222222222222222222222222 +1695 2222222222222222222222222222222222222222222222222222222 +1696 2222222222222222222222222222222222222222222222222222222 +1697 2222222222222222222222222222222222222222222222222222222 +1698 2222222222222222222222222222222222222222222222222222222 +1699 2222222222222222222222222222222222222222222222222222222 +1700 2222222222222222222222222222222222222222222222222222222 +1701 2222222222222222222222222222222222222222222222222222222 +1702 2222222222222222222222222222222222222222222222222222222 +1703 2222222222222222222222222222222222222222222222222222222 +1704 2222222222222222222222222222222222222222222222222222222 +1705 2222222222222222222222222222222222222222222222222222222 +1706 2222222222222222222222222222222222222222222222222222222 +1707 2222222222222222222222222222222222222222222222222222222 +1708 2222222222222222222222222222222222222222222222222222222 +1709 2222222222222222222222222222222222222222222222222222222 +1710 2222222222222222222222222222222222222222222222222222222 +1711 2222222222222222222222222222222222222222222222222222222 +1712 2222222222222222222222222222222222222222222222222222222 +1713 2222222222222222222222222222222222222222222222222222222 +1714 2222222222222222222222222222222222222222222222222222222 +1715 2222222222222222222222222222222222222222222222222222222 +1716 2222222222222222222222222222222222222222222222222222222 +1717 2222222222222222222222222222222222222222222222222222222 +1718 2222222222222222222222222222222222222222222222222222222 +1719 2222222222222222222222222222222222222222222222222222222 +1720 2222222222222222222222222222222222222222222222222222222 +1721 2222222222222222222222222222222222222222222222222222222 +1722 2222222222222222222222222222222222222222222222222222222 +1723 2222222222222222222222222222222222222222222222222222222 +1724 2222222222222222222222222222222222222222222222222222222 +1725 2222222222222222222222222222222222222222222222222222222 +1726 2222222222222222222222222222222222222222222222222222222 +1727 2222222222222222222222222222222222222222222222222222222 +1728 2222222222222222222222222222222222222222222222222222222 +1729 2222222222222222222222222222222222222222222222222222222 +1730 2222222222222222222222222222222222222222222222222222222 +1731 2222222222222222222222222222222222222222222222222222222 +1732 2222222222222222222222222222222222222222222222222222222 +1733 2222222222222222222222222222222222222222222222222222222 +1734 2222222222222222222222222222222222222222222222222222222 +1735 2222222222222222222222222222222222222222222222222222222 +1736 2222222222222222222222222222222222222222222222222222222 +1737 2222222222222222222222222222222222222222222222222222222 +1738 2222222222222222222222222222222222222222222222222222222 +1739 2222222222222222222222222222222222222222222222222222222 +1740 2222222222222222222222222222222222222222222222222222222 +1741 2222222222222222222222222222222222222222222222222222222 +1742 2222222222222222222222222222222222222222222222222222222 +1743 2222222222222222222222222222222222222222222222222222222 +1744 2222222222222222222222222222222222222222222222222222222 +1745 2222222222222222222222222222222222222222222222222222222 +1746 2222222222222222222222222222222222222222222222222222222 +1747 2222222222222222222222222222222222222222222222222222222 +1748 2222222222222222222222222222222222222222222222222222222 +1749 2222222222222222222222222222222222222222222222222222222 +1750 2222222222222222222222222222222222222222222222222222222 +1751 2222222222222222222222222222222222222222222222222222222 +1752 2222222222222222222222222222222222222222222222222222222 +1753 2222222222222222222222222222222222222222222222222222222 +1754 2222222222222222222222222222222222222222222222222222222 +1755 2222222222222222222222222222222222222222222222222222222 +1756 2222222222222222222222222222222222222222222222222222222 +1757 2222222222222222222222222222222222222222222222222222222 +1758 2222222222222222222222222222222222222222222222222222222 +1759 2222222222222222222222222222222222222222222222222222222 +1760 2222222222222222222222222222222222222222222222222222222 +1761 2222222222222222222222222222222222222222222222222222222 +1762 2222222222222222222222222222222222222222222222222222222 +1763 2222222222222222222222222222222222222222222222222222222 +1764 2222222222222222222222222222222222222222222222222222222 +1765 2222222222222222222222222222222222222222222222222222222 +1766 2222222222222222222222222222222222222222222222222222222 +1767 2222222222222222222222222222222222222222222222222222222 +1768 2222222222222222222222222222222222222222222222222222222 +1769 2222222222222222222222222222222222222222222222222222222 +1770 2222222222222222222222222222222222222222222222222222222 +1771 2222222222222222222222222222222222222222222222222222222 +1772 2222222222222222222222222222222222222222222222222222222 +1773 2222222222222222222222222222222222222222222222222222222 +1774 2222222222222222222222222222222222222222222222222222222 +1775 2222222222222222222222222222222222222222222222222222222 +1776 2222222222222222222222222222222222222222222222222222222 +1777 2222222222222222222222222222222222222222222222222222222 +1778 2222222222222222222222222222222222222222222222222222222 +1779 2222222222222222222222222222222222222222222222222222222 +1780 2222222222222222222222222222222222222222222222222222222 +1781 2222222222222222222222222222222222222222222222222222222 +1782 2222222222222222222222222222222222222222222222222222222 +1783 2222222222222222222222222222222222222222222222222222222 +1784 2222222222222222222222222222222222222222222222222222222 +1785 2222222222222222222222222222222222222222222222222222222 +1786 2222222222222222222222222222222222222222222222222222222 +1787 2222222222222222222222222222222222222222222222222222222 +1788 2222222222222222222222222222222222222222222222222222222 +1789 2222222222222222222222222222222222222222222222222222222 +1790 2222222222222222222222222222222222222222222222222222222 +1791 2222222222222222222222222222222222222222222222222222222 +1792 2222222222222222222222222222222222222222222222222222222 +1793 2222222222222222222222222222222222222222222222222222222 +1794 2222222222222222222222222222222222222222222222222222222 +1795 2222222222222222222222222222222222222222222222222222222 +1796 2222222222222222222222222222222222222222222222222222222 +1797 2222222222222222222222222222222222222222222222222222222 +1798 2222222222222222222222222222222222222222222222222222222 +1799 2222222222222222222222222222222222222222222222222222222 +1800 2222222222222222222222222222222222222222222222222222222 +1801 2222222222222222222222222222222222222222222222222222222 +1802 2222222222222222222222222222222222222222222222222222222 +1803 2222222222222222222222222222222222222222222222222222222 +1804 2222222222222222222222222222222222222222222222222222222 +1805 2222222222222222222222222222222222222222222222222222222 +1806 2222222222222222222222222222222222222222222222222222222 +1807 2222222222222222222222222222222222222222222222222222222 +1808 2222222222222222222222222222222222222222222222222222222 +1809 2222222222222222222222222222222222222222222222222222222 +1810 2222222222222222222222222222222222222222222222222222222 +1811 2222222222222222222222222222222222222222222222222222222 +1812 2222222222222222222222222222222222222222222222222222222 +1813 2222222222222222222222222222222222222222222222222222222 +1814 2222222222222222222222222222222222222222222222222222222 +1815 2222222222222222222222222222222222222222222222222222222 +1816 2222222222222222222222222222222222222222222222222222222 +1817 2222222222222222222222222222222222222222222222222222222 +1818 2222222222222222222222222222222222222222222222222222222 +1819 2222222222222222222222222222222222222222222222222222222 +1820 2222222222222222222222222222222222222222222222222222222 +1821 2222222222222222222222222222222222222222222222222222222 +1822 2222222222222222222222222222222222222222222222222222222 +1823 2222222222222222222222222222222222222222222222222222222 +1824 2222222222222222222222222222222222222222222222222222222 +1825 2222222222222222222222222222222222222222222222222222222 +1826 2222222222222222222222222222222222222222222222222222222 +1827 2222222222222222222222222222222222222222222222222222222 +1828 2222222222222222222222222222222222222222222222222222222 +1829 2222222222222222222222222222222222222222222222222222222 +1830 2222222222222222222222222222222222222222222222222222222 +1831 2222222222222222222222222222222222222222222222222222222 +1832 2222222222222222222222222222222222222222222222222222222 +1833 2222222222222222222222222222222222222222222222222222222 +1834 2222222222222222222222222222222222222222222222222222222 +1835 2222222222222222222222222222222222222222222222222222222 +1836 2222222222222222222222222222222222222222222222222222222 +1837 2222222222222222222222222222222222222222222222222222222 +1838 2222222222222222222222222222222222222222222222222222222 +1839 2222222222222222222222222222222222222222222222222222222 +1840 2222222222222222222222222222222222222222222222222222222 +1841 2222222222222222222222222222222222222222222222222222222 +1842 2222222222222222222222222222222222222222222222222222222 +1843 2222222222222222222222222222222222222222222222222222222 +1844 2222222222222222222222222222222222222222222222222222222 +1845 2222222222222222222222222222222222222222222222222222222 +1846 2222222222222222222222222222222222222222222222222222222 +1847 2222222222222222222222222222222222222222222222222222222 +1848 2222222222222222222222222222222222222222222222222222222 +1849 2222222222222222222222222222222222222222222222222222222 +1850 2222222222222222222222222222222222222222222222222222222 +1851 2222222222222222222222222222222222222222222222222222222 +1852 2222222222222222222222222222222222222222222222222222222 +1853 2222222222222222222222222222222222222222222222222222222 +1854 2222222222222222222222222222222222222222222222222222222 +1855 2222222222222222222222222222222222222222222222222222222 +1856 2222222222222222222222222222222222222222222222222222222 +1857 2222222222222222222222222222222222222222222222222222222 +1858 2222222222222222222222222222222222222222222222222222222 +1859 2222222222222222222222222222222222222222222222222222222 +1860 2222222222222222222222222222222222222222222222222222222 +1861 2222222222222222222222222222222222222222222222222222222 +1862 2222222222222222222222222222222222222222222222222222222 +1863 2222222222222222222222222222222222222222222222222222222 +1864 2222222222222222222222222222222222222222222222222222222 +1865 2222222222222222222222222222222222222222222222222222222 +1866 2222222222222222222222222222222222222222222222222222222 +1867 2222222222222222222222222222222222222222222222222222222 +1868 2222222222222222222222222222222222222222222222222222222 +1869 2222222222222222222222222222222222222222222222222222222 +1870 2222222222222222222222222222222222222222222222222222222 +1871 2222222222222222222222222222222222222222222222222222222 +1872 2222222222222222222222222222222222222222222222222222222 +1873 2222222222222222222222222222222222222222222222222222222 +1874 2222222222222222222222222222222222222222222222222222222 +1875 2222222222222222222222222222222222222222222222222222222 +1876 2222222222222222222222222222222222222222222222222222222 +1877 2222222222222222222222222222222222222222222222222222222 +1878 2222222222222222222222222222222222222222222222222222222 +1879 2222222222222222222222222222222222222222222222222222222 +1880 2222222222222222222222222222222222222222222222222222222 +1881 2222222222222222222222222222222222222222222222222222222 +1882 2222222222222222222222222222222222222222222222222222222 +1883 2222222222222222222222222222222222222222222222222222222 +1884 2222222222222222222222222222222222222222222222222222222 +1885 2222222222222222222222222222222222222222222222222222222 +1886 2222222222222222222222222222222222222222222222222222222 +1887 2222222222222222222222222222222222222222222222222222222 +1888 2222222222222222222222222222222222222222222222222222222 +1889 2222222222222222222222222222222222222222222222222222222 +1890 2222222222222222222222222222222222222222222222222222222 +1891 2222222222222222222222222222222222222222222222222222222 +1892 2222222222222222222222222222222222222222222222222222222 +1893 2222222222222222222222222222222222222222222222222222222 +1894 2222222222222222222222222222222222222222222222222222222 +1895 2222222222222222222222222222222222222222222222222222222 +1896 2222222222222222222222222222222222222222222222222222222 +1897 2222222222222222222222222222222222222222222222222222222 +1898 2222222222222222222222222222222222222222222222222222222 +1899 2222222222222222222222222222222222222222222222222222222 +1900 2222222222222222222222222222222222222222222222222222222 +1901 2222222222222222222222222222222222222222222222222222222 +1902 2222222222222222222222222222222222222222222222222222222 +1903 2222222222222222222222222222222222222222222222222222222 +1904 2222222222222222222222222222222222222222222222222222222 +1905 2222222222222222222222222222222222222222222222222222222 +1906 2222222222222222222222222222222222222222222222222222222 +1907 2222222222222222222222222222222222222222222222222222222 +1908 2222222222222222222222222222222222222222222222222222222 +1909 2222222222222222222222222222222222222222222222222222222 +1910 2222222222222222222222222222222222222222222222222222222 +1911 2222222222222222222222222222222222222222222222222222222 +1912 2222222222222222222222222222222222222222222222222222222 +1913 2222222222222222222222222222222222222222222222222222222 +1914 2222222222222222222222222222222222222222222222222222222 +1915 2222222222222222222222222222222222222222222222222222222 +1916 2222222222222222222222222222222222222222222222222222222 +1917 2222222222222222222222222222222222222222222222222222222 +1918 2222222222222222222222222222222222222222222222222222222 +1919 2222222222222222222222222222222222222222222222222222222 +1920 2222222222222222222222222222222222222222222222222222222 +1921 2222222222222222222222222222222222222222222222222222222 +1922 2222222222222222222222222222222222222222222222222222222 +1923 2222222222222222222222222222222222222222222222222222222 +1924 2222222222222222222222222222222222222222222222222222222 +1925 2222222222222222222222222222222222222222222222222222222 +1926 2222222222222222222222222222222222222222222222222222222 +1927 2222222222222222222222222222222222222222222222222222222 +1928 2222222222222222222222222222222222222222222222222222222 +1929 2222222222222222222222222222222222222222222222222222222 +1930 2222222222222222222222222222222222222222222222222222222 +1931 2222222222222222222222222222222222222222222222222222222 +1932 2222222222222222222222222222222222222222222222222222222 +1933 2222222222222222222222222222222222222222222222222222222 +1934 2222222222222222222222222222222222222222222222222222222 +1935 2222222222222222222222222222222222222222222222222222222 +1936 2222222222222222222222222222222222222222222222222222222 +1937 2222222222222222222222222222222222222222222222222222222 +1938 2222222222222222222222222222222222222222222222222222222 +1939 2222222222222222222222222222222222222222222222222222222 +1940 2222222222222222222222222222222222222222222222222222222 +1941 2222222222222222222222222222222222222222222222222222222 +1942 2222222222222222222222222222222222222222222222222222222 +1943 2222222222222222222222222222222222222222222222222222222 +1944 2222222222222222222222222222222222222222222222222222222 +1945 2222222222222222222222222222222222222222222222222222222 +1946 2222222222222222222222222222222222222222222222222222222 +1947 2222222222222222222222222222222222222222222222222222222 +1948 2222222222222222222222222222222222222222222222222222222 +1949 2222222222222222222222222222222222222222222222222222222 +1950 2222222222222222222222222222222222222222222222222222222 +1951 2222222222222222222222222222222222222222222222222222222 +1952 2222222222222222222222222222222222222222222222222222222 +1953 2222222222222222222222222222222222222222222222222222222 +1954 2222222222222222222222222222222222222222222222222222222 +1955 2222222222222222222222222222222222222222222222222222222 +1956 2222222222222222222222222222222222222222222222222222222 +1957 2222222222222222222222222222222222222222222222222222222 +1958 2222222222222222222222222222222222222222222222222222222 +1959 2222222222222222222222222222222222222222222222222222222 +1960 2222222222222222222222222222222222222222222222222222222 +1961 2222222222222222222222222222222222222222222222222222222 +1962 2222222222222222222222222222222222222222222222222222222 +1963 2222222222222222222222222222222222222222222222222222222 +1964 2222222222222222222222222222222222222222222222222222222 +1965 2222222222222222222222222222222222222222222222222222222 +1966 2222222222222222222222222222222222222222222222222222222 +1967 2222222222222222222222222222222222222222222222222222222 +1968 2222222222222222222222222222222222222222222222222222222 +1969 2222222222222222222222222222222222222222222222222222222 +1970 2222222222222222222222222222222222222222222222222222222 +1971 2222222222222222222222222222222222222222222222222222222 +1972 2222222222222222222222222222222222222222222222222222222 +1973 2222222222222222222222222222222222222222222222222222222 +1974 2222222222222222222222222222222222222222222222222222222 +1975 2222222222222222222222222222222222222222222222222222222 +1976 2222222222222222222222222222222222222222222222222222222 +1977 2222222222222222222222222222222222222222222222222222222 +1978 2222222222222222222222222222222222222222222222222222222 +1979 2222222222222222222222222222222222222222222222222222222 +1980 2222222222222222222222222222222222222222222222222222222 +1981 2222222222222222222222222222222222222222222222222222222 +1982 2222222222222222222222222222222222222222222222222222222 +1983 2222222222222222222222222222222222222222222222222222222 +1984 2222222222222222222222222222222222222222222222222222222 +1985 2222222222222222222222222222222222222222222222222222222 +1986 2222222222222222222222222222222222222222222222222222222 +1987 2222222222222222222222222222222222222222222222222222222 +1988 2222222222222222222222222222222222222222222222222222222 +1989 2222222222222222222222222222222222222222222222222222222 +1990 2222222222222222222222222222222222222222222222222222222 +1991 2222222222222222222222222222222222222222222222222222222 +1992 2222222222222222222222222222222222222222222222222222222 +1993 2222222222222222222222222222222222222222222222222222222 +1994 2222222222222222222222222222222222222222222222222222222 +1995 2222222222222222222222222222222222222222222222222222222 +1996 2222222222222222222222222222222222222222222222222222222 +1997 2222222222222222222222222222222222222222222222222222222 +1998 2222222222222222222222222222222222222222222222222222222 +1999 2222222222222222222222222222222222222222222222222222222 +2000 2222222222222222222222222222222222222222222222222222222 +2001 2222222222222222222222222222222222222222222222222222222 +2002 2222222222222222222222222222222222222222222222222222222 +2003 2222222222222222222222222222222222222222222222222222222 +2004 2222222222222222222222222222222222222222222222222222222 +2005 2222222222222222222222222222222222222222222222222222222 +2006 2222222222222222222222222222222222222222222222222222222 +2007 2222222222222222222222222222222222222222222222222222222 +2008 2222222222222222222222222222222222222222222222222222222 +2009 2222222222222222222222222222222222222222222222222222222 +2010 2222222222222222222222222222222222222222222222222222222 +2011 2222222222222222222222222222222222222222222222222222222 +2012 2222222222222222222222222222222222222222222222222222222 +2013 2222222222222222222222222222222222222222222222222222222 +2014 2222222222222222222222222222222222222222222222222222222 +2015 2222222222222222222222222222222222222222222222222222222 +2016 2222222222222222222222222222222222222222222222222222222 +2017 2222222222222222222222222222222222222222222222222222222 +2018 2222222222222222222222222222222222222222222222222222222 +2019 2222222222222222222222222222222222222222222222222222222 +2020 2222222222222222222222222222222222222222222222222222222 +2021 2222222222222222222222222222222222222222222222222222222 +2022 2222222222222222222222222222222222222222222222222222222 +2023 2222222222222222222222222222222222222222222222222222222 +2024 2222222222222222222222222222222222222222222222222222222 +2025 2222222222222222222222222222222222222222222222222222222 +2026 2222222222222222222222222222222222222222222222222222222 +2027 2222222222222222222222222222222222222222222222222222222 +2028 2222222222222222222222222222222222222222222222222222222 +2029 2222222222222222222222222222222222222222222222222222222 +2030 2222222222222222222222222222222222222222222222222222222 +2031 2222222222222222222222222222222222222222222222222222222 +2032 2222222222222222222222222222222222222222222222222222222 +2033 2222222222222222222222222222222222222222222222222222222 +2034 2222222222222222222222222222222222222222222222222222222 +2035 2222222222222222222222222222222222222222222222222222222 +2036 2222222222222222222222222222222222222222222222222222222 +2037 2222222222222222222222222222222222222222222222222222222 +2038 2222222222222222222222222222222222222222222222222222222 +2039 2222222222222222222222222222222222222222222222222222222 +2040 2222222222222222222222222222222222222222222222222222222 +2041 2222222222222222222222222222222222222222222222222222222 +2042 2222222222222222222222222222222222222222222222222222222 +2043 2222222222222222222222222222222222222222222222222222222 +2044 2222222222222222222222222222222222222222222222222222222 +2045 2222222222222222222222222222222222222222222222222222222 +2046 2222222222222222222222222222222222222222222222222222222 +2047 2222222222222222222222222222222222222222222222222222222 +2048 2222222222222222222222222222222222222222222222222222222 +2049 2222222222222222222222222222222222222222222222222222222 +2050 2222222222222222222222222222222222222222222222222222222 +2051 2222222222222222222222222222222222222222222222222222222 +2052 2222222222222222222222222222222222222222222222222222222 +2053 2222222222222222222222222222222222222222222222222222222 +2054 2222222222222222222222222222222222222222222222222222222 +2055 2222222222222222222222222222222222222222222222222222222 +2056 2222222222222222222222222222222222222222222222222222222 +2057 2222222222222222222222222222222222222222222222222222222 +2058 2222222222222222222222222222222222222222222222222222222 +2059 2222222222222222222222222222222222222222222222222222222 +2060 2222222222222222222222222222222222222222222222222222222 +2061 2222222222222222222222222222222222222222222222222222222 +2062 2222222222222222222222222222222222222222222222222222222 +2063 2222222222222222222222222222222222222222222222222222222 +2064 2222222222222222222222222222222222222222222222222222222 +2065 2222222222222222222222222222222222222222222222222222222 +2066 2222222222222222222222222222222222222222222222222222222 +2067 2222222222222222222222222222222222222222222222222222222 +2068 2222222222222222222222222222222222222222222222222222222 +2069 2222222222222222222222222222222222222222222222222222222 +2070 2222222222222222222222222222222222222222222222222222222 +2071 2222222222222222222222222222222222222222222222222222222 +2072 2222222222222222222222222222222222222222222222222222222 +2073 2222222222222222222222222222222222222222222222222222222 +2074 2222222222222222222222222222222222222222222222222222222 +2075 2222222222222222222222222222222222222222222222222222222 +2076 2222222222222222222222222222222222222222222222222222222 +2077 2222222222222222222222222222222222222222222222222222222 +2078 2222222222222222222222222222222222222222222222222222222 +2079 2222222222222222222222222222222222222222222222222222222 +2080 2222222222222222222222222222222222222222222222222222222 +2081 2222222222222222222222222222222222222222222222222222222 +2082 2222222222222222222222222222222222222222222222222222222 +2083 2222222222222222222222222222222222222222222222222222222 +2084 2222222222222222222222222222222222222222222222222222222 +2085 2222222222222222222222222222222222222222222222222222222 +2086 2222222222222222222222222222222222222222222222222222222 +2087 2222222222222222222222222222222222222222222222222222222 +2088 2222222222222222222222222222222222222222222222222222222 +2089 2222222222222222222222222222222222222222222222222222222 +2090 2222222222222222222222222222222222222222222222222222222 +2091 2222222222222222222222222222222222222222222222222222222 +2092 2222222222222222222222222222222222222222222222222222222 +2093 2222222222222222222222222222222222222222222222222222222 +2094 2222222222222222222222222222222222222222222222222222222 +2095 2222222222222222222222222222222222222222222222222222222 +2096 2222222222222222222222222222222222222222222222222222222 +2097 2222222222222222222222222222222222222222222222222222222 +2098 2222222222222222222222222222222222222222222222222222222 +2099 2222222222222222222222222222222222222222222222222222222 +2100 2222222222222222222222222222222222222222222222222222222 +2101 2222222222222222222222222222222222222222222222222222222 +2102 2222222222222222222222222222222222222222222222222222222 +2103 2222222222222222222222222222222222222222222222222222222 +2104 2222222222222222222222222222222222222222222222222222222 +2105 2222222222222222222222222222222222222222222222222222222 +2106 2222222222222222222222222222222222222222222222222222222 +2107 2222222222222222222222222222222222222222222222222222222 +2108 2222222222222222222222222222222222222222222222222222222 +2109 2222222222222222222222222222222222222222222222222222222 +2110 2222222222222222222222222222222222222222222222222222222 +2111 2222222222222222222222222222222222222222222222222222222 +2112 2222222222222222222222222222222222222222222222222222222 +2113 2222222222222222222222222222222222222222222222222222222 +2114 2222222222222222222222222222222222222222222222222222222 +2115 2222222222222222222222222222222222222222222222222222222 +2116 2222222222222222222222222222222222222222222222222222222 +2117 2222222222222222222222222222222222222222222222222222222 +2118 2222222222222222222222222222222222222222222222222222222 +2119 2222222222222222222222222222222222222222222222222222222 +2120 2222222222222222222222222222222222222222222222222222222 +2121 2222222222222222222222222222222222222222222222222222222 +2122 2222222222222222222222222222222222222222222222222222222 +2123 2222222222222222222222222222222222222222222222222222222 +2124 2222222222222222222222222222222222222222222222222222222 +2125 2222222222222222222222222222222222222222222222222222222 +2126 2222222222222222222222222222222222222222222222222222222 +2127 2222222222222222222222222222222222222222222222222222222 +2128 2222222222222222222222222222222222222222222222222222222 +2129 2222222222222222222222222222222222222222222222222222222 +2130 2222222222222222222222222222222222222222222222222222222 +2131 2222222222222222222222222222222222222222222222222222222 +2132 2222222222222222222222222222222222222222222222222222222 +2133 2222222222222222222222222222222222222222222222222222222 +2134 2222222222222222222222222222222222222222222222222222222 +2135 2222222222222222222222222222222222222222222222222222222 +2136 2222222222222222222222222222222222222222222222222222222 +2137 2222222222222222222222222222222222222222222222222222222 +2138 2222222222222222222222222222222222222222222222222222222 +2139 2222222222222222222222222222222222222222222222222222222 +2140 2222222222222222222222222222222222222222222222222222222 +2141 2222222222222222222222222222222222222222222222222222222 +2142 2222222222222222222222222222222222222222222222222222222 +2143 2222222222222222222222222222222222222222222222222222222 +2144 2222222222222222222222222222222222222222222222222222222 +2145 2222222222222222222222222222222222222222222222222222222 +2146 2222222222222222222222222222222222222222222222222222222 +2147 2222222222222222222222222222222222222222222222222222222 +2148 2222222222222222222222222222222222222222222222222222222 +2149 2222222222222222222222222222222222222222222222222222222 +2150 2222222222222222222222222222222222222222222222222222222 +2151 2222222222222222222222222222222222222222222222222222222 +2152 2222222222222222222222222222222222222222222222222222222 +2153 2222222222222222222222222222222222222222222222222222222 +2154 2222222222222222222222222222222222222222222222222222222 +2155 2222222222222222222222222222222222222222222222222222222 +2156 2222222222222222222222222222222222222222222222222222222 +2157 2222222222222222222222222222222222222222222222222222222 +2158 2222222222222222222222222222222222222222222222222222222 +2159 2222222222222222222222222222222222222222222222222222222 +2160 2222222222222222222222222222222222222222222222222222222 +2161 2222222222222222222222222222222222222222222222222222222 +2162 2222222222222222222222222222222222222222222222222222222 +2163 2222222222222222222222222222222222222222222222222222222 +2164 2222222222222222222222222222222222222222222222222222222 +2165 2222222222222222222222222222222222222222222222222222222 +2166 2222222222222222222222222222222222222222222222222222222 +2167 2222222222222222222222222222222222222222222222222222222 +2168 2222222222222222222222222222222222222222222222222222222 +2169 2222222222222222222222222222222222222222222222222222222 +2170 2222222222222222222222222222222222222222222222222222222 +2171 2222222222222222222222222222222222222222222222222222222 +2172 2222222222222222222222222222222222222222222222222222222 +2173 2222222222222222222222222222222222222222222222222222222 +2174 2222222222222222222222222222222222222222222222222222222 +2175 2222222222222222222222222222222222222222222222222222222 +2176 2222222222222222222222222222222222222222222222222222222 +2177 2222222222222222222222222222222222222222222222222222222 +2178 2222222222222222222222222222222222222222222222222222222 +2179 2222222222222222222222222222222222222222222222222222222 +2180 2222222222222222222222222222222222222222222222222222222 +2181 2222222222222222222222222222222222222222222222222222222 +2182 2222222222222222222222222222222222222222222222222222222 +2183 2222222222222222222222222222222222222222222222222222222 +2184 2222222222222222222222222222222222222222222222222222222 +2185 2222222222222222222222222222222222222222222222222222222 +2186 2222222222222222222222222222222222222222222222222222222 +2187 2222222222222222222222222222222222222222222222222222222 +2188 2222222222222222222222222222222222222222222222222222222 +2189 2222222222222222222222222222222222222222222222222222222 +2190 2222222222222222222222222222222222222222222222222222222 +2191 2222222222222222222222222222222222222222222222222222222 +2192 2222222222222222222222222222222222222222222222222222222 +2193 2222222222222222222222222222222222222222222222222222222 +2194 2222222222222222222222222222222222222222222222222222222 +2195 2222222222222222222222222222222222222222222222222222222 +2196 2222222222222222222222222222222222222222222222222222222 +2197 2222222222222222222222222222222222222222222222222222222 +2198 2222222222222222222222222222222222222222222222222222222 +2199 2222222222222222222222222222222222222222222222222222222 +2200 2222222222222222222222222222222222222222222222222222222 +2201 2222222222222222222222222222222222222222222222222222222 +2202 2222222222222222222222222222222222222222222222222222222 +2203 2222222222222222222222222222222222222222222222222222222 +2204 2222222222222222222222222222222222222222222222222222222 +2205 2222222222222222222222222222222222222222222222222222222 +2206 2222222222222222222222222222222222222222222222222222222 +2207 2222222222222222222222222222222222222222222222222222222 +2208 2222222222222222222222222222222222222222222222222222222 +2209 2222222222222222222222222222222222222222222222222222222 +2210 2222222222222222222222222222222222222222222222222222222 +2211 2222222222222222222222222222222222222222222222222222222 +2212 2222222222222222222222222222222222222222222222222222222 +2213 2222222222222222222222222222222222222222222222222222222 +2214 2222222222222222222222222222222222222222222222222222222 +2215 2222222222222222222222222222222222222222222222222222222 +2216 2222222222222222222222222222222222222222222222222222222 +2217 2222222222222222222222222222222222222222222222222222222 +2218 2222222222222222222222222222222222222222222222222222222 +2219 2222222222222222222222222222222222222222222222222222222 +2220 2222222222222222222222222222222222222222222222222222222 +2221 2222222222222222222222222222222222222222222222222222222 +2222 2222222222222222222222222222222222222222222222222222222 +2223 2222222222222222222222222222222222222222222222222222222 +2224 2222222222222222222222222222222222222222222222222222222 +2225 2222222222222222222222222222222222222222222222222222222 +2226 2222222222222222222222222222222222222222222222222222222 +2227 2222222222222222222222222222222222222222222222222222222 +2228 2222222222222222222222222222222222222222222222222222222 +2229 2222222222222222222222222222222222222222222222222222222 +2230 2222222222222222222222222222222222222222222222222222222 +2231 2222222222222222222222222222222222222222222222222222222 +2232 2222222222222222222222222222222222222222222222222222222 +2233 2222222222222222222222222222222222222222222222222222222 +2234 2222222222222222222222222222222222222222222222222222222 +2235 2222222222222222222222222222222222222222222222222222222 +2236 2222222222222222222222222222222222222222222222222222222 +2237 2222222222222222222222222222222222222222222222222222222 +2238 2222222222222222222222222222222222222222222222222222222 +2239 2222222222222222222222222222222222222222222222222222222 +2240 2222222222222222222222222222222222222222222222222222222 +2241 2222222222222222222222222222222222222222222222222222222 +2242 2222222222222222222222222222222222222222222222222222222 +2243 2222222222222222222222222222222222222222222222222222222 +2244 2222222222222222222222222222222222222222222222222222222 +2245 2222222222222222222222222222222222222222222222222222222 +2246 2222222222222222222222222222222222222222222222222222222 +2247 2222222222222222222222222222222222222222222222222222222 +2248 2222222222222222222222222222222222222222222222222222222 +2249 2222222222222222222222222222222222222222222222222222222 +2250 2222222222222222222222222222222222222222222222222222222 +2251 2222222222222222222222222222222222222222222222222222222 +2252 2222222222222222222222222222222222222222222222222222222 +2253 2222222222222222222222222222222222222222222222222222222 +2254 2222222222222222222222222222222222222222222222222222222 +2255 2222222222222222222222222222222222222222222222222222222 +2256 2222222222222222222222222222222222222222222222222222222 +2257 2222222222222222222222222222222222222222222222222222222 +2258 2222222222222222222222222222222222222222222222222222222 +2259 2222222222222222222222222222222222222222222222222222222 +2260 2222222222222222222222222222222222222222222222222222222 +2261 2222222222222222222222222222222222222222222222222222222 +2262 2222222222222222222222222222222222222222222222222222222 +2263 2222222222222222222222222222222222222222222222222222222 +2264 2222222222222222222222222222222222222222222222222222222 +2265 2222222222222222222222222222222222222222222222222222222 +2266 2222222222222222222222222222222222222222222222222222222 +2267 2222222222222222222222222222222222222222222222222222222 +2268 2222222222222222222222222222222222222222222222222222222 +2269 2222222222222222222222222222222222222222222222222222222 +2270 2222222222222222222222222222222222222222222222222222222 +2271 2222222222222222222222222222222222222222222222222222222 +2272 2222222222222222222222222222222222222222222222222222222 +2273 2222222222222222222222222222222222222222222222222222222 +2274 2222222222222222222222222222222222222222222222222222222 +2275 2222222222222222222222222222222222222222222222222222222 +2276 2222222222222222222222222222222222222222222222222222222 +2277 2222222222222222222222222222222222222222222222222222222 +2278 2222222222222222222222222222222222222222222222222222222 +2279 2222222222222222222222222222222222222222222222222222222 +2280 2222222222222222222222222222222222222222222222222222222 +2281 2222222222222222222222222222222222222222222222222222222 +2282 2222222222222222222222222222222222222222222222222222222 +2283 2222222222222222222222222222222222222222222222222222222 +2284 2222222222222222222222222222222222222222222222222222222 +2285 2222222222222222222222222222222222222222222222222222222 +2286 2222222222222222222222222222222222222222222222222222222 +2287 2222222222222222222222222222222222222222222222222222222 +2288 2222222222222222222222222222222222222222222222222222222 +2289 2222222222222222222222222222222222222222222222222222222 +2290 2222222222222222222222222222222222222222222222222222222 +2291 2222222222222222222222222222222222222222222222222222222 +2292 2222222222222222222222222222222222222222222222222222222 +2293 2222222222222222222222222222222222222222222222222222222 +2294 2222222222222222222222222222222222222222222222222222222 +2295 2222222222222222222222222222222222222222222222222222222 +2296 2222222222222222222222222222222222222222222222222222222 +2297 2222222222222222222222222222222222222222222222222222222 +2298 2222222222222222222222222222222222222222222222222222222 +2299 2222222222222222222222222222222222222222222222222222222 +2300 2222222222222222222222222222222222222222222222222222222 +2301 2222222222222222222222222222222222222222222222222222222 +2302 2222222222222222222222222222222222222222222222222222222 +2303 2222222222222222222222222222222222222222222222222222222 +2304 2222222222222222222222222222222222222222222222222222222 +2305 2222222222222222222222222222222222222222222222222222222 +2306 2222222222222222222222222222222222222222222222222222222 +2307 2222222222222222222222222222222222222222222222222222222 +2308 2222222222222222222222222222222222222222222222222222222 +2309 2222222222222222222222222222222222222222222222222222222 +2310 2222222222222222222222222222222222222222222222222222222 +2311 2222222222222222222222222222222222222222222222222222222 +2312 2222222222222222222222222222222222222222222222222222222 +2313 2222222222222222222222222222222222222222222222222222222 +2314 2222222222222222222222222222222222222222222222222222222 +2315 2222222222222222222222222222222222222222222222222222222 +2316 2222222222222222222222222222222222222222222222222222222 +2317 2222222222222222222222222222222222222222222222222222222 +2318 2222222222222222222222222222222222222222222222222222222 +2319 2222222222222222222222222222222222222222222222222222222 +2320 2222222222222222222222222222222222222222222222222222222 +2321 2222222222222222222222222222222222222222222222222222222 +2322 2222222222222222222222222222222222222222222222222222222 +2323 2222222222222222222222222222222222222222222222222222222 +2324 2222222222222222222222222222222222222222222222222222222 +2325 2222222222222222222222222222222222222222222222222222222 +2326 2222222222222222222222222222222222222222222222222222222 +2327 2222222222222222222222222222222222222222222222222222222 +2328 2222222222222222222222222222222222222222222222222222222 +2329 2222222222222222222222222222222222222222222222222222222 +2330 2222222222222222222222222222222222222222222222222222222 +2331 2222222222222222222222222222222222222222222222222222222 +2332 2222222222222222222222222222222222222222222222222222222 +2333 2222222222222222222222222222222222222222222222222222222 +2334 2222222222222222222222222222222222222222222222222222222 +2335 2222222222222222222222222222222222222222222222222222222 +2336 2222222222222222222222222222222222222222222222222222222 +2337 2222222222222222222222222222222222222222222222222222222 +2338 2222222222222222222222222222222222222222222222222222222 +2339 2222222222222222222222222222222222222222222222222222222 +2340 2222222222222222222222222222222222222222222222222222222 +2341 2222222222222222222222222222222222222222222222222222222 +2342 2222222222222222222222222222222222222222222222222222222 +2343 2222222222222222222222222222222222222222222222222222222 +2344 2222222222222222222222222222222222222222222222222222222 +2345 2222222222222222222222222222222222222222222222222222222 +2346 2222222222222222222222222222222222222222222222222222222 +2347 2222222222222222222222222222222222222222222222222222222 +2348 2222222222222222222222222222222222222222222222222222222 +2349 2222222222222222222222222222222222222222222222222222222 +2350 2222222222222222222222222222222222222222222222222222222 +2351 2222222222222222222222222222222222222222222222222222222 +2352 2222222222222222222222222222222222222222222222222222222 +2353 2222222222222222222222222222222222222222222222222222222 +2354 2222222222222222222222222222222222222222222222222222222 +2355 2222222222222222222222222222222222222222222222222222222 +2356 2222222222222222222222222222222222222222222222222222222 +2357 2222222222222222222222222222222222222222222222222222222 +2358 2222222222222222222222222222222222222222222222222222222 +2359 2222222222222222222222222222222222222222222222222222222 +2360 2222222222222222222222222222222222222222222222222222222 +2361 2222222222222222222222222222222222222222222222222222222 +2362 2222222222222222222222222222222222222222222222222222222 +2363 2222222222222222222222222222222222222222222222222222222 +2364 2222222222222222222222222222222222222222222222222222222 +2365 2222222222222222222222222222222222222222222222222222222 +2366 2222222222222222222222222222222222222222222222222222222 +2367 2222222222222222222222222222222222222222222222222222222 +2368 2222222222222222222222222222222222222222222222222222222 +2369 2222222222222222222222222222222222222222222222222222222 +2370 2222222222222222222222222222222222222222222222222222222 +2371 2222222222222222222222222222222222222222222222222222222 +2372 2222222222222222222222222222222222222222222222222222222 +2373 2222222222222222222222222222222222222222222222222222222 +2374 2222222222222222222222222222222222222222222222222222222 +2375 2222222222222222222222222222222222222222222222222222222 +2376 2222222222222222222222222222222222222222222222222222222 +2377 2222222222222222222222222222222222222222222222222222222 +2378 2222222222222222222222222222222222222222222222222222222 +2379 2222222222222222222222222222222222222222222222222222222 +2380 2222222222222222222222222222222222222222222222222222222 +2381 2222222222222222222222222222222222222222222222222222222 +2382 2222222222222222222222222222222222222222222222222222222 +2383 2222222222222222222222222222222222222222222222222222222 +2384 2222222222222222222222222222222222222222222222222222222 +2385 2222222222222222222222222222222222222222222222222222222 +2386 2222222222222222222222222222222222222222222222222222222 +2387 2222222222222222222222222222222222222222222222222222222 +2388 2222222222222222222222222222222222222222222222222222222 +2389 2222222222222222222222222222222222222222222222222222222 +2390 2222222222222222222222222222222222222222222222222222222 +2391 2222222222222222222222222222222222222222222222222222222 +2392 2222222222222222222222222222222222222222222222222222222 +2393 2222222222222222222222222222222222222222222222222222222 +2394 2222222222222222222222222222222222222222222222222222222 +2395 2222222222222222222222222222222222222222222222222222222 +2396 2222222222222222222222222222222222222222222222222222222 +2397 2222222222222222222222222222222222222222222222222222222 +2398 2222222222222222222222222222222222222222222222222222222 +2399 2222222222222222222222222222222222222222222222222222222 +2400 2222222222222222222222222222222222222222222222222222222 +2401 2222222222222222222222222222222222222222222222222222222 +2402 2222222222222222222222222222222222222222222222222222222 +2403 2222222222222222222222222222222222222222222222222222222 +2404 2222222222222222222222222222222222222222222222222222222 +2405 2222222222222222222222222222222222222222222222222222222 +2406 2222222222222222222222222222222222222222222222222222222 +2407 2222222222222222222222222222222222222222222222222222222 +2408 2222222222222222222222222222222222222222222222222222222 +2409 2222222222222222222222222222222222222222222222222222222 +2410 2222222222222222222222222222222222222222222222222222222 +2411 2222222222222222222222222222222222222222222222222222222 +2412 2222222222222222222222222222222222222222222222222222222 +2413 2222222222222222222222222222222222222222222222222222222 +2414 2222222222222222222222222222222222222222222222222222222 +2415 2222222222222222222222222222222222222222222222222222222 +2416 2222222222222222222222222222222222222222222222222222222 +2417 2222222222222222222222222222222222222222222222222222222 +2418 2222222222222222222222222222222222222222222222222222222 +2419 2222222222222222222222222222222222222222222222222222222 +2420 2222222222222222222222222222222222222222222222222222222 +2421 2222222222222222222222222222222222222222222222222222222 +2422 2222222222222222222222222222222222222222222222222222222 +2423 2222222222222222222222222222222222222222222222222222222 +2424 2222222222222222222222222222222222222222222222222222222 +2425 2222222222222222222222222222222222222222222222222222222 +2426 2222222222222222222222222222222222222222222222222222222 +2427 2222222222222222222222222222222222222222222222222222222 +2428 2222222222222222222222222222222222222222222222222222222 +2429 2222222222222222222222222222222222222222222222222222222 +2430 2222222222222222222222222222222222222222222222222222222 +2431 2222222222222222222222222222222222222222222222222222222 +2432 2222222222222222222222222222222222222222222222222222222 +2433 2222222222222222222222222222222222222222222222222222222 +2434 2222222222222222222222222222222222222222222222222222222 +2435 2222222222222222222222222222222222222222222222222222222 +2436 2222222222222222222222222222222222222222222222222222222 +2437 2222222222222222222222222222222222222222222222222222222 +2438 2222222222222222222222222222222222222222222222222222222 +2439 2222222222222222222222222222222222222222222222222222222 +2440 2222222222222222222222222222222222222222222222222222222 +2441 2222222222222222222222222222222222222222222222222222222 +2442 2222222222222222222222222222222222222222222222222222222 +2443 2222222222222222222222222222222222222222222222222222222 +2444 2222222222222222222222222222222222222222222222222222222 +2445 2222222222222222222222222222222222222222222222222222222 +2446 2222222222222222222222222222222222222222222222222222222 +2447 2222222222222222222222222222222222222222222222222222222 +2448 2222222222222222222222222222222222222222222222222222222 +2449 2222222222222222222222222222222222222222222222222222222 +2450 2222222222222222222222222222222222222222222222222222222 +2451 2222222222222222222222222222222222222222222222222222222 +2452 2222222222222222222222222222222222222222222222222222222 +2453 2222222222222222222222222222222222222222222222222222222 +2454 2222222222222222222222222222222222222222222222222222222 +2455 2222222222222222222222222222222222222222222222222222222 +2456 2222222222222222222222222222222222222222222222222222222 +2457 2222222222222222222222222222222222222222222222222222222 +2458 2222222222222222222222222222222222222222222222222222222 +2459 2222222222222222222222222222222222222222222222222222222 +2460 2222222222222222222222222222222222222222222222222222222 +2461 2222222222222222222222222222222222222222222222222222222 +2462 2222222222222222222222222222222222222222222222222222222 +2463 2222222222222222222222222222222222222222222222222222222 +2464 2222222222222222222222222222222222222222222222222222222 +2465 2222222222222222222222222222222222222222222222222222222 +2466 2222222222222222222222222222222222222222222222222222222 +2467 2222222222222222222222222222222222222222222222222222222 +2468 2222222222222222222222222222222222222222222222222222222 +2469 2222222222222222222222222222222222222222222222222222222 +2470 2222222222222222222222222222222222222222222222222222222 +2471 2222222222222222222222222222222222222222222222222222222 +2472 2222222222222222222222222222222222222222222222222222222 +2473 2222222222222222222222222222222222222222222222222222222 +2474 2222222222222222222222222222222222222222222222222222222 +2475 2222222222222222222222222222222222222222222222222222222 +2476 2222222222222222222222222222222222222222222222222222222 +2477 2222222222222222222222222222222222222222222222222222222 +2478 2222222222222222222222222222222222222222222222222222222 +2479 2222222222222222222222222222222222222222222222222222222 +2480 2222222222222222222222222222222222222222222222222222222 +2481 2222222222222222222222222222222222222222222222222222222 +2482 2222222222222222222222222222222222222222222222222222222 +2483 2222222222222222222222222222222222222222222222222222222 +2484 2222222222222222222222222222222222222222222222222222222 +2485 2222222222222222222222222222222222222222222222222222222 +2486 2222222222222222222222222222222222222222222222222222222 +2487 2222222222222222222222222222222222222222222222222222222 +2488 2222222222222222222222222222222222222222222222222222222 +2489 2222222222222222222222222222222222222222222222222222222 +2490 2222222222222222222222222222222222222222222222222222222 +2491 2222222222222222222222222222222222222222222222222222222 +2492 2222222222222222222222222222222222222222222222222222222 +2493 2222222222222222222222222222222222222222222222222222222 +2494 2222222222222222222222222222222222222222222222222222222 +2495 2222222222222222222222222222222222222222222222222222222 +2496 2222222222222222222222222222222222222222222222222222222 +2497 2222222222222222222222222222222222222222222222222222222 +2498 2222222222222222222222222222222222222222222222222222222 +2499 2222222222222222222222222222222222222222222222222222222 +2500 2222222222222222222222222222222222222222222222222222222 +2501 2222222222222222222222222222222222222222222222222222222 +2502 2222222222222222222222222222222222222222222222222222222 +2503 2222222222222222222222222222222222222222222222222222222 +2504 2222222222222222222222222222222222222222222222222222222 +2505 2222222222222222222222222222222222222222222222222222222 +2506 2222222222222222222222222222222222222222222222222222222 +2507 2222222222222222222222222222222222222222222222222222222 +2508 2222222222222222222222222222222222222222222222222222222 +2509 2222222222222222222222222222222222222222222222222222222 +2510 2222222222222222222222222222222222222222222222222222222 +2511 2222222222222222222222222222222222222222222222222222222 +2512 2222222222222222222222222222222222222222222222222222222 +2513 2222222222222222222222222222222222222222222222222222222 +2514 2222222222222222222222222222222222222222222222222222222 +2515 2222222222222222222222222222222222222222222222222222222 +2516 2222222222222222222222222222222222222222222222222222222 +2517 2222222222222222222222222222222222222222222222222222222 +2518 2222222222222222222222222222222222222222222222222222222 +2519 2222222222222222222222222222222222222222222222222222222 +2520 2222222222222222222222222222222222222222222222222222222 +2521 2222222222222222222222222222222222222222222222222222222 +2522 2222222222222222222222222222222222222222222222222222222 +2523 2222222222222222222222222222222222222222222222222222222 +2524 2222222222222222222222222222222222222222222222222222222 +2525 2222222222222222222222222222222222222222222222222222222 +2526 2222222222222222222222222222222222222222222222222222222 +2527 2222222222222222222222222222222222222222222222222222222 +2528 2222222222222222222222222222222222222222222222222222222 +2529 2222222222222222222222222222222222222222222222222222222 +2530 2222222222222222222222222222222222222222222222222222222 +2531 2222222222222222222222222222222222222222222222222222222 +2532 2222222222222222222222222222222222222222222222222222222 +2533 2222222222222222222222222222222222222222222222222222222 +2534 2222222222222222222222222222222222222222222222222222222 +2535 2222222222222222222222222222222222222222222222222222222 +2536 2222222222222222222222222222222222222222222222222222222 +2537 2222222222222222222222222222222222222222222222222222222 +2538 2222222222222222222222222222222222222222222222222222222 +2539 2222222222222222222222222222222222222222222222222222222 +2540 2222222222222222222222222222222222222222222222222222222 +2541 2222222222222222222222222222222222222222222222222222222 +2542 2222222222222222222222222222222222222222222222222222222 +2543 2222222222222222222222222222222222222222222222222222222 +2544 2222222222222222222222222222222222222222222222222222222 +2545 2222222222222222222222222222222222222222222222222222222 +2546 2222222222222222222222222222222222222222222222222222222 +2547 2222222222222222222222222222222222222222222222222222222 +2548 2222222222222222222222222222222222222222222222222222222 +2549 2222222222222222222222222222222222222222222222222222222 +2550 2222222222222222222222222222222222222222222222222222222 +2551 2222222222222222222222222222222222222222222222222222222 +2552 2222222222222222222222222222222222222222222222222222222 +2553 2222222222222222222222222222222222222222222222222222222 +2554 2222222222222222222222222222222222222222222222222222222 +2555 2222222222222222222222222222222222222222222222222222222 +2556 2222222222222222222222222222222222222222222222222222222 +2557 2222222222222222222222222222222222222222222222222222222 +2558 2222222222222222222222222222222222222222222222222222222 +2559 2222222222222222222222222222222222222222222222222222222 +2560 2222222222222222222222222222222222222222222222222222222 +2561 2222222222222222222222222222222222222222222222222222222 +2562 2222222222222222222222222222222222222222222222222222222 +2563 2222222222222222222222222222222222222222222222222222222 +2564 2222222222222222222222222222222222222222222222222222222 +2565 2222222222222222222222222222222222222222222222222222222 +2566 2222222222222222222222222222222222222222222222222222222 +2567 2222222222222222222222222222222222222222222222222222222 +2568 2222222222222222222222222222222222222222222222222222222 +2569 2222222222222222222222222222222222222222222222222222222 +2570 2222222222222222222222222222222222222222222222222222222 +2571 2222222222222222222222222222222222222222222222222222222 +2572 2222222222222222222222222222222222222222222222222222222 +2573 2222222222222222222222222222222222222222222222222222222 +2574 2222222222222222222222222222222222222222222222222222222 +2575 2222222222222222222222222222222222222222222222222222222 +2576 2222222222222222222222222222222222222222222222222222222 +2577 2222222222222222222222222222222222222222222222222222222 +2578 2222222222222222222222222222222222222222222222222222222 +2579 2222222222222222222222222222222222222222222222222222222 +2580 2222222222222222222222222222222222222222222222222222222 +2581 2222222222222222222222222222222222222222222222222222222 +2582 2222222222222222222222222222222222222222222222222222222 +2583 2222222222222222222222222222222222222222222222222222222 +2584 2222222222222222222222222222222222222222222222222222222 +2585 2222222222222222222222222222222222222222222222222222222 +2586 2222222222222222222222222222222222222222222222222222222 +2587 2222222222222222222222222222222222222222222222222222222 +2588 2222222222222222222222222222222222222222222222222222222 +2589 2222222222222222222222222222222222222222222222222222222 +2590 2222222222222222222222222222222222222222222222222222222 +2591 2222222222222222222222222222222222222222222222222222222 +2592 2222222222222222222222222222222222222222222222222222222 +2593 2222222222222222222222222222222222222222222222222222222 +2594 2222222222222222222222222222222222222222222222222222222 +2595 2222222222222222222222222222222222222222222222222222222 +2596 2222222222222222222222222222222222222222222222222222222 +2597 2222222222222222222222222222222222222222222222222222222 +2598 2222222222222222222222222222222222222222222222222222222 +2599 2222222222222222222222222222222222222222222222222222222 +2600 2222222222222222222222222222222222222222222222222222222 +2601 2222222222222222222222222222222222222222222222222222222 +2602 2222222222222222222222222222222222222222222222222222222 +2603 2222222222222222222222222222222222222222222222222222222 +2604 2222222222222222222222222222222222222222222222222222222 +2605 2222222222222222222222222222222222222222222222222222222 +2606 2222222222222222222222222222222222222222222222222222222 +2607 2222222222222222222222222222222222222222222222222222222 +2608 2222222222222222222222222222222222222222222222222222222 +2609 2222222222222222222222222222222222222222222222222222222 +2610 2222222222222222222222222222222222222222222222222222222 +2611 2222222222222222222222222222222222222222222222222222222 +2612 2222222222222222222222222222222222222222222222222222222 +2613 2222222222222222222222222222222222222222222222222222222 +2614 2222222222222222222222222222222222222222222222222222222 +2615 2222222222222222222222222222222222222222222222222222222 +2616 2222222222222222222222222222222222222222222222222222222 +2617 2222222222222222222222222222222222222222222222222222222 +2618 2222222222222222222222222222222222222222222222222222222 +2619 2222222222222222222222222222222222222222222222222222222 +2620 2222222222222222222222222222222222222222222222222222222 +2621 2222222222222222222222222222222222222222222222222222222 +2622 2222222222222222222222222222222222222222222222222222222 +2623 2222222222222222222222222222222222222222222222222222222 +2624 2222222222222222222222222222222222222222222222222222222 +2625 2222222222222222222222222222222222222222222222222222222 +2626 2222222222222222222222222222222222222222222222222222222 +2627 2222222222222222222222222222222222222222222222222222222 +2628 2222222222222222222222222222222222222222222222222222222 +2629 2222222222222222222222222222222222222222222222222222222 +2630 2222222222222222222222222222222222222222222222222222222 +2631 2222222222222222222222222222222222222222222222222222222 +2632 2222222222222222222222222222222222222222222222222222222 +2633 2222222222222222222222222222222222222222222222222222222 +2634 2222222222222222222222222222222222222222222222222222222 +2635 2222222222222222222222222222222222222222222222222222222 +2636 2222222222222222222222222222222222222222222222222222222 +2637 2222222222222222222222222222222222222222222222222222222 +2638 2222222222222222222222222222222222222222222222222222222 +2639 2222222222222222222222222222222222222222222222222222222 +2640 2222222222222222222222222222222222222222222222222222222 +2641 2222222222222222222222222222222222222222222222222222222 +2642 2222222222222222222222222222222222222222222222222222222 +2643 2222222222222222222222222222222222222222222222222222222 +2644 2222222222222222222222222222222222222222222222222222222 +2645 2222222222222222222222222222222222222222222222222222222 +2646 2222222222222222222222222222222222222222222222222222222 +2647 2222222222222222222222222222222222222222222222222222222 +2648 2222222222222222222222222222222222222222222222222222222 +2649 2222222222222222222222222222222222222222222222222222222 +2650 2222222222222222222222222222222222222222222222222222222 +2651 2222222222222222222222222222222222222222222222222222222 +2652 2222222222222222222222222222222222222222222222222222222 +2653 2222222222222222222222222222222222222222222222222222222 +2654 2222222222222222222222222222222222222222222222222222222 +2655 2222222222222222222222222222222222222222222222222222222 +2656 2222222222222222222222222222222222222222222222222222222 +2657 2222222222222222222222222222222222222222222222222222222 +2658 2222222222222222222222222222222222222222222222222222222 +2659 2222222222222222222222222222222222222222222222222222222 +2660 2222222222222222222222222222222222222222222222222222222 +2661 2222222222222222222222222222222222222222222222222222222 +2662 2222222222222222222222222222222222222222222222222222222 +2663 2222222222222222222222222222222222222222222222222222222 +2664 2222222222222222222222222222222222222222222222222222222 +2665 2222222222222222222222222222222222222222222222222222222 +2666 2222222222222222222222222222222222222222222222222222222 +2667 2222222222222222222222222222222222222222222222222222222 +2668 2222222222222222222222222222222222222222222222222222222 +2669 2222222222222222222222222222222222222222222222222222222 +2670 2222222222222222222222222222222222222222222222222222222 +2671 2222222222222222222222222222222222222222222222222222222 +2672 2222222222222222222222222222222222222222222222222222222 +2673 2222222222222222222222222222222222222222222222222222222 +2674 2222222222222222222222222222222222222222222222222222222 +2675 2222222222222222222222222222222222222222222222222222222 +2676 2222222222222222222222222222222222222222222222222222222 +2677 2222222222222222222222222222222222222222222222222222222 +2678 2222222222222222222222222222222222222222222222222222222 +2679 2222222222222222222222222222222222222222222222222222222 +2680 2222222222222222222222222222222222222222222222222222222 +2681 2222222222222222222222222222222222222222222222222222222 +2682 2222222222222222222222222222222222222222222222222222222 +2683 2222222222222222222222222222222222222222222222222222222 +2684 2222222222222222222222222222222222222222222222222222222 +2685 2222222222222222222222222222222222222222222222222222222 +2686 2222222222222222222222222222222222222222222222222222222 +2687 2222222222222222222222222222222222222222222222222222222 +2688 2222222222222222222222222222222222222222222222222222222 +2689 2222222222222222222222222222222222222222222222222222222 +2690 2222222222222222222222222222222222222222222222222222222 +2691 2222222222222222222222222222222222222222222222222222222 +2692 2222222222222222222222222222222222222222222222222222222 +2693 2222222222222222222222222222222222222222222222222222222 +2694 2222222222222222222222222222222222222222222222222222222 +2695 2222222222222222222222222222222222222222222222222222222 +2696 2222222222222222222222222222222222222222222222222222222 +2697 2222222222222222222222222222222222222222222222222222222 +2698 2222222222222222222222222222222222222222222222222222222 +2699 2222222222222222222222222222222222222222222222222222222 +2700 2222222222222222222222222222222222222222222222222222222 +2701 2222222222222222222222222222222222222222222222222222222 +2702 2222222222222222222222222222222222222222222222222222222 +2703 2222222222222222222222222222222222222222222222222222222 +2704 2222222222222222222222222222222222222222222222222222222 +2705 2222222222222222222222222222222222222222222222222222222 +2706 2222222222222222222222222222222222222222222222222222222 +2707 2222222222222222222222222222222222222222222222222222222 +2708 2222222222222222222222222222222222222222222222222222222 +2709 2222222222222222222222222222222222222222222222222222222 +2710 2222222222222222222222222222222222222222222222222222222 +2711 2222222222222222222222222222222222222222222222222222222 +2712 2222222222222222222222222222222222222222222222222222222 +2713 2222222222222222222222222222222222222222222222222222222 +2714 2222222222222222222222222222222222222222222222222222222 +2715 2222222222222222222222222222222222222222222222222222222 +2716 2222222222222222222222222222222222222222222222222222222 +2717 2222222222222222222222222222222222222222222222222222222 +2718 2222222222222222222222222222222222222222222222222222222 +2719 2222222222222222222222222222222222222222222222222222222 +2720 2222222222222222222222222222222222222222222222222222222 +2721 2222222222222222222222222222222222222222222222222222222 +2722 2222222222222222222222222222222222222222222222222222222 +2723 2222222222222222222222222222222222222222222222222222222 +2724 2222222222222222222222222222222222222222222222222222222 +2725 2222222222222222222222222222222222222222222222222222222 +2726 2222222222222222222222222222222222222222222222222222222 +2727 2222222222222222222222222222222222222222222222222222222 +2728 2222222222222222222222222222222222222222222222222222222 +2729 2222222222222222222222222222222222222222222222222222222 +2730 2222222222222222222222222222222222222222222222222222222 +2731 2222222222222222222222222222222222222222222222222222222 +2732 2222222222222222222222222222222222222222222222222222222 +2733 2222222222222222222222222222222222222222222222222222222 +2734 2222222222222222222222222222222222222222222222222222222 +2735 2222222222222222222222222222222222222222222222222222222 +2736 2222222222222222222222222222222222222222222222222222222 +2737 2222222222222222222222222222222222222222222222222222222 +2738 2222222222222222222222222222222222222222222222222222222 +2739 2222222222222222222222222222222222222222222222222222222 +2740 2222222222222222222222222222222222222222222222222222222 +2741 2222222222222222222222222222222222222222222222222222222 +2742 2222222222222222222222222222222222222222222222222222222 +2743 2222222222222222222222222222222222222222222222222222222 +2744 2222222222222222222222222222222222222222222222222222222 +2745 2222222222222222222222222222222222222222222222222222222 +2746 2222222222222222222222222222222222222222222222222222222 +2747 2222222222222222222222222222222222222222222222222222222 +2748 2222222222222222222222222222222222222222222222222222222 +2749 2222222222222222222222222222222222222222222222222222222 +2750 2222222222222222222222222222222222222222222222222222222 +2751 2222222222222222222222222222222222222222222222222222222 +2752 2222222222222222222222222222222222222222222222222222222 +2753 2222222222222222222222222222222222222222222222222222222 +2754 2222222222222222222222222222222222222222222222222222222 +2755 2222222222222222222222222222222222222222222222222222222 +2756 2222222222222222222222222222222222222222222222222222222 +2757 2222222222222222222222222222222222222222222222222222222 +2758 2222222222222222222222222222222222222222222222222222222 +2759 2222222222222222222222222222222222222222222222222222222 +2760 2222222222222222222222222222222222222222222222222222222 +2761 2222222222222222222222222222222222222222222222222222222 +2762 2222222222222222222222222222222222222222222222222222222 +2763 2222222222222222222222222222222222222222222222222222222 +2764 2222222222222222222222222222222222222222222222222222222 +2765 2222222222222222222222222222222222222222222222222222222 +2766 2222222222222222222222222222222222222222222222222222222 +2767 2222222222222222222222222222222222222222222222222222222 +2768 2222222222222222222222222222222222222222222222222222222 +2769 2222222222222222222222222222222222222222222222222222222 +2770 2222222222222222222222222222222222222222222222222222222 +2771 2222222222222222222222222222222222222222222222222222222 +2772 2222222222222222222222222222222222222222222222222222222 +2773 2222222222222222222222222222222222222222222222222222222 +2774 2222222222222222222222222222222222222222222222222222222 +2775 2222222222222222222222222222222222222222222222222222222 +2776 2222222222222222222222222222222222222222222222222222222 +2777 2222222222222222222222222222222222222222222222222222222 +2778 2222222222222222222222222222222222222222222222222222222 +2779 2222222222222222222222222222222222222222222222222222222 +2780 2222222222222222222222222222222222222222222222222222222 +2781 2222222222222222222222222222222222222222222222222222222 +2782 2222222222222222222222222222222222222222222222222222222 +2783 2222222222222222222222222222222222222222222222222222222 +2784 2222222222222222222222222222222222222222222222222222222 +2785 2222222222222222222222222222222222222222222222222222222 +2786 2222222222222222222222222222222222222222222222222222222 +2787 2222222222222222222222222222222222222222222222222222222 +2788 2222222222222222222222222222222222222222222222222222222 +2789 2222222222222222222222222222222222222222222222222222222 +2790 2222222222222222222222222222222222222222222222222222222 +2791 2222222222222222222222222222222222222222222222222222222 +2792 2222222222222222222222222222222222222222222222222222222 +2793 2222222222222222222222222222222222222222222222222222222 +2794 2222222222222222222222222222222222222222222222222222222 +2795 2222222222222222222222222222222222222222222222222222222 +2796 2222222222222222222222222222222222222222222222222222222 +2797 2222222222222222222222222222222222222222222222222222222 +2798 2222222222222222222222222222222222222222222222222222222 +2799 2222222222222222222222222222222222222222222222222222222 +2800 2222222222222222222222222222222222222222222222222222222 +2801 2222222222222222222222222222222222222222222222222222222 +2802 2222222222222222222222222222222222222222222222222222222 +2803 2222222222222222222222222222222222222222222222222222222 +2804 2222222222222222222222222222222222222222222222222222222 +2805 2222222222222222222222222222222222222222222222222222222 +2806 2222222222222222222222222222222222222222222222222222222 +2807 2222222222222222222222222222222222222222222222222222222 +2808 2222222222222222222222222222222222222222222222222222222 +2809 2222222222222222222222222222222222222222222222222222222 +2810 2222222222222222222222222222222222222222222222222222222 +2811 2222222222222222222222222222222222222222222222222222222 +2812 2222222222222222222222222222222222222222222222222222222 +2813 2222222222222222222222222222222222222222222222222222222 +2814 2222222222222222222222222222222222222222222222222222222 +2815 2222222222222222222222222222222222222222222222222222222 +2816 2222222222222222222222222222222222222222222222222222222 +2817 2222222222222222222222222222222222222222222222222222222 +2818 2222222222222222222222222222222222222222222222222222222 +2819 2222222222222222222222222222222222222222222222222222222 +2820 2222222222222222222222222222222222222222222222222222222 +2821 2222222222222222222222222222222222222222222222222222222 +2822 2222222222222222222222222222222222222222222222222222222 +2823 2222222222222222222222222222222222222222222222222222222 +2824 2222222222222222222222222222222222222222222222222222222 +2825 2222222222222222222222222222222222222222222222222222222 +2826 2222222222222222222222222222222222222222222222222222222 +2827 2222222222222222222222222222222222222222222222222222222 +2828 2222222222222222222222222222222222222222222222222222222 +2829 2222222222222222222222222222222222222222222222222222222 +2830 2222222222222222222222222222222222222222222222222222222 +2831 2222222222222222222222222222222222222222222222222222222 +2832 2222222222222222222222222222222222222222222222222222222 +2833 2222222222222222222222222222222222222222222222222222222 +2834 2222222222222222222222222222222222222222222222222222222 +2835 2222222222222222222222222222222222222222222222222222222 +2836 2222222222222222222222222222222222222222222222222222222 +2837 2222222222222222222222222222222222222222222222222222222 +2838 2222222222222222222222222222222222222222222222222222222 +2839 2222222222222222222222222222222222222222222222222222222 +2840 2222222222222222222222222222222222222222222222222222222 +2841 2222222222222222222222222222222222222222222222222222222 +2842 2222222222222222222222222222222222222222222222222222222 +2843 2222222222222222222222222222222222222222222222222222222 +2844 2222222222222222222222222222222222222222222222222222222 +2845 2222222222222222222222222222222222222222222222222222222 +2846 2222222222222222222222222222222222222222222222222222222 +2847 2222222222222222222222222222222222222222222222222222222 +2848 2222222222222222222222222222222222222222222222222222222 +2849 2222222222222222222222222222222222222222222222222222222 +2850 2222222222222222222222222222222222222222222222222222222 +2851 2222222222222222222222222222222222222222222222222222222 +2852 2222222222222222222222222222222222222222222222222222222 +2853 2222222222222222222222222222222222222222222222222222222 +2854 2222222222222222222222222222222222222222222222222222222 +2855 2222222222222222222222222222222222222222222222222222222 +2856 2222222222222222222222222222222222222222222222222222222 +2857 2222222222222222222222222222222222222222222222222222222 +2858 2222222222222222222222222222222222222222222222222222222 +2859 2222222222222222222222222222222222222222222222222222222 +2860 2222222222222222222222222222222222222222222222222222222 +2861 2222222222222222222222222222222222222222222222222222222 +2862 2222222222222222222222222222222222222222222222222222222 +2863 2222222222222222222222222222222222222222222222222222222 +2864 2222222222222222222222222222222222222222222222222222222 +2865 2222222222222222222222222222222222222222222222222222222 +2866 2222222222222222222222222222222222222222222222222222222 +2867 2222222222222222222222222222222222222222222222222222222 +2868 2222222222222222222222222222222222222222222222222222222 +2869 2222222222222222222222222222222222222222222222222222222 +2870 2222222222222222222222222222222222222222222222222222222 +2871 2222222222222222222222222222222222222222222222222222222 +2872 2222222222222222222222222222222222222222222222222222222 +2873 2222222222222222222222222222222222222222222222222222222 +2874 2222222222222222222222222222222222222222222222222222222 +2875 2222222222222222222222222222222222222222222222222222222 +2876 2222222222222222222222222222222222222222222222222222222 +2877 2222222222222222222222222222222222222222222222222222222 +2878 2222222222222222222222222222222222222222222222222222222 +2879 2222222222222222222222222222222222222222222222222222222 +2880 2222222222222222222222222222222222222222222222222222222 +2881 2222222222222222222222222222222222222222222222222222222 +2882 2222222222222222222222222222222222222222222222222222222 +2883 2222222222222222222222222222222222222222222222222222222 +2884 2222222222222222222222222222222222222222222222222222222 +2885 2222222222222222222222222222222222222222222222222222222 +2886 2222222222222222222222222222222222222222222222222222222 +2887 2222222222222222222222222222222222222222222222222222222 +2888 2222222222222222222222222222222222222222222222222222222 +2889 2222222222222222222222222222222222222222222222222222222 +2890 2222222222222222222222222222222222222222222222222222222 +2891 2222222222222222222222222222222222222222222222222222222 +2892 2222222222222222222222222222222222222222222222222222222 +2893 2222222222222222222222222222222222222222222222222222222 +2894 2222222222222222222222222222222222222222222222222222222 +2895 2222222222222222222222222222222222222222222222222222222 +2896 2222222222222222222222222222222222222222222222222222222 +2897 2222222222222222222222222222222222222222222222222222222 +2898 2222222222222222222222222222222222222222222222222222222 +2899 2222222222222222222222222222222222222222222222222222222 +2900 2222222222222222222222222222222222222222222222222222222 +2901 2222222222222222222222222222222222222222222222222222222 +2902 2222222222222222222222222222222222222222222222222222222 +2903 2222222222222222222222222222222222222222222222222222222 +2904 2222222222222222222222222222222222222222222222222222222 +2905 2222222222222222222222222222222222222222222222222222222 +2906 2222222222222222222222222222222222222222222222222222222 +2907 2222222222222222222222222222222222222222222222222222222 +2908 2222222222222222222222222222222222222222222222222222222 +2909 2222222222222222222222222222222222222222222222222222222 +2910 2222222222222222222222222222222222222222222222222222222 +2911 2222222222222222222222222222222222222222222222222222222 +2912 2222222222222222222222222222222222222222222222222222222 +2913 2222222222222222222222222222222222222222222222222222222 +2914 2222222222222222222222222222222222222222222222222222222 +2915 2222222222222222222222222222222222222222222222222222222 +2916 2222222222222222222222222222222222222222222222222222222 +2917 2222222222222222222222222222222222222222222222222222222 +2918 2222222222222222222222222222222222222222222222222222222 +2919 2222222222222222222222222222222222222222222222222222222 +2920 2222222222222222222222222222222222222222222222222222222 +2921 2222222222222222222222222222222222222222222222222222222 +2922 2222222222222222222222222222222222222222222222222222222 +2923 2222222222222222222222222222222222222222222222222222222 +2924 2222222222222222222222222222222222222222222222222222222 +2925 2222222222222222222222222222222222222222222222222222222 +2926 2222222222222222222222222222222222222222222222222222222 +2927 2222222222222222222222222222222222222222222222222222222 +2928 2222222222222222222222222222222222222222222222222222222 +2929 2222222222222222222222222222222222222222222222222222222 +2930 2222222222222222222222222222222222222222222222222222222 +2931 2222222222222222222222222222222222222222222222222222222 +2932 2222222222222222222222222222222222222222222222222222222 +2933 2222222222222222222222222222222222222222222222222222222 +2934 2222222222222222222222222222222222222222222222222222222 +2935 2222222222222222222222222222222222222222222222222222222 +2936 2222222222222222222222222222222222222222222222222222222 +2937 2222222222222222222222222222222222222222222222222222222 +2938 2222222222222222222222222222222222222222222222222222222 +2939 2222222222222222222222222222222222222222222222222222222 +2940 2222222222222222222222222222222222222222222222222222222 +2941 2222222222222222222222222222222222222222222222222222222 +2942 2222222222222222222222222222222222222222222222222222222 +2943 2222222222222222222222222222222222222222222222222222222 +2944 2222222222222222222222222222222222222222222222222222222 +2945 2222222222222222222222222222222222222222222222222222222 +2946 2222222222222222222222222222222222222222222222222222222 +2947 2222222222222222222222222222222222222222222222222222222 +2948 2222222222222222222222222222222222222222222222222222222 +2949 2222222222222222222222222222222222222222222222222222222 +2950 2222222222222222222222222222222222222222222222222222222 +2951 2222222222222222222222222222222222222222222222222222222 +2952 2222222222222222222222222222222222222222222222222222222 +2953 2222222222222222222222222222222222222222222222222222222 +2954 2222222222222222222222222222222222222222222222222222222 +2955 2222222222222222222222222222222222222222222222222222222 +2956 2222222222222222222222222222222222222222222222222222222 +2957 2222222222222222222222222222222222222222222222222222222 +2958 2222222222222222222222222222222222222222222222222222222 +2959 2222222222222222222222222222222222222222222222222222222 +2960 2222222222222222222222222222222222222222222222222222222 +2961 2222222222222222222222222222222222222222222222222222222 +2962 2222222222222222222222222222222222222222222222222222222 +2963 2222222222222222222222222222222222222222222222222222222 +2964 2222222222222222222222222222222222222222222222222222222 +2965 2222222222222222222222222222222222222222222222222222222 +2966 2222222222222222222222222222222222222222222222222222222 +2967 2222222222222222222222222222222222222222222222222222222 +2968 2222222222222222222222222222222222222222222222222222222 +2969 2222222222222222222222222222222222222222222222222222222 +2970 2222222222222222222222222222222222222222222222222222222 +2971 2222222222222222222222222222222222222222222222222222222 +2972 2222222222222222222222222222222222222222222222222222222 +2973 2222222222222222222222222222222222222222222222222222222 +2974 2222222222222222222222222222222222222222222222222222222 +2975 2222222222222222222222222222222222222222222222222222222 +2976 2222222222222222222222222222222222222222222222222222222 +2977 2222222222222222222222222222222222222222222222222222222 +2978 2222222222222222222222222222222222222222222222222222222 +2979 2222222222222222222222222222222222222222222222222222222 +2980 2222222222222222222222222222222222222222222222222222222 +2981 2222222222222222222222222222222222222222222222222222222 +2982 2222222222222222222222222222222222222222222222222222222 +2983 2222222222222222222222222222222222222222222222222222222 +2984 2222222222222222222222222222222222222222222222222222222 +2985 2222222222222222222222222222222222222222222222222222222 +2986 2222222222222222222222222222222222222222222222222222222 +2987 2222222222222222222222222222222222222222222222222222222 +2988 2222222222222222222222222222222222222222222222222222222 +2989 2222222222222222222222222222222222222222222222222222222 +2990 2222222222222222222222222222222222222222222222222222222 +2991 2222222222222222222222222222222222222222222222222222222 +2992 2222222222222222222222222222222222222222222222222222222 +2993 2222222222222222222222222222222222222222222222222222222 +2994 2222222222222222222222222222222222222222222222222222222 +2995 2222222222222222222222222222222222222222222222222222222 +2996 2222222222222222222222222222222222222222222222222222222 +2997 2222222222222222222222222222222222222222222222222222222 +2998 2222222222222222222222222222222222222222222222222222222 +2999 2222222222222222222222222222222222222222222222222222222 +3000 2222222222222222222222222222222222222222222222222222222 +3001 2222222222222222222222222222222222222222222222222222222 +3002 2222222222222222222222222222222222222222222222222222222 +3003 2222222222222222222222222222222222222222222222222222222 +3004 2222222222222222222222222222222222222222222222222222222 +3005 2222222222222222222222222222222222222222222222222222222 +3006 2222222222222222222222222222222222222222222222222222222 +3007 2222222222222222222222222222222222222222222222222222222 +3008 2222222222222222222222222222222222222222222222222222222 +3009 2222222222222222222222222222222222222222222222222222222 +3010 2222222222222222222222222222222222222222222222222222222 +3011 2222222222222222222222222222222222222222222222222222222 +3012 2222222222222222222222222222222222222222222222222222222 +3013 2222222222222222222222222222222222222222222222222222222 +3014 2222222222222222222222222222222222222222222222222222222 +3015 2222222222222222222222222222222222222222222222222222222 +3016 2222222222222222222222222222222222222222222222222222222 +3017 2222222222222222222222222222222222222222222222222222222 +3018 2222222222222222222222222222222222222222222222222222222 +3019 2222222222222222222222222222222222222222222222222222222 +3020 2222222222222222222222222222222222222222222222222222222 +3021 2222222222222222222222222222222222222222222222222222222 +3022 2222222222222222222222222222222222222222222222222222222 +3023 2222222222222222222222222222222222222222222222222222222 +3024 2222222222222222222222222222222222222222222222222222222 +3025 2222222222222222222222222222222222222222222222222222222 +3026 2222222222222222222222222222222222222222222222222222222 +3027 2222222222222222222222222222222222222222222222222222222 +3028 2222222222222222222222222222222222222222222222222222222 +3029 2222222222222222222222222222222222222222222222222222222 +3030 2222222222222222222222222222222222222222222222222222222 +3031 2222222222222222222222222222222222222222222222222222222 +3032 2222222222222222222222222222222222222222222222222222222 +3033 2222222222222222222222222222222222222222222222222222222 +3034 2222222222222222222222222222222222222222222222222222222 +3035 2222222222222222222222222222222222222222222222222222222 +3036 2222222222222222222222222222222222222222222222222222222 +3037 2222222222222222222222222222222222222222222222222222222 +3038 2222222222222222222222222222222222222222222222222222222 +3039 2222222222222222222222222222222222222222222222222222222 +3040 2222222222222222222222222222222222222222222222222222222 +3041 2222222222222222222222222222222222222222222222222222222 +3042 2222222222222222222222222222222222222222222222222222222 +3043 2222222222222222222222222222222222222222222222222222222 +3044 2222222222222222222222222222222222222222222222222222222 +3045 2222222222222222222222222222222222222222222222222222222 +3046 2222222222222222222222222222222222222222222222222222222 +3047 2222222222222222222222222222222222222222222222222222222 +3048 2222222222222222222222222222222222222222222222222222222 +3049 2222222222222222222222222222222222222222222222222222222 +3050 2222222222222222222222222222222222222222222222222222222 +3051 2222222222222222222222222222222222222222222222222222222 +3052 2222222222222222222222222222222222222222222222222222222 +3053 2222222222222222222222222222222222222222222222222222222 +3054 2222222222222222222222222222222222222222222222222222222 +3055 2222222222222222222222222222222222222222222222222222222 +3056 2222222222222222222222222222222222222222222222222222222 +3057 2222222222222222222222222222222222222222222222222222222 +3058 2222222222222222222222222222222222222222222222222222222 +3059 2222222222222222222222222222222222222222222222222222222 +3060 2222222222222222222222222222222222222222222222222222222 +3061 2222222222222222222222222222222222222222222222222222222 +3062 2222222222222222222222222222222222222222222222222222222 +3063 2222222222222222222222222222222222222222222222222222222 +3064 2222222222222222222222222222222222222222222222222222222 +3065 2222222222222222222222222222222222222222222222222222222 +3066 2222222222222222222222222222222222222222222222222222222 +3067 2222222222222222222222222222222222222222222222222222222 +3068 2222222222222222222222222222222222222222222222222222222 +3069 2222222222222222222222222222222222222222222222222222222 +3070 2222222222222222222222222222222222222222222222222222222 +3071 2222222222222222222222222222222222222222222222222222222 +3072 2222222222222222222222222222222222222222222222222222222 +3073 2222222222222222222222222222222222222222222222222222222 +3074 2222222222222222222222222222222222222222222222222222222 +3075 2222222222222222222222222222222222222222222222222222222 +3076 2222222222222222222222222222222222222222222222222222222 +3077 2222222222222222222222222222222222222222222222222222222 +3078 2222222222222222222222222222222222222222222222222222222 +3079 2222222222222222222222222222222222222222222222222222222 +3080 2222222222222222222222222222222222222222222222222222222 +3081 2222222222222222222222222222222222222222222222222222222 +3082 2222222222222222222222222222222222222222222222222222222 +3083 2222222222222222222222222222222222222222222222222222222 +3084 2222222222222222222222222222222222222222222222222222222 +3085 2222222222222222222222222222222222222222222222222222222 +3086 2222222222222222222222222222222222222222222222222222222 +3087 2222222222222222222222222222222222222222222222222222222 +3088 2222222222222222222222222222222222222222222222222222222 +3089 2222222222222222222222222222222222222222222222222222222 +3090 2222222222222222222222222222222222222222222222222222222 +3091 2222222222222222222222222222222222222222222222222222222 +3092 2222222222222222222222222222222222222222222222222222222 +3093 2222222222222222222222222222222222222222222222222222222 +3094 2222222222222222222222222222222222222222222222222222222 +3095 2222222222222222222222222222222222222222222222222222222 +3096 2222222222222222222222222222222222222222222222222222222 +3097 2222222222222222222222222222222222222222222222222222222 +3098 2222222222222222222222222222222222222222222222222222222 +3099 2222222222222222222222222222222222222222222222222222222 +3100 2222222222222222222222222222222222222222222222222222222 +3101 2222222222222222222222222222222222222222222222222222222 +3102 2222222222222222222222222222222222222222222222222222222 +3103 2222222222222222222222222222222222222222222222222222222 +3104 2222222222222222222222222222222222222222222222222222222 +3105 2222222222222222222222222222222222222222222222222222222 +3106 2222222222222222222222222222222222222222222222222222222 +3107 2222222222222222222222222222222222222222222222222222222 +3108 2222222222222222222222222222222222222222222222222222222 +3109 2222222222222222222222222222222222222222222222222222222 +3110 2222222222222222222222222222222222222222222222222222222 +3111 2222222222222222222222222222222222222222222222222222222 +3112 2222222222222222222222222222222222222222222222222222222 +3113 2222222222222222222222222222222222222222222222222222222 +3114 2222222222222222222222222222222222222222222222222222222 +3115 2222222222222222222222222222222222222222222222222222222 +3116 2222222222222222222222222222222222222222222222222222222 +3117 2222222222222222222222222222222222222222222222222222222 +3118 2222222222222222222222222222222222222222222222222222222 +3119 2222222222222222222222222222222222222222222222222222222 +3120 2222222222222222222222222222222222222222222222222222222 +3121 2222222222222222222222222222222222222222222222222222222 +3122 2222222222222222222222222222222222222222222222222222222 +3123 2222222222222222222222222222222222222222222222222222222 +3124 2222222222222222222222222222222222222222222222222222222 +3125 2222222222222222222222222222222222222222222222222222222 +3126 2222222222222222222222222222222222222222222222222222222 +3127 2222222222222222222222222222222222222222222222222222222 +3128 2222222222222222222222222222222222222222222222222222222 +3129 2222222222222222222222222222222222222222222222222222222 +3130 2222222222222222222222222222222222222222222222222222222 +3131 2222222222222222222222222222222222222222222222222222222 +3132 2222222222222222222222222222222222222222222222222222222 +3133 2222222222222222222222222222222222222222222222222222222 +3134 2222222222222222222222222222222222222222222222222222222 +3135 2222222222222222222222222222222222222222222222222222222 +3136 2222222222222222222222222222222222222222222222222222222 +3137 2222222222222222222222222222222222222222222222222222222 +3138 2222222222222222222222222222222222222222222222222222222 +3139 2222222222222222222222222222222222222222222222222222222 +3140 2222222222222222222222222222222222222222222222222222222 +3141 2222222222222222222222222222222222222222222222222222222 +3142 2222222222222222222222222222222222222222222222222222222 +3143 2222222222222222222222222222222222222222222222222222222 +3144 2222222222222222222222222222222222222222222222222222222 +3145 2222222222222222222222222222222222222222222222222222222 +3146 2222222222222222222222222222222222222222222222222222222 +3147 2222222222222222222222222222222222222222222222222222222 +3148 2222222222222222222222222222222222222222222222222222222 +3149 2222222222222222222222222222222222222222222222222222222 +3150 2222222222222222222222222222222222222222222222222222222 +3151 2222222222222222222222222222222222222222222222222222222 +3152 2222222222222222222222222222222222222222222222222222222 +3153 2222222222222222222222222222222222222222222222222222222 +3154 2222222222222222222222222222222222222222222222222222222 +3155 2222222222222222222222222222222222222222222222222222222 +3156 2222222222222222222222222222222222222222222222222222222 +3157 2222222222222222222222222222222222222222222222222222222 +3158 2222222222222222222222222222222222222222222222222222222 +3159 2222222222222222222222222222222222222222222222222222222 +3160 2222222222222222222222222222222222222222222222222222222 +3161 2222222222222222222222222222222222222222222222222222222 +3162 2222222222222222222222222222222222222222222222222222222 +3163 2222222222222222222222222222222222222222222222222222222 +3164 2222222222222222222222222222222222222222222222222222222 +3165 2222222222222222222222222222222222222222222222222222222 +3166 2222222222222222222222222222222222222222222222222222222 +3167 2222222222222222222222222222222222222222222222222222222 +3168 2222222222222222222222222222222222222222222222222222222 +3169 2222222222222222222222222222222222222222222222222222222 +3170 2222222222222222222222222222222222222222222222222222222 +3171 2222222222222222222222222222222222222222222222222222222 +3172 2222222222222222222222222222222222222222222222222222222 +3173 2222222222222222222222222222222222222222222222222222222 +3174 2222222222222222222222222222222222222222222222222222222 +3175 2222222222222222222222222222222222222222222222222222222 +3176 2222222222222222222222222222222222222222222222222222222 +3177 2222222222222222222222222222222222222222222222222222222 +3178 2222222222222222222222222222222222222222222222222222222 +3179 2222222222222222222222222222222222222222222222222222222 +3180 2222222222222222222222222222222222222222222222222222222 +3181 2222222222222222222222222222222222222222222222222222222 +3182 2222222222222222222222222222222222222222222222222222222 +3183 2222222222222222222222222222222222222222222222222222222 +3184 2222222222222222222222222222222222222222222222222222222 +3185 2222222222222222222222222222222222222222222222222222222 +3186 2222222222222222222222222222222222222222222222222222222 +3187 2222222222222222222222222222222222222222222222222222222 +3188 2222222222222222222222222222222222222222222222222222222 +3189 2222222222222222222222222222222222222222222222222222222 +3190 2222222222222222222222222222222222222222222222222222222 +3191 2222222222222222222222222222222222222222222222222222222 +3192 2222222222222222222222222222222222222222222222222222222 +3193 2222222222222222222222222222222222222222222222222222222 +3194 2222222222222222222222222222222222222222222222222222222 +3195 2222222222222222222222222222222222222222222222222222222 +3196 2222222222222222222222222222222222222222222222222222222 +3197 2222222222222222222222222222222222222222222222222222222 +3198 2222222222222222222222222222222222222222222222222222222 +3199 2222222222222222222222222222222222222222222222222222222 +3200 2222222222222222222222222222222222222222222222222222222 +3201 2222222222222222222222222222222222222222222222222222222 +3202 2222222222222222222222222222222222222222222222222222222 +3203 2222222222222222222222222222222222222222222222222222222 +3204 2222222222222222222222222222222222222222222222222222222 +3205 2222222222222222222222222222222222222222222222222222222 +3206 2222222222222222222222222222222222222222222222222222222 +3207 2222222222222222222222222222222222222222222222222222222 +3208 2222222222222222222222222222222222222222222222222222222 +3209 2222222222222222222222222222222222222222222222222222222 +3210 2222222222222222222222222222222222222222222222222222222 +3211 2222222222222222222222222222222222222222222222222222222 +3212 2222222222222222222222222222222222222222222222222222222 +3213 2222222222222222222222222222222222222222222222222222222 +3214 2222222222222222222222222222222222222222222222222222222 +3215 2222222222222222222222222222222222222222222222222222222 +3216 2222222222222222222222222222222222222222222222222222222 +3217 2222222222222222222222222222222222222222222222222222222 +3218 2222222222222222222222222222222222222222222222222222222 +3219 2222222222222222222222222222222222222222222222222222222 +3220 2222222222222222222222222222222222222222222222222222222 +3221 2222222222222222222222222222222222222222222222222222222 +3222 2222222222222222222222222222222222222222222222222222222 +3223 2222222222222222222222222222222222222222222222222222222 +3224 2222222222222222222222222222222222222222222222222222222 +3225 2222222222222222222222222222222222222222222222222222222 +3226 2222222222222222222222222222222222222222222222222222222 +3227 2222222222222222222222222222222222222222222222222222222 +3228 2222222222222222222222222222222222222222222222222222222 +3229 2222222222222222222222222222222222222222222222222222222 +3230 2222222222222222222222222222222222222222222222222222222 +3231 2222222222222222222222222222222222222222222222222222222 +3232 2222222222222222222222222222222222222222222222222222222 +3233 2222222222222222222222222222222222222222222222222222222 +3234 2222222222222222222222222222222222222222222222222222222 +3235 2222222222222222222222222222222222222222222222222222222 +3236 2222222222222222222222222222222222222222222222222222222 +3237 2222222222222222222222222222222222222222222222222222222 +3238 2222222222222222222222222222222222222222222222222222222 +3239 2222222222222222222222222222222222222222222222222222222 +3240 2222222222222222222222222222222222222222222222222222222 +3241 2222222222222222222222222222222222222222222222222222222 +3242 2222222222222222222222222222222222222222222222222222222 +3243 2222222222222222222222222222222222222222222222222222222 +3244 2222222222222222222222222222222222222222222222222222222 +3245 2222222222222222222222222222222222222222222222222222222 +3246 2222222222222222222222222222222222222222222222222222222 +3247 2222222222222222222222222222222222222222222222222222222 +3248 2222222222222222222222222222222222222222222222222222222 +3249 2222222222222222222222222222222222222222222222222222222 +3250 2222222222222222222222222222222222222222222222222222222 +3251 2222222222222222222222222222222222222222222222222222222 +3252 2222222222222222222222222222222222222222222222222222222 +3253 2222222222222222222222222222222222222222222222222222222 +3254 2222222222222222222222222222222222222222222222222222222 +3255 2222222222222222222222222222222222222222222222222222222 +3256 2222222222222222222222222222222222222222222222222222222 +3257 2222222222222222222222222222222222222222222222222222222 +3258 2222222222222222222222222222222222222222222222222222222 +3259 2222222222222222222222222222222222222222222222222222222 +3260 2222222222222222222222222222222222222222222222222222222 +3261 2222222222222222222222222222222222222222222222222222222 +3262 2222222222222222222222222222222222222222222222222222222 +3263 2222222222222222222222222222222222222222222222222222222 +3264 2222222222222222222222222222222222222222222222222222222 +3265 2222222222222222222222222222222222222222222222222222222 +3266 2222222222222222222222222222222222222222222222222222222 +3267 2222222222222222222222222222222222222222222222222222222 +3268 2222222222222222222222222222222222222222222222222222222 +3269 2222222222222222222222222222222222222222222222222222222 +3270 2222222222222222222222222222222222222222222222222222222 +3271 2222222222222222222222222222222222222222222222222222222 +3272 2222222222222222222222222222222222222222222222222222222 +3273 2222222222222222222222222222222222222222222222222222222 +3274 2222222222222222222222222222222222222222222222222222222 +3275 2222222222222222222222222222222222222222222222222222222 +3276 2222222222222222222222222222222222222222222222222222222 +3277 2222222222222222222222222222222222222222222222222222222 +3278 2222222222222222222222222222222222222222222222222222222 +3279 2222222222222222222222222222222222222222222222222222222 +3280 2222222222222222222222222222222222222222222222222222222 +3281 2222222222222222222222222222222222222222222222222222222 +3282 2222222222222222222222222222222222222222222222222222222 +3283 2222222222222222222222222222222222222222222222222222222 +3284 2222222222222222222222222222222222222222222222222222222 +3285 2222222222222222222222222222222222222222222222222222222 +3286 2222222222222222222222222222222222222222222222222222222 +3287 2222222222222222222222222222222222222222222222222222222 +3288 2222222222222222222222222222222222222222222222222222222 +3289 2222222222222222222222222222222222222222222222222222222 +3290 2222222222222222222222222222222222222222222222222222222 +3291 2222222222222222222222222222222222222222222222222222222 +3292 2222222222222222222222222222222222222222222222222222222 +3293 2222222222222222222222222222222222222222222222222222222 +3294 2222222222222222222222222222222222222222222222222222222 +3295 2222222222222222222222222222222222222222222222222222222 +3296 2222222222222222222222222222222222222222222222222222222 +3297 2222222222222222222222222222222222222222222222222222222 +3298 2222222222222222222222222222222222222222222222222222222 +3299 2222222222222222222222222222222222222222222222222222222 +3300 2222222222222222222222222222222222222222222222222222222 +3301 2222222222222222222222222222222222222222222222222222222 +3302 2222222222222222222222222222222222222222222222222222222 +3303 2222222222222222222222222222222222222222222222222222222 +3304 2222222222222222222222222222222222222222222222222222222 +3305 2222222222222222222222222222222222222222222222222222222 +3306 2222222222222222222222222222222222222222222222222222222 +3307 2222222222222222222222222222222222222222222222222222222 +3308 2222222222222222222222222222222222222222222222222222222 +3309 2222222222222222222222222222222222222222222222222222222 +3310 2222222222222222222222222222222222222222222222222222222 +3311 2222222222222222222222222222222222222222222222222222222 +3312 2222222222222222222222222222222222222222222222222222222 +3313 2222222222222222222222222222222222222222222222222222222 +3314 2222222222222222222222222222222222222222222222222222222 +3315 2222222222222222222222222222222222222222222222222222222 +3316 2222222222222222222222222222222222222222222222222222222 +3317 2222222222222222222222222222222222222222222222222222222 +3318 2222222222222222222222222222222222222222222222222222222 +3319 2222222222222222222222222222222222222222222222222222222 +3320 2222222222222222222222222222222222222222222222222222222 +3321 2222222222222222222222222222222222222222222222222222222 +3322 2222222222222222222222222222222222222222222222222222222 +3323 2222222222222222222222222222222222222222222222222222222 +3324 2222222222222222222222222222222222222222222222222222222 +3325 2222222222222222222222222222222222222222222222222222222 +3326 2222222222222222222222222222222222222222222222222222222 +3327 2222222222222222222222222222222222222222222222222222222 +3328 2222222222222222222222222222222222222222222222222222222 +3329 2222222222222222222222222222222222222222222222222222222 +3330 2222222222222222222222222222222222222222222222222222222 +3331 2222222222222222222222222222222222222222222222222222222 +3332 2222222222222222222222222222222222222222222222222222222 +3333 2222222222222222222222222222222222222222222222222222222 +3334 2222222222222222222222222222222222222222222222222222222 +3335 2222222222222222222222222222222222222222222222222222222 +3336 2222222222222222222222222222222222222222222222222222222 +3337 2222222222222222222222222222222222222222222222222222222 +3338 2222222222222222222222222222222222222222222222222222222 +3339 2222222222222222222222222222222222222222222222222222222 +3340 2222222222222222222222222222222222222222222222222222222 +3341 2222222222222222222222222222222222222222222222222222222 +3342 2222222222222222222222222222222222222222222222222222222 +3343 2222222222222222222222222222222222222222222222222222222 +3344 2222222222222222222222222222222222222222222222222222222 +3345 2222222222222222222222222222222222222222222222222222222 +3346 2222222222222222222222222222222222222222222222222222222 +3347 2222222222222222222222222222222222222222222222222222222 +3348 2222222222222222222222222222222222222222222222222222222 +3349 2222222222222222222222222222222222222222222222222222222 +3350 2222222222222222222222222222222222222222222222222222222 +3351 2222222222222222222222222222222222222222222222222222222 +3352 2222222222222222222222222222222222222222222222222222222 +3353 2222222222222222222222222222222222222222222222222222222 +3354 2222222222222222222222222222222222222222222222222222222 +3355 2222222222222222222222222222222222222222222222222222222 +3356 2222222222222222222222222222222222222222222222222222222 +3357 2222222222222222222222222222222222222222222222222222222 +3358 2222222222222222222222222222222222222222222222222222222 +3359 2222222222222222222222222222222222222222222222222222222 +3360 2222222222222222222222222222222222222222222222222222222 +3361 2222222222222222222222222222222222222222222222222222222 +3362 2222222222222222222222222222222222222222222222222222222 +3363 2222222222222222222222222222222222222222222222222222222 +3364 2222222222222222222222222222222222222222222222222222222 +3365 2222222222222222222222222222222222222222222222222222222 +3366 2222222222222222222222222222222222222222222222222222222 +3367 2222222222222222222222222222222222222222222222222222222 +3368 2222222222222222222222222222222222222222222222222222222 +3369 2222222222222222222222222222222222222222222222222222222 +3370 2222222222222222222222222222222222222222222222222222222 +3371 2222222222222222222222222222222222222222222222222222222 +3372 2222222222222222222222222222222222222222222222222222222 +3373 2222222222222222222222222222222222222222222222222222222 +3374 2222222222222222222222222222222222222222222222222222222 +3375 2222222222222222222222222222222222222222222222222222222 +3376 2222222222222222222222222222222222222222222222222222222 +3377 2222222222222222222222222222222222222222222222222222222 +3378 2222222222222222222222222222222222222222222222222222222 +3379 2222222222222222222222222222222222222222222222222222222 +3380 2222222222222222222222222222222222222222222222222222222 +3381 2222222222222222222222222222222222222222222222222222222 +3382 2222222222222222222222222222222222222222222222222222222 +3383 2222222222222222222222222222222222222222222222222222222 +3384 2222222222222222222222222222222222222222222222222222222 +3385 2222222222222222222222222222222222222222222222222222222 +3386 2222222222222222222222222222222222222222222222222222222 +3387 2222222222222222222222222222222222222222222222222222222 +3388 2222222222222222222222222222222222222222222222222222222 +3389 2222222222222222222222222222222222222222222222222222222 +3390 2222222222222222222222222222222222222222222222222222222 +3391 2222222222222222222222222222222222222222222222222222222 +3392 2222222222222222222222222222222222222222222222222222222 +3393 2222222222222222222222222222222222222222222222222222222 +3394 2222222222222222222222222222222222222222222222222222222 +3395 2222222222222222222222222222222222222222222222222222222 +3396 2222222222222222222222222222222222222222222222222222222 +3397 2222222222222222222222222222222222222222222222222222222 +3398 2222222222222222222222222222222222222222222222222222222 +3399 2222222222222222222222222222222222222222222222222222222 +3400 2222222222222222222222222222222222222222222222222222222 +3401 2222222222222222222222222222222222222222222222222222222 +3402 2222222222222222222222222222222222222222222222222222222 +3403 2222222222222222222222222222222222222222222222222222222 +3404 2222222222222222222222222222222222222222222222222222222 +3405 2222222222222222222222222222222222222222222222222222222 +3406 2222222222222222222222222222222222222222222222222222222 +3407 2222222222222222222222222222222222222222222222222222222 +3408 2222222222222222222222222222222222222222222222222222222 +3409 2222222222222222222222222222222222222222222222222222222 +3410 2222222222222222222222222222222222222222222222222222222 +3411 2222222222222222222222222222222222222222222222222222222 +3412 2222222222222222222222222222222222222222222222222222222 +3413 2222222222222222222222222222222222222222222222222222222 +3414 2222222222222222222222222222222222222222222222222222222 +3415 2222222222222222222222222222222222222222222222222222222 +3416 2222222222222222222222222222222222222222222222222222222 +3417 2222222222222222222222222222222222222222222222222222222 +3418 2222222222222222222222222222222222222222222222222222222 +3419 2222222222222222222222222222222222222222222222222222222 +3420 2222222222222222222222222222222222222222222222222222222 +3421 2222222222222222222222222222222222222222222222222222222 +3422 2222222222222222222222222222222222222222222222222222222 +3423 2222222222222222222222222222222222222222222222222222222 +3424 2222222222222222222222222222222222222222222222222222222 +3425 2222222222222222222222222222222222222222222222222222222 +3426 2222222222222222222222222222222222222222222222222222222 +3427 2222222222222222222222222222222222222222222222222222222 +3428 2222222222222222222222222222222222222222222222222222222 +3429 2222222222222222222222222222222222222222222222222222222 +3430 2222222222222222222222222222222222222222222222222222222 +3431 2222222222222222222222222222222222222222222222222222222 +3432 2222222222222222222222222222222222222222222222222222222 +3433 2222222222222222222222222222222222222222222222222222222 +3434 2222222222222222222222222222222222222222222222222222222 +3435 2222222222222222222222222222222222222222222222222222222 +3436 2222222222222222222222222222222222222222222222222222222 +3437 2222222222222222222222222222222222222222222222222222222 +3438 2222222222222222222222222222222222222222222222222222222 +3439 2222222222222222222222222222222222222222222222222222222 +3440 2222222222222222222222222222222222222222222222222222222 +3441 2222222222222222222222222222222222222222222222222222222 +3442 2222222222222222222222222222222222222222222222222222222 +3443 2222222222222222222222222222222222222222222222222222222 +3444 2222222222222222222222222222222222222222222222222222222 +3445 2222222222222222222222222222222222222222222222222222222 +3446 2222222222222222222222222222222222222222222222222222222 +3447 2222222222222222222222222222222222222222222222222222222 +3448 2222222222222222222222222222222222222222222222222222222 +3449 2222222222222222222222222222222222222222222222222222222 +3450 2222222222222222222222222222222222222222222222222222222 +3451 2222222222222222222222222222222222222222222222222222222 +3452 2222222222222222222222222222222222222222222222222222222 +3453 2222222222222222222222222222222222222222222222222222222 +3454 2222222222222222222222222222222222222222222222222222222 +3455 2222222222222222222222222222222222222222222222222222222 +3456 2222222222222222222222222222222222222222222222222222222 +3457 2222222222222222222222222222222222222222222222222222222 +3458 2222222222222222222222222222222222222222222222222222222 +3459 2222222222222222222222222222222222222222222222222222222 +3460 2222222222222222222222222222222222222222222222222222222 +3461 2222222222222222222222222222222222222222222222222222222 +3462 2222222222222222222222222222222222222222222222222222222 +3463 2222222222222222222222222222222222222222222222222222222 +3464 2222222222222222222222222222222222222222222222222222222 +3465 2222222222222222222222222222222222222222222222222222222 +3466 2222222222222222222222222222222222222222222222222222222 +3467 2222222222222222222222222222222222222222222222222222222 +3468 2222222222222222222222222222222222222222222222222222222 +3469 2222222222222222222222222222222222222222222222222222222 +3470 2222222222222222222222222222222222222222222222222222222 +3471 2222222222222222222222222222222222222222222222222222222 +3472 2222222222222222222222222222222222222222222222222222222 +3473 2222222222222222222222222222222222222222222222222222222 +3474 2222222222222222222222222222222222222222222222222222222 +3475 2222222222222222222222222222222222222222222222222222222 +3476 2222222222222222222222222222222222222222222222222222222 +3477 2222222222222222222222222222222222222222222222222222222 +3478 2222222222222222222222222222222222222222222222222222222 +3479 2222222222222222222222222222222222222222222222222222222 +3480 2222222222222222222222222222222222222222222222222222222 +3481 2222222222222222222222222222222222222222222222222222222 +3482 2222222222222222222222222222222222222222222222222222222 +3483 2222222222222222222222222222222222222222222222222222222 +3484 2222222222222222222222222222222222222222222222222222222 +3485 2222222222222222222222222222222222222222222222222222222 +3486 2222222222222222222222222222222222222222222222222222222 +3487 2222222222222222222222222222222222222222222222222222222 +3488 2222222222222222222222222222222222222222222222222222222 +3489 2222222222222222222222222222222222222222222222222222222 +3490 2222222222222222222222222222222222222222222222222222222 +3491 2222222222222222222222222222222222222222222222222222222 +3492 2222222222222222222222222222222222222222222222222222222 +3493 2222222222222222222222222222222222222222222222222222222 +3494 2222222222222222222222222222222222222222222222222222222 +3495 2222222222222222222222222222222222222222222222222222222 +3496 2222222222222222222222222222222222222222222222222222222 +3497 2222222222222222222222222222222222222222222222222222222 +3498 2222222222222222222222222222222222222222222222222222222 +3499 2222222222222222222222222222222222222222222222222222222 +3500 2222222222222222222222222222222222222222222222222222222 +3501 2222222222222222222222222222222222222222222222222222222 +3502 2222222222222222222222222222222222222222222222222222222 +3503 2222222222222222222222222222222222222222222222222222222 +3504 2222222222222222222222222222222222222222222222222222222 +3505 2222222222222222222222222222222222222222222222222222222 +3506 2222222222222222222222222222222222222222222222222222222 +3507 2222222222222222222222222222222222222222222222222222222 +3508 2222222222222222222222222222222222222222222222222222222 +3509 2222222222222222222222222222222222222222222222222222222 +3510 2222222222222222222222222222222222222222222222222222222 +3511 2222222222222222222222222222222222222222222222222222222 +3512 2222222222222222222222222222222222222222222222222222222 +3513 2222222222222222222222222222222222222222222222222222222 +3514 2222222222222222222222222222222222222222222222222222222 +3515 2222222222222222222222222222222222222222222222222222222 +3516 2222222222222222222222222222222222222222222222222222222 +3517 2222222222222222222222222222222222222222222222222222222 +3518 2222222222222222222222222222222222222222222222222222222 +3519 2222222222222222222222222222222222222222222222222222222 +3520 2222222222222222222222222222222222222222222222222222222 +3521 2222222222222222222222222222222222222222222222222222222 +3522 2222222222222222222222222222222222222222222222222222222 +3523 2222222222222222222222222222222222222222222222222222222 +3524 2222222222222222222222222222222222222222222222222222222 +3525 2222222222222222222222222222222222222222222222222222222 +3526 2222222222222222222222222222222222222222222222222222222 +3527 2222222222222222222222222222222222222222222222222222222 +3528 2222222222222222222222222222222222222222222222222222222 +3529 2222222222222222222222222222222222222222222222222222222 +3530 2222222222222222222222222222222222222222222222222222222 +3531 2222222222222222222222222222222222222222222222222222222 +3532 2222222222222222222222222222222222222222222222222222222 +3533 2222222222222222222222222222222222222222222222222222222 +3534 2222222222222222222222222222222222222222222222222222222 +3535 2222222222222222222222222222222222222222222222222222222 +3536 2222222222222222222222222222222222222222222222222222222 +3537 2222222222222222222222222222222222222222222222222222222 +3538 2222222222222222222222222222222222222222222222222222222 +3539 2222222222222222222222222222222222222222222222222222222 +3540 2222222222222222222222222222222222222222222222222222222 +3541 2222222222222222222222222222222222222222222222222222222 +3542 2222222222222222222222222222222222222222222222222222222 +3543 2222222222222222222222222222222222222222222222222222222 +3544 2222222222222222222222222222222222222222222222222222222 +3545 2222222222222222222222222222222222222222222222222222222 +3546 2222222222222222222222222222222222222222222222222222222 +3547 2222222222222222222222222222222222222222222222222222222 +3548 2222222222222222222222222222222222222222222222222222222 +3549 2222222222222222222222222222222222222222222222222222222 +3550 2222222222222222222222222222222222222222222222222222222 +3551 2222222222222222222222222222222222222222222222222222222 +3552 2222222222222222222222222222222222222222222222222222222 +3553 2222222222222222222222222222222222222222222222222222222 +3554 2222222222222222222222222222222222222222222222222222222 +3555 2222222222222222222222222222222222222222222222222222222 +3556 2222222222222222222222222222222222222222222222222222222 +3557 2222222222222222222222222222222222222222222222222222222 +3558 2222222222222222222222222222222222222222222222222222222 +3559 2222222222222222222222222222222222222222222222222222222 +3560 2222222222222222222222222222222222222222222222222222222 +3561 2222222222222222222222222222222222222222222222222222222 +3562 2222222222222222222222222222222222222222222222222222222 +3563 2222222222222222222222222222222222222222222222222222222 +3564 2222222222222222222222222222222222222222222222222222222 +3565 2222222222222222222222222222222222222222222222222222222 +3566 2222222222222222222222222222222222222222222222222222222 +3567 2222222222222222222222222222222222222222222222222222222 +3568 2222222222222222222222222222222222222222222222222222222 +3569 2222222222222222222222222222222222222222222222222222222 +3570 2222222222222222222222222222222222222222222222222222222 +3571 2222222222222222222222222222222222222222222222222222222 +3572 2222222222222222222222222222222222222222222222222222222 +3573 2222222222222222222222222222222222222222222222222222222 +3574 2222222222222222222222222222222222222222222222222222222 +3575 2222222222222222222222222222222222222222222222222222222 +3576 2222222222222222222222222222222222222222222222222222222 +3577 2222222222222222222222222222222222222222222222222222222 +3578 2222222222222222222222222222222222222222222222222222222 +3579 2222222222222222222222222222222222222222222222222222222 +3580 2222222222222222222222222222222222222222222222222222222 +3581 2222222222222222222222222222222222222222222222222222222 +3582 2222222222222222222222222222222222222222222222222222222 +3583 2222222222222222222222222222222222222222222222222222222 +3584 2222222222222222222222222222222222222222222222222222222 +3585 2222222222222222222222222222222222222222222222222222222 +3586 2222222222222222222222222222222222222222222222222222222 +3587 2222222222222222222222222222222222222222222222222222222 +3588 2222222222222222222222222222222222222222222222222222222 +3589 2222222222222222222222222222222222222222222222222222222 +3590 2222222222222222222222222222222222222222222222222222222 +3591 2222222222222222222222222222222222222222222222222222222 +3592 2222222222222222222222222222222222222222222222222222222 +3593 2222222222222222222222222222222222222222222222222222222 +3594 2222222222222222222222222222222222222222222222222222222 +3595 2222222222222222222222222222222222222222222222222222222 +3596 2222222222222222222222222222222222222222222222222222222 +3597 2222222222222222222222222222222222222222222222222222222 +3598 2222222222222222222222222222222222222222222222222222222 +3599 2222222222222222222222222222222222222222222222222222222 +3600 2222222222222222222222222222222222222222222222222222222 +3601 2222222222222222222222222222222222222222222222222222222 +3602 2222222222222222222222222222222222222222222222222222222 +3603 2222222222222222222222222222222222222222222222222222222 +3604 2222222222222222222222222222222222222222222222222222222 +3605 2222222222222222222222222222222222222222222222222222222 +3606 2222222222222222222222222222222222222222222222222222222 +3607 2222222222222222222222222222222222222222222222222222222 +3608 2222222222222222222222222222222222222222222222222222222 +3609 2222222222222222222222222222222222222222222222222222222 +3610 2222222222222222222222222222222222222222222222222222222 +3611 2222222222222222222222222222222222222222222222222222222 +3612 2222222222222222222222222222222222222222222222222222222 +3613 2222222222222222222222222222222222222222222222222222222 +3614 2222222222222222222222222222222222222222222222222222222 +3615 2222222222222222222222222222222222222222222222222222222 +3616 2222222222222222222222222222222222222222222222222222222 +3617 2222222222222222222222222222222222222222222222222222222 +3618 2222222222222222222222222222222222222222222222222222222 +3619 2222222222222222222222222222222222222222222222222222222 +3620 2222222222222222222222222222222222222222222222222222222 +3621 2222222222222222222222222222222222222222222222222222222 +3622 2222222222222222222222222222222222222222222222222222222 +3623 2222222222222222222222222222222222222222222222222222222 +3624 2222222222222222222222222222222222222222222222222222222 +3625 2222222222222222222222222222222222222222222222222222222 +3626 2222222222222222222222222222222222222222222222222222222 +3627 2222222222222222222222222222222222222222222222222222222 +3628 2222222222222222222222222222222222222222222222222222222 +3629 2222222222222222222222222222222222222222222222222222222 +3630 2222222222222222222222222222222222222222222222222222222 +3631 2222222222222222222222222222222222222222222222222222222 +3632 2222222222222222222222222222222222222222222222222222222 +3633 2222222222222222222222222222222222222222222222222222222 +3634 2222222222222222222222222222222222222222222222222222222 +3635 2222222222222222222222222222222222222222222222222222222 +3636 2222222222222222222222222222222222222222222222222222222 +3637 2222222222222222222222222222222222222222222222222222222 +3638 2222222222222222222222222222222222222222222222222222222 +3639 2222222222222222222222222222222222222222222222222222222 +3640 2222222222222222222222222222222222222222222222222222222 +3641 2222222222222222222222222222222222222222222222222222222 +3642 2222222222222222222222222222222222222222222222222222222 +3643 2222222222222222222222222222222222222222222222222222222 +3644 2222222222222222222222222222222222222222222222222222222 +3645 2222222222222222222222222222222222222222222222222222222 +3646 2222222222222222222222222222222222222222222222222222222 +3647 2222222222222222222222222222222222222222222222222222222 +3648 2222222222222222222222222222222222222222222222222222222 +3649 2222222222222222222222222222222222222222222222222222222 +3650 2222222222222222222222222222222222222222222222222222222 +3651 2222222222222222222222222222222222222222222222222222222 +3652 2222222222222222222222222222222222222222222222222222222 +3653 2222222222222222222222222222222222222222222222222222222 +3654 2222222222222222222222222222222222222222222222222222222 +3655 2222222222222222222222222222222222222222222222222222222 +3656 2222222222222222222222222222222222222222222222222222222 +3657 2222222222222222222222222222222222222222222222222222222 +3658 2222222222222222222222222222222222222222222222222222222 +3659 2222222222222222222222222222222222222222222222222222222 +3660 2222222222222222222222222222222222222222222222222222222 +3661 2222222222222222222222222222222222222222222222222222222 +3662 2222222222222222222222222222222222222222222222222222222 +3663 2222222222222222222222222222222222222222222222222222222 +3664 2222222222222222222222222222222222222222222222222222222 +3665 2222222222222222222222222222222222222222222222222222222 +3666 2222222222222222222222222222222222222222222222222222222 +3667 2222222222222222222222222222222222222222222222222222222 +3668 2222222222222222222222222222222222222222222222222222222 +3669 2222222222222222222222222222222222222222222222222222222 +3670 2222222222222222222222222222222222222222222222222222222 +3671 2222222222222222222222222222222222222222222222222222222 +3672 2222222222222222222222222222222222222222222222222222222 +3673 2222222222222222222222222222222222222222222222222222222 +3674 2222222222222222222222222222222222222222222222222222222 +3675 2222222222222222222222222222222222222222222222222222222 +3676 2222222222222222222222222222222222222222222222222222222 +3677 2222222222222222222222222222222222222222222222222222222 +3678 2222222222222222222222222222222222222222222222222222222 +3679 2222222222222222222222222222222222222222222222222222222 +3680 2222222222222222222222222222222222222222222222222222222 +3681 2222222222222222222222222222222222222222222222222222222 +3682 2222222222222222222222222222222222222222222222222222222 +3683 2222222222222222222222222222222222222222222222222222222 +3684 2222222222222222222222222222222222222222222222222222222 +3685 2222222222222222222222222222222222222222222222222222222 +3686 2222222222222222222222222222222222222222222222222222222 +3687 2222222222222222222222222222222222222222222222222222222 +3688 2222222222222222222222222222222222222222222222222222222 +3689 2222222222222222222222222222222222222222222222222222222 +3690 2222222222222222222222222222222222222222222222222222222 +3691 2222222222222222222222222222222222222222222222222222222 +3692 2222222222222222222222222222222222222222222222222222222 +3693 2222222222222222222222222222222222222222222222222222222 +3694 2222222222222222222222222222222222222222222222222222222 +3695 2222222222222222222222222222222222222222222222222222222 +3696 2222222222222222222222222222222222222222222222222222222 +3697 2222222222222222222222222222222222222222222222222222222 +3698 2222222222222222222222222222222222222222222222222222222 +3699 2222222222222222222222222222222222222222222222222222222 +3700 2222222222222222222222222222222222222222222222222222222 +3701 2222222222222222222222222222222222222222222222222222222 +3702 2222222222222222222222222222222222222222222222222222222 +3703 2222222222222222222222222222222222222222222222222222222 +3704 2222222222222222222222222222222222222222222222222222222 +3705 2222222222222222222222222222222222222222222222222222222 +3706 2222222222222222222222222222222222222222222222222222222 +3707 2222222222222222222222222222222222222222222222222222222 +3708 2222222222222222222222222222222222222222222222222222222 +3709 2222222222222222222222222222222222222222222222222222222 +3710 2222222222222222222222222222222222222222222222222222222 +3711 2222222222222222222222222222222222222222222222222222222 +3712 2222222222222222222222222222222222222222222222222222222 +3713 2222222222222222222222222222222222222222222222222222222 +3714 2222222222222222222222222222222222222222222222222222222 +3715 2222222222222222222222222222222222222222222222222222222 +3716 2222222222222222222222222222222222222222222222222222222 +3717 2222222222222222222222222222222222222222222222222222222 +3718 2222222222222222222222222222222222222222222222222222222 +3719 2222222222222222222222222222222222222222222222222222222 +3720 2222222222222222222222222222222222222222222222222222222 +3721 2222222222222222222222222222222222222222222222222222222 +3722 2222222222222222222222222222222222222222222222222222222 +3723 2222222222222222222222222222222222222222222222222222222 +3724 2222222222222222222222222222222222222222222222222222222 +3725 2222222222222222222222222222222222222222222222222222222 +3726 2222222222222222222222222222222222222222222222222222222 +3727 2222222222222222222222222222222222222222222222222222222 +3728 2222222222222222222222222222222222222222222222222222222 +3729 2222222222222222222222222222222222222222222222222222222 +3730 2222222222222222222222222222222222222222222222222222222 +3731 2222222222222222222222222222222222222222222222222222222 +3732 2222222222222222222222222222222222222222222222222222222 +3733 2222222222222222222222222222222222222222222222222222222 +3734 2222222222222222222222222222222222222222222222222222222 +3735 2222222222222222222222222222222222222222222222222222222 +3736 2222222222222222222222222222222222222222222222222222222 +3737 2222222222222222222222222222222222222222222222222222222 +3738 2222222222222222222222222222222222222222222222222222222 +3739 2222222222222222222222222222222222222222222222222222222 +3740 2222222222222222222222222222222222222222222222222222222 +3741 2222222222222222222222222222222222222222222222222222222 +3742 2222222222222222222222222222222222222222222222222222222 +3743 2222222222222222222222222222222222222222222222222222222 +3744 2222222222222222222222222222222222222222222222222222222 +3745 2222222222222222222222222222222222222222222222222222222 +3746 2222222222222222222222222222222222222222222222222222222 +3747 2222222222222222222222222222222222222222222222222222222 +3748 2222222222222222222222222222222222222222222222222222222 +3749 2222222222222222222222222222222222222222222222222222222 +3750 2222222222222222222222222222222222222222222222222222222 +3751 2222222222222222222222222222222222222222222222222222222 +3752 2222222222222222222222222222222222222222222222222222222 +3753 2222222222222222222222222222222222222222222222222222222 +3754 2222222222222222222222222222222222222222222222222222222 +3755 2222222222222222222222222222222222222222222222222222222 +3756 2222222222222222222222222222222222222222222222222222222 +3757 2222222222222222222222222222222222222222222222222222222 +3758 2222222222222222222222222222222222222222222222222222222 +3759 2222222222222222222222222222222222222222222222222222222 +3760 2222222222222222222222222222222222222222222222222222222 +3761 2222222222222222222222222222222222222222222222222222222 +3762 2222222222222222222222222222222222222222222222222222222 +3763 2222222222222222222222222222222222222222222222222222222 +3764 2222222222222222222222222222222222222222222222222222222 +3765 2222222222222222222222222222222222222222222222222222222 +3766 2222222222222222222222222222222222222222222222222222222 +3767 2222222222222222222222222222222222222222222222222222222 +3768 2222222222222222222222222222222222222222222222222222222 +3769 2222222222222222222222222222222222222222222222222222222 +3770 2222222222222222222222222222222222222222222222222222222 +3771 2222222222222222222222222222222222222222222222222222222 +3772 2222222222222222222222222222222222222222222222222222222 +3773 2222222222222222222222222222222222222222222222222222222 +3774 2222222222222222222222222222222222222222222222222222222 +3775 2222222222222222222222222222222222222222222222222222222 +3776 2222222222222222222222222222222222222222222222222222222 +3777 2222222222222222222222222222222222222222222222222222222 +3778 2222222222222222222222222222222222222222222222222222222 +3779 2222222222222222222222222222222222222222222222222222222 +3780 2222222222222222222222222222222222222222222222222222222 +3781 2222222222222222222222222222222222222222222222222222222 +3782 2222222222222222222222222222222222222222222222222222222 +3783 2222222222222222222222222222222222222222222222222222222 +3784 2222222222222222222222222222222222222222222222222222222 +3785 2222222222222222222222222222222222222222222222222222222 +3786 2222222222222222222222222222222222222222222222222222222 +3787 2222222222222222222222222222222222222222222222222222222 +3788 2222222222222222222222222222222222222222222222222222222 +3789 2222222222222222222222222222222222222222222222222222222 +3790 2222222222222222222222222222222222222222222222222222222 +3791 2222222222222222222222222222222222222222222222222222222 +3792 2222222222222222222222222222222222222222222222222222222 +3793 2222222222222222222222222222222222222222222222222222222 +3794 2222222222222222222222222222222222222222222222222222222 +3795 2222222222222222222222222222222222222222222222222222222 +3796 2222222222222222222222222222222222222222222222222222222 +3797 2222222222222222222222222222222222222222222222222222222 +3798 2222222222222222222222222222222222222222222222222222222 +3799 2222222222222222222222222222222222222222222222222222222 +3800 2222222222222222222222222222222222222222222222222222222 +3801 2222222222222222222222222222222222222222222222222222222 +3802 2222222222222222222222222222222222222222222222222222222 +3803 2222222222222222222222222222222222222222222222222222222 +3804 2222222222222222222222222222222222222222222222222222222 +3805 2222222222222222222222222222222222222222222222222222222 +3806 2222222222222222222222222222222222222222222222222222222 +3807 2222222222222222222222222222222222222222222222222222222 +3808 2222222222222222222222222222222222222222222222222222222 +3809 2222222222222222222222222222222222222222222222222222222 +3810 2222222222222222222222222222222222222222222222222222222 +3811 2222222222222222222222222222222222222222222222222222222 +3812 2222222222222222222222222222222222222222222222222222222 +3813 2222222222222222222222222222222222222222222222222222222 +3814 2222222222222222222222222222222222222222222222222222222 +3815 2222222222222222222222222222222222222222222222222222222 +3816 2222222222222222222222222222222222222222222222222222222 +3817 2222222222222222222222222222222222222222222222222222222 +3818 2222222222222222222222222222222222222222222222222222222 +3819 2222222222222222222222222222222222222222222222222222222 +3820 2222222222222222222222222222222222222222222222222222222 +3821 2222222222222222222222222222222222222222222222222222222 +3822 2222222222222222222222222222222222222222222222222222222 +3823 2222222222222222222222222222222222222222222222222222222 +3824 2222222222222222222222222222222222222222222222222222222 +3825 2222222222222222222222222222222222222222222222222222222 +3826 2222222222222222222222222222222222222222222222222222222 +3827 2222222222222222222222222222222222222222222222222222222 +3828 2222222222222222222222222222222222222222222222222222222 +3829 2222222222222222222222222222222222222222222222222222222 +3830 2222222222222222222222222222222222222222222222222222222 +3831 2222222222222222222222222222222222222222222222222222222 +3832 2222222222222222222222222222222222222222222222222222222 +3833 2222222222222222222222222222222222222222222222222222222 +3834 2222222222222222222222222222222222222222222222222222222 +3835 2222222222222222222222222222222222222222222222222222222 +3836 2222222222222222222222222222222222222222222222222222222 +3837 2222222222222222222222222222222222222222222222222222222 +3838 2222222222222222222222222222222222222222222222222222222 +3839 2222222222222222222222222222222222222222222222222222222 +3840 2222222222222222222222222222222222222222222222222222222 +3841 2222222222222222222222222222222222222222222222222222222 +3842 2222222222222222222222222222222222222222222222222222222 +3843 2222222222222222222222222222222222222222222222222222222 +3844 2222222222222222222222222222222222222222222222222222222 +3845 2222222222222222222222222222222222222222222222222222222 +3846 2222222222222222222222222222222222222222222222222222222 +3847 2222222222222222222222222222222222222222222222222222222 +3848 2222222222222222222222222222222222222222222222222222222 +3849 2222222222222222222222222222222222222222222222222222222 +3850 2222222222222222222222222222222222222222222222222222222 +3851 2222222222222222222222222222222222222222222222222222222 +3852 2222222222222222222222222222222222222222222222222222222 +3853 2222222222222222222222222222222222222222222222222222222 +3854 2222222222222222222222222222222222222222222222222222222 +3855 2222222222222222222222222222222222222222222222222222222 +3856 2222222222222222222222222222222222222222222222222222222 +3857 2222222222222222222222222222222222222222222222222222222 +3858 2222222222222222222222222222222222222222222222222222222 +3859 2222222222222222222222222222222222222222222222222222222 +3860 2222222222222222222222222222222222222222222222222222222 +3861 2222222222222222222222222222222222222222222222222222222 +3862 2222222222222222222222222222222222222222222222222222222 +3863 2222222222222222222222222222222222222222222222222222222 +3864 2222222222222222222222222222222222222222222222222222222 +3865 2222222222222222222222222222222222222222222222222222222 +3866 2222222222222222222222222222222222222222222222222222222 +3867 2222222222222222222222222222222222222222222222222222222 +3868 2222222222222222222222222222222222222222222222222222222 +3869 2222222222222222222222222222222222222222222222222222222 +3870 2222222222222222222222222222222222222222222222222222222 +3871 2222222222222222222222222222222222222222222222222222222 +3872 2222222222222222222222222222222222222222222222222222222 +3873 2222222222222222222222222222222222222222222222222222222 +3874 2222222222222222222222222222222222222222222222222222222 +3875 2222222222222222222222222222222222222222222222222222222 +3876 2222222222222222222222222222222222222222222222222222222 +3877 2222222222222222222222222222222222222222222222222222222 +3878 2222222222222222222222222222222222222222222222222222222 +3879 2222222222222222222222222222222222222222222222222222222 +3880 2222222222222222222222222222222222222222222222222222222 +3881 2222222222222222222222222222222222222222222222222222222 +3882 2222222222222222222222222222222222222222222222222222222 +3883 2222222222222222222222222222222222222222222222222222222 +3884 2222222222222222222222222222222222222222222222222222222 +3885 2222222222222222222222222222222222222222222222222222222 +3886 2222222222222222222222222222222222222222222222222222222 +3887 2222222222222222222222222222222222222222222222222222222 +3888 2222222222222222222222222222222222222222222222222222222 +3889 2222222222222222222222222222222222222222222222222222222 +3890 2222222222222222222222222222222222222222222222222222222 +3891 2222222222222222222222222222222222222222222222222222222 +3892 2222222222222222222222222222222222222222222222222222222 +3893 2222222222222222222222222222222222222222222222222222222 +3894 2222222222222222222222222222222222222222222222222222222 +3895 2222222222222222222222222222222222222222222222222222222 +3896 2222222222222222222222222222222222222222222222222222222 +3897 2222222222222222222222222222222222222222222222222222222 +3898 2222222222222222222222222222222222222222222222222222222 +3899 2222222222222222222222222222222222222222222222222222222 +3900 2222222222222222222222222222222222222222222222222222222 +3901 2222222222222222222222222222222222222222222222222222222 +3902 2222222222222222222222222222222222222222222222222222222 +3903 2222222222222222222222222222222222222222222222222222222 +3904 2222222222222222222222222222222222222222222222222222222 +3905 2222222222222222222222222222222222222222222222222222222 +3906 2222222222222222222222222222222222222222222222222222222 +3907 2222222222222222222222222222222222222222222222222222222 +3908 2222222222222222222222222222222222222222222222222222222 +3909 2222222222222222222222222222222222222222222222222222222 +3910 2222222222222222222222222222222222222222222222222222222 +3911 2222222222222222222222222222222222222222222222222222222 +3912 2222222222222222222222222222222222222222222222222222222 +3913 2222222222222222222222222222222222222222222222222222222 +3914 2222222222222222222222222222222222222222222222222222222 +3915 2222222222222222222222222222222222222222222222222222222 +3916 2222222222222222222222222222222222222222222222222222222 +3917 2222222222222222222222222222222222222222222222222222222 +3918 2222222222222222222222222222222222222222222222222222222 +3919 2222222222222222222222222222222222222222222222222222222 +3920 2222222222222222222222222222222222222222222222222222222 +3921 2222222222222222222222222222222222222222222222222222222 +3922 2222222222222222222222222222222222222222222222222222222 +3923 2222222222222222222222222222222222222222222222222222222 +3924 2222222222222222222222222222222222222222222222222222222 +3925 2222222222222222222222222222222222222222222222222222222 +3926 2222222222222222222222222222222222222222222222222222222 +3927 2222222222222222222222222222222222222222222222222222222 +3928 2222222222222222222222222222222222222222222222222222222 +3929 2222222222222222222222222222222222222222222222222222222 +3930 2222222222222222222222222222222222222222222222222222222 +3931 2222222222222222222222222222222222222222222222222222222 +3932 2222222222222222222222222222222222222222222222222222222 +3933 2222222222222222222222222222222222222222222222222222222 +3934 2222222222222222222222222222222222222222222222222222222 +3935 2222222222222222222222222222222222222222222222222222222 +3936 2222222222222222222222222222222222222222222222222222222 +3937 2222222222222222222222222222222222222222222222222222222 +3938 2222222222222222222222222222222222222222222222222222222 +3939 2222222222222222222222222222222222222222222222222222222 +3940 2222222222222222222222222222222222222222222222222222222 +3941 2222222222222222222222222222222222222222222222222222222 +3942 2222222222222222222222222222222222222222222222222222222 +3943 2222222222222222222222222222222222222222222222222222222 +3944 2222222222222222222222222222222222222222222222222222222 +3945 2222222222222222222222222222222222222222222222222222222 +3946 2222222222222222222222222222222222222222222222222222222 +3947 2222222222222222222222222222222222222222222222222222222 +3948 2222222222222222222222222222222222222222222222222222222 +3949 2222222222222222222222222222222222222222222222222222222 +3950 2222222222222222222222222222222222222222222222222222222 +3951 2222222222222222222222222222222222222222222222222222222 +3952 2222222222222222222222222222222222222222222222222222222 +3953 2222222222222222222222222222222222222222222222222222222 +3954 2222222222222222222222222222222222222222222222222222222 +3955 2222222222222222222222222222222222222222222222222222222 +3956 2222222222222222222222222222222222222222222222222222222 +3957 2222222222222222222222222222222222222222222222222222222 +3958 2222222222222222222222222222222222222222222222222222222 +3959 2222222222222222222222222222222222222222222222222222222 +3960 2222222222222222222222222222222222222222222222222222222 +3961 2222222222222222222222222222222222222222222222222222222 +3962 2222222222222222222222222222222222222222222222222222222 +3963 2222222222222222222222222222222222222222222222222222222 +3964 2222222222222222222222222222222222222222222222222222222 +3965 2222222222222222222222222222222222222222222222222222222 +3966 2222222222222222222222222222222222222222222222222222222 +3967 2222222222222222222222222222222222222222222222222222222 +3968 2222222222222222222222222222222222222222222222222222222 +3969 2222222222222222222222222222222222222222222222222222222 +3970 2222222222222222222222222222222222222222222222222222222 +3971 2222222222222222222222222222222222222222222222222222222 +3972 2222222222222222222222222222222222222222222222222222222 +3973 2222222222222222222222222222222222222222222222222222222 +3974 2222222222222222222222222222222222222222222222222222222 +3975 2222222222222222222222222222222222222222222222222222222 +3976 2222222222222222222222222222222222222222222222222222222 +3977 2222222222222222222222222222222222222222222222222222222 +3978 2222222222222222222222222222222222222222222222222222222 +3979 2222222222222222222222222222222222222222222222222222222 +3980 2222222222222222222222222222222222222222222222222222222 +3981 2222222222222222222222222222222222222222222222222222222 +3982 2222222222222222222222222222222222222222222222222222222 +3983 2222222222222222222222222222222222222222222222222222222 +3984 2222222222222222222222222222222222222222222222222222222 +3985 2222222222222222222222222222222222222222222222222222222 +3986 2222222222222222222222222222222222222222222222222222222 +3987 2222222222222222222222222222222222222222222222222222222 +3988 2222222222222222222222222222222222222222222222222222222 +3989 2222222222222222222222222222222222222222222222222222222 +3990 2222222222222222222222222222222222222222222222222222222 +3991 2222222222222222222222222222222222222222222222222222222 +3992 2222222222222222222222222222222222222222222222222222222 +3993 2222222222222222222222222222222222222222222222222222222 +3994 2222222222222222222222222222222222222222222222222222222 +3995 2222222222222222222222222222222222222222222222222222222 +3996 2222222222222222222222222222222222222222222222222222222 +3997 2222222222222222222222222222222222222222222222222222222 +3998 2222222222222222222222222222222222222222222222222222222 +3999 2222222222222222222222222222222222222222222222222222222 diff --git a/jetty-test-webapp/src/main/webapp/data.txt b/jetty-test-webapp/src/main/webapp/data.txt new file mode 100644 index 00000000000..f28670f2968 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/data.txt @@ -0,0 +1,10000 @@ +0000 3333333333333333333333333333333333333333333333333333333 +0001 3333333333333333333333333333333333333333333333333333333 +0002 3333333333333333333333333333333333333333333333333333333 +0003 3333333333333333333333333333333333333333333333333333333 +0004 3333333333333333333333333333333333333333333333333333333 +0005 3333333333333333333333333333333333333333333333333333333 +0006 3333333333333333333333333333333333333333333333333333333 +0007 3333333333333333333333333333333333333333333333333333333 +0008 3333333333333333333333333333333333333333333333333333333 +0009 3333333333333333333333333333333333333333333333333333333 +0010 3333333333333333333333333333333333333333333333333333333 +0011 3333333333333333333333333333333333333333333333333333333 +0012 3333333333333333333333333333333333333333333333333333333 +0013 3333333333333333333333333333333333333333333333333333333 +0014 3333333333333333333333333333333333333333333333333333333 +0015 3333333333333333333333333333333333333333333333333333333 +0016 3333333333333333333333333333333333333333333333333333333 +0017 3333333333333333333333333333333333333333333333333333333 +0018 3333333333333333333333333333333333333333333333333333333 +0019 3333333333333333333333333333333333333333333333333333333 +0020 3333333333333333333333333333333333333333333333333333333 +0021 3333333333333333333333333333333333333333333333333333333 +0022 3333333333333333333333333333333333333333333333333333333 +0023 3333333333333333333333333333333333333333333333333333333 +0024 3333333333333333333333333333333333333333333333333333333 +0025 3333333333333333333333333333333333333333333333333333333 +0026 3333333333333333333333333333333333333333333333333333333 +0027 3333333333333333333333333333333333333333333333333333333 +0028 3333333333333333333333333333333333333333333333333333333 +0029 3333333333333333333333333333333333333333333333333333333 +0030 3333333333333333333333333333333333333333333333333333333 +0031 3333333333333333333333333333333333333333333333333333333 +0032 3333333333333333333333333333333333333333333333333333333 +0033 3333333333333333333333333333333333333333333333333333333 +0034 3333333333333333333333333333333333333333333333333333333 +0035 3333333333333333333333333333333333333333333333333333333 +0036 3333333333333333333333333333333333333333333333333333333 +0037 3333333333333333333333333333333333333333333333333333333 +0038 3333333333333333333333333333333333333333333333333333333 +0039 3333333333333333333333333333333333333333333333333333333 +0040 3333333333333333333333333333333333333333333333333333333 +0041 3333333333333333333333333333333333333333333333333333333 +0042 3333333333333333333333333333333333333333333333333333333 +0043 3333333333333333333333333333333333333333333333333333333 +0044 3333333333333333333333333333333333333333333333333333333 +0045 3333333333333333333333333333333333333333333333333333333 +0046 3333333333333333333333333333333333333333333333333333333 +0047 3333333333333333333333333333333333333333333333333333333 +0048 3333333333333333333333333333333333333333333333333333333 +0049 3333333333333333333333333333333333333333333333333333333 +0050 3333333333333333333333333333333333333333333333333333333 +0051 3333333333333333333333333333333333333333333333333333333 +0052 3333333333333333333333333333333333333333333333333333333 +0053 3333333333333333333333333333333333333333333333333333333 +0054 3333333333333333333333333333333333333333333333333333333 +0055 3333333333333333333333333333333333333333333333333333333 +0056 3333333333333333333333333333333333333333333333333333333 +0057 3333333333333333333333333333333333333333333333333333333 +0058 3333333333333333333333333333333333333333333333333333333 +0059 3333333333333333333333333333333333333333333333333333333 +0060 3333333333333333333333333333333333333333333333333333333 +0061 3333333333333333333333333333333333333333333333333333333 +0062 3333333333333333333333333333333333333333333333333333333 +0063 3333333333333333333333333333333333333333333333333333333 +0064 3333333333333333333333333333333333333333333333333333333 +0065 3333333333333333333333333333333333333333333333333333333 +0066 3333333333333333333333333333333333333333333333333333333 +0067 3333333333333333333333333333333333333333333333333333333 +0068 3333333333333333333333333333333333333333333333333333333 +0069 3333333333333333333333333333333333333333333333333333333 +0070 3333333333333333333333333333333333333333333333333333333 +0071 3333333333333333333333333333333333333333333333333333333 +0072 3333333333333333333333333333333333333333333333333333333 +0073 3333333333333333333333333333333333333333333333333333333 +0074 3333333333333333333333333333333333333333333333333333333 +0075 3333333333333333333333333333333333333333333333333333333 +0076 3333333333333333333333333333333333333333333333333333333 +0077 3333333333333333333333333333333333333333333333333333333 +0078 3333333333333333333333333333333333333333333333333333333 +0079 3333333333333333333333333333333333333333333333333333333 +0080 3333333333333333333333333333333333333333333333333333333 +0081 3333333333333333333333333333333333333333333333333333333 +0082 3333333333333333333333333333333333333333333333333333333 +0083 3333333333333333333333333333333333333333333333333333333 +0084 3333333333333333333333333333333333333333333333333333333 +0085 3333333333333333333333333333333333333333333333333333333 +0086 3333333333333333333333333333333333333333333333333333333 +0087 3333333333333333333333333333333333333333333333333333333 +0088 3333333333333333333333333333333333333333333333333333333 +0089 3333333333333333333333333333333333333333333333333333333 +0090 3333333333333333333333333333333333333333333333333333333 +0091 3333333333333333333333333333333333333333333333333333333 +0092 3333333333333333333333333333333333333333333333333333333 +0093 3333333333333333333333333333333333333333333333333333333 +0094 3333333333333333333333333333333333333333333333333333333 +0095 3333333333333333333333333333333333333333333333333333333 +0096 3333333333333333333333333333333333333333333333333333333 +0097 3333333333333333333333333333333333333333333333333333333 +0098 3333333333333333333333333333333333333333333333333333333 +0099 3333333333333333333333333333333333333333333333333333333 +0100 3333333333333333333333333333333333333333333333333333333 +0101 3333333333333333333333333333333333333333333333333333333 +0102 3333333333333333333333333333333333333333333333333333333 +0103 3333333333333333333333333333333333333333333333333333333 +0104 3333333333333333333333333333333333333333333333333333333 +0105 3333333333333333333333333333333333333333333333333333333 +0106 3333333333333333333333333333333333333333333333333333333 +0107 3333333333333333333333333333333333333333333333333333333 +0108 3333333333333333333333333333333333333333333333333333333 +0109 3333333333333333333333333333333333333333333333333333333 +0110 3333333333333333333333333333333333333333333333333333333 +0111 3333333333333333333333333333333333333333333333333333333 +0112 3333333333333333333333333333333333333333333333333333333 +0113 3333333333333333333333333333333333333333333333333333333 +0114 3333333333333333333333333333333333333333333333333333333 +0115 3333333333333333333333333333333333333333333333333333333 +0116 3333333333333333333333333333333333333333333333333333333 +0117 3333333333333333333333333333333333333333333333333333333 +0118 3333333333333333333333333333333333333333333333333333333 +0119 3333333333333333333333333333333333333333333333333333333 +0120 3333333333333333333333333333333333333333333333333333333 +0121 3333333333333333333333333333333333333333333333333333333 +0122 3333333333333333333333333333333333333333333333333333333 +0123 3333333333333333333333333333333333333333333333333333333 +0124 3333333333333333333333333333333333333333333333333333333 +0125 3333333333333333333333333333333333333333333333333333333 +0126 3333333333333333333333333333333333333333333333333333333 +0127 3333333333333333333333333333333333333333333333333333333 +0128 3333333333333333333333333333333333333333333333333333333 +0129 3333333333333333333333333333333333333333333333333333333 +0130 3333333333333333333333333333333333333333333333333333333 +0131 3333333333333333333333333333333333333333333333333333333 +0132 3333333333333333333333333333333333333333333333333333333 +0133 3333333333333333333333333333333333333333333333333333333 +0134 3333333333333333333333333333333333333333333333333333333 +0135 3333333333333333333333333333333333333333333333333333333 +0136 3333333333333333333333333333333333333333333333333333333 +0137 3333333333333333333333333333333333333333333333333333333 +0138 3333333333333333333333333333333333333333333333333333333 +0139 3333333333333333333333333333333333333333333333333333333 +0140 3333333333333333333333333333333333333333333333333333333 +0141 3333333333333333333333333333333333333333333333333333333 +0142 3333333333333333333333333333333333333333333333333333333 +0143 3333333333333333333333333333333333333333333333333333333 +0144 3333333333333333333333333333333333333333333333333333333 +0145 3333333333333333333333333333333333333333333333333333333 +0146 3333333333333333333333333333333333333333333333333333333 +0147 3333333333333333333333333333333333333333333333333333333 +0148 3333333333333333333333333333333333333333333333333333333 +0149 3333333333333333333333333333333333333333333333333333333 +0150 3333333333333333333333333333333333333333333333333333333 +0151 3333333333333333333333333333333333333333333333333333333 +0152 3333333333333333333333333333333333333333333333333333333 +0153 3333333333333333333333333333333333333333333333333333333 +0154 3333333333333333333333333333333333333333333333333333333 +0155 3333333333333333333333333333333333333333333333333333333 +0156 3333333333333333333333333333333333333333333333333333333 +0157 3333333333333333333333333333333333333333333333333333333 +0158 3333333333333333333333333333333333333333333333333333333 +0159 3333333333333333333333333333333333333333333333333333333 +0160 3333333333333333333333333333333333333333333333333333333 +0161 3333333333333333333333333333333333333333333333333333333 +0162 3333333333333333333333333333333333333333333333333333333 +0163 3333333333333333333333333333333333333333333333333333333 +0164 3333333333333333333333333333333333333333333333333333333 +0165 3333333333333333333333333333333333333333333333333333333 +0166 3333333333333333333333333333333333333333333333333333333 +0167 3333333333333333333333333333333333333333333333333333333 +0168 3333333333333333333333333333333333333333333333333333333 +0169 3333333333333333333333333333333333333333333333333333333 +0170 3333333333333333333333333333333333333333333333333333333 +0171 3333333333333333333333333333333333333333333333333333333 +0172 3333333333333333333333333333333333333333333333333333333 +0173 3333333333333333333333333333333333333333333333333333333 +0174 3333333333333333333333333333333333333333333333333333333 +0175 3333333333333333333333333333333333333333333333333333333 +0176 3333333333333333333333333333333333333333333333333333333 +0177 3333333333333333333333333333333333333333333333333333333 +0178 3333333333333333333333333333333333333333333333333333333 +0179 3333333333333333333333333333333333333333333333333333333 +0180 3333333333333333333333333333333333333333333333333333333 +0181 3333333333333333333333333333333333333333333333333333333 +0182 3333333333333333333333333333333333333333333333333333333 +0183 3333333333333333333333333333333333333333333333333333333 +0184 3333333333333333333333333333333333333333333333333333333 +0185 3333333333333333333333333333333333333333333333333333333 +0186 3333333333333333333333333333333333333333333333333333333 +0187 3333333333333333333333333333333333333333333333333333333 +0188 3333333333333333333333333333333333333333333333333333333 +0189 3333333333333333333333333333333333333333333333333333333 +0190 3333333333333333333333333333333333333333333333333333333 +0191 3333333333333333333333333333333333333333333333333333333 +0192 3333333333333333333333333333333333333333333333333333333 +0193 3333333333333333333333333333333333333333333333333333333 +0194 3333333333333333333333333333333333333333333333333333333 +0195 3333333333333333333333333333333333333333333333333333333 +0196 3333333333333333333333333333333333333333333333333333333 +0197 3333333333333333333333333333333333333333333333333333333 +0198 3333333333333333333333333333333333333333333333333333333 +0199 3333333333333333333333333333333333333333333333333333333 +0200 3333333333333333333333333333333333333333333333333333333 +0201 3333333333333333333333333333333333333333333333333333333 +0202 3333333333333333333333333333333333333333333333333333333 +0203 3333333333333333333333333333333333333333333333333333333 +0204 3333333333333333333333333333333333333333333333333333333 +0205 3333333333333333333333333333333333333333333333333333333 +0206 3333333333333333333333333333333333333333333333333333333 +0207 3333333333333333333333333333333333333333333333333333333 +0208 3333333333333333333333333333333333333333333333333333333 +0209 3333333333333333333333333333333333333333333333333333333 +0210 3333333333333333333333333333333333333333333333333333333 +0211 3333333333333333333333333333333333333333333333333333333 +0212 3333333333333333333333333333333333333333333333333333333 +0213 3333333333333333333333333333333333333333333333333333333 +0214 3333333333333333333333333333333333333333333333333333333 +0215 3333333333333333333333333333333333333333333333333333333 +0216 3333333333333333333333333333333333333333333333333333333 +0217 3333333333333333333333333333333333333333333333333333333 +0218 3333333333333333333333333333333333333333333333333333333 +0219 3333333333333333333333333333333333333333333333333333333 +0220 3333333333333333333333333333333333333333333333333333333 +0221 3333333333333333333333333333333333333333333333333333333 +0222 3333333333333333333333333333333333333333333333333333333 +0223 3333333333333333333333333333333333333333333333333333333 +0224 3333333333333333333333333333333333333333333333333333333 +0225 3333333333333333333333333333333333333333333333333333333 +0226 3333333333333333333333333333333333333333333333333333333 +0227 3333333333333333333333333333333333333333333333333333333 +0228 3333333333333333333333333333333333333333333333333333333 +0229 3333333333333333333333333333333333333333333333333333333 +0230 3333333333333333333333333333333333333333333333333333333 +0231 3333333333333333333333333333333333333333333333333333333 +0232 3333333333333333333333333333333333333333333333333333333 +0233 3333333333333333333333333333333333333333333333333333333 +0234 3333333333333333333333333333333333333333333333333333333 +0235 3333333333333333333333333333333333333333333333333333333 +0236 3333333333333333333333333333333333333333333333333333333 +0237 3333333333333333333333333333333333333333333333333333333 +0238 3333333333333333333333333333333333333333333333333333333 +0239 3333333333333333333333333333333333333333333333333333333 +0240 3333333333333333333333333333333333333333333333333333333 +0241 3333333333333333333333333333333333333333333333333333333 +0242 3333333333333333333333333333333333333333333333333333333 +0243 3333333333333333333333333333333333333333333333333333333 +0244 3333333333333333333333333333333333333333333333333333333 +0245 3333333333333333333333333333333333333333333333333333333 +0246 3333333333333333333333333333333333333333333333333333333 +0247 3333333333333333333333333333333333333333333333333333333 +0248 3333333333333333333333333333333333333333333333333333333 +0249 3333333333333333333333333333333333333333333333333333333 +0250 3333333333333333333333333333333333333333333333333333333 +0251 3333333333333333333333333333333333333333333333333333333 +0252 3333333333333333333333333333333333333333333333333333333 +0253 3333333333333333333333333333333333333333333333333333333 +0254 3333333333333333333333333333333333333333333333333333333 +0255 3333333333333333333333333333333333333333333333333333333 +0256 3333333333333333333333333333333333333333333333333333333 +0257 3333333333333333333333333333333333333333333333333333333 +0258 3333333333333333333333333333333333333333333333333333333 +0259 3333333333333333333333333333333333333333333333333333333 +0260 3333333333333333333333333333333333333333333333333333333 +0261 3333333333333333333333333333333333333333333333333333333 +0262 3333333333333333333333333333333333333333333333333333333 +0263 3333333333333333333333333333333333333333333333333333333 +0264 3333333333333333333333333333333333333333333333333333333 +0265 3333333333333333333333333333333333333333333333333333333 +0266 3333333333333333333333333333333333333333333333333333333 +0267 3333333333333333333333333333333333333333333333333333333 +0268 3333333333333333333333333333333333333333333333333333333 +0269 3333333333333333333333333333333333333333333333333333333 +0270 3333333333333333333333333333333333333333333333333333333 +0271 3333333333333333333333333333333333333333333333333333333 +0272 3333333333333333333333333333333333333333333333333333333 +0273 3333333333333333333333333333333333333333333333333333333 +0274 3333333333333333333333333333333333333333333333333333333 +0275 3333333333333333333333333333333333333333333333333333333 +0276 3333333333333333333333333333333333333333333333333333333 +0277 3333333333333333333333333333333333333333333333333333333 +0278 3333333333333333333333333333333333333333333333333333333 +0279 3333333333333333333333333333333333333333333333333333333 +0280 3333333333333333333333333333333333333333333333333333333 +0281 3333333333333333333333333333333333333333333333333333333 +0282 3333333333333333333333333333333333333333333333333333333 +0283 3333333333333333333333333333333333333333333333333333333 +0284 3333333333333333333333333333333333333333333333333333333 +0285 3333333333333333333333333333333333333333333333333333333 +0286 3333333333333333333333333333333333333333333333333333333 +0287 3333333333333333333333333333333333333333333333333333333 +0288 3333333333333333333333333333333333333333333333333333333 +0289 3333333333333333333333333333333333333333333333333333333 +0290 3333333333333333333333333333333333333333333333333333333 +0291 3333333333333333333333333333333333333333333333333333333 +0292 3333333333333333333333333333333333333333333333333333333 +0293 3333333333333333333333333333333333333333333333333333333 +0294 3333333333333333333333333333333333333333333333333333333 +0295 3333333333333333333333333333333333333333333333333333333 +0296 3333333333333333333333333333333333333333333333333333333 +0297 3333333333333333333333333333333333333333333333333333333 +0298 3333333333333333333333333333333333333333333333333333333 +0299 3333333333333333333333333333333333333333333333333333333 +0300 3333333333333333333333333333333333333333333333333333333 +0301 3333333333333333333333333333333333333333333333333333333 +0302 3333333333333333333333333333333333333333333333333333333 +0303 3333333333333333333333333333333333333333333333333333333 +0304 3333333333333333333333333333333333333333333333333333333 +0305 3333333333333333333333333333333333333333333333333333333 +0306 3333333333333333333333333333333333333333333333333333333 +0307 3333333333333333333333333333333333333333333333333333333 +0308 3333333333333333333333333333333333333333333333333333333 +0309 3333333333333333333333333333333333333333333333333333333 +0310 3333333333333333333333333333333333333333333333333333333 +0311 3333333333333333333333333333333333333333333333333333333 +0312 3333333333333333333333333333333333333333333333333333333 +0313 3333333333333333333333333333333333333333333333333333333 +0314 3333333333333333333333333333333333333333333333333333333 +0315 3333333333333333333333333333333333333333333333333333333 +0316 3333333333333333333333333333333333333333333333333333333 +0317 3333333333333333333333333333333333333333333333333333333 +0318 3333333333333333333333333333333333333333333333333333333 +0319 3333333333333333333333333333333333333333333333333333333 +0320 3333333333333333333333333333333333333333333333333333333 +0321 3333333333333333333333333333333333333333333333333333333 +0322 3333333333333333333333333333333333333333333333333333333 +0323 3333333333333333333333333333333333333333333333333333333 +0324 3333333333333333333333333333333333333333333333333333333 +0325 3333333333333333333333333333333333333333333333333333333 +0326 3333333333333333333333333333333333333333333333333333333 +0327 3333333333333333333333333333333333333333333333333333333 +0328 3333333333333333333333333333333333333333333333333333333 +0329 3333333333333333333333333333333333333333333333333333333 +0330 3333333333333333333333333333333333333333333333333333333 +0331 3333333333333333333333333333333333333333333333333333333 +0332 3333333333333333333333333333333333333333333333333333333 +0333 3333333333333333333333333333333333333333333333333333333 +0334 3333333333333333333333333333333333333333333333333333333 +0335 3333333333333333333333333333333333333333333333333333333 +0336 3333333333333333333333333333333333333333333333333333333 +0337 3333333333333333333333333333333333333333333333333333333 +0338 3333333333333333333333333333333333333333333333333333333 +0339 3333333333333333333333333333333333333333333333333333333 +0340 3333333333333333333333333333333333333333333333333333333 +0341 3333333333333333333333333333333333333333333333333333333 +0342 3333333333333333333333333333333333333333333333333333333 +0343 3333333333333333333333333333333333333333333333333333333 +0344 3333333333333333333333333333333333333333333333333333333 +0345 3333333333333333333333333333333333333333333333333333333 +0346 3333333333333333333333333333333333333333333333333333333 +0347 3333333333333333333333333333333333333333333333333333333 +0348 3333333333333333333333333333333333333333333333333333333 +0349 3333333333333333333333333333333333333333333333333333333 +0350 3333333333333333333333333333333333333333333333333333333 +0351 3333333333333333333333333333333333333333333333333333333 +0352 3333333333333333333333333333333333333333333333333333333 +0353 3333333333333333333333333333333333333333333333333333333 +0354 3333333333333333333333333333333333333333333333333333333 +0355 3333333333333333333333333333333333333333333333333333333 +0356 3333333333333333333333333333333333333333333333333333333 +0357 3333333333333333333333333333333333333333333333333333333 +0358 3333333333333333333333333333333333333333333333333333333 +0359 3333333333333333333333333333333333333333333333333333333 +0360 3333333333333333333333333333333333333333333333333333333 +0361 3333333333333333333333333333333333333333333333333333333 +0362 3333333333333333333333333333333333333333333333333333333 +0363 3333333333333333333333333333333333333333333333333333333 +0364 3333333333333333333333333333333333333333333333333333333 +0365 3333333333333333333333333333333333333333333333333333333 +0366 3333333333333333333333333333333333333333333333333333333 +0367 3333333333333333333333333333333333333333333333333333333 +0368 3333333333333333333333333333333333333333333333333333333 +0369 3333333333333333333333333333333333333333333333333333333 +0370 3333333333333333333333333333333333333333333333333333333 +0371 3333333333333333333333333333333333333333333333333333333 +0372 3333333333333333333333333333333333333333333333333333333 +0373 3333333333333333333333333333333333333333333333333333333 +0374 3333333333333333333333333333333333333333333333333333333 +0375 3333333333333333333333333333333333333333333333333333333 +0376 3333333333333333333333333333333333333333333333333333333 +0377 3333333333333333333333333333333333333333333333333333333 +0378 3333333333333333333333333333333333333333333333333333333 +0379 3333333333333333333333333333333333333333333333333333333 +0380 3333333333333333333333333333333333333333333333333333333 +0381 3333333333333333333333333333333333333333333333333333333 +0382 3333333333333333333333333333333333333333333333333333333 +0383 3333333333333333333333333333333333333333333333333333333 +0384 3333333333333333333333333333333333333333333333333333333 +0385 3333333333333333333333333333333333333333333333333333333 +0386 3333333333333333333333333333333333333333333333333333333 +0387 3333333333333333333333333333333333333333333333333333333 +0388 3333333333333333333333333333333333333333333333333333333 +0389 3333333333333333333333333333333333333333333333333333333 +0390 3333333333333333333333333333333333333333333333333333333 +0391 3333333333333333333333333333333333333333333333333333333 +0392 3333333333333333333333333333333333333333333333333333333 +0393 3333333333333333333333333333333333333333333333333333333 +0394 3333333333333333333333333333333333333333333333333333333 +0395 3333333333333333333333333333333333333333333333333333333 +0396 3333333333333333333333333333333333333333333333333333333 +0397 3333333333333333333333333333333333333333333333333333333 +0398 3333333333333333333333333333333333333333333333333333333 +0399 3333333333333333333333333333333333333333333333333333333 +0400 3333333333333333333333333333333333333333333333333333333 +0401 3333333333333333333333333333333333333333333333333333333 +0402 3333333333333333333333333333333333333333333333333333333 +0403 3333333333333333333333333333333333333333333333333333333 +0404 3333333333333333333333333333333333333333333333333333333 +0405 3333333333333333333333333333333333333333333333333333333 +0406 3333333333333333333333333333333333333333333333333333333 +0407 3333333333333333333333333333333333333333333333333333333 +0408 3333333333333333333333333333333333333333333333333333333 +0409 3333333333333333333333333333333333333333333333333333333 +0410 3333333333333333333333333333333333333333333333333333333 +0411 3333333333333333333333333333333333333333333333333333333 +0412 3333333333333333333333333333333333333333333333333333333 +0413 3333333333333333333333333333333333333333333333333333333 +0414 3333333333333333333333333333333333333333333333333333333 +0415 3333333333333333333333333333333333333333333333333333333 +0416 3333333333333333333333333333333333333333333333333333333 +0417 3333333333333333333333333333333333333333333333333333333 +0418 3333333333333333333333333333333333333333333333333333333 +0419 3333333333333333333333333333333333333333333333333333333 +0420 3333333333333333333333333333333333333333333333333333333 +0421 3333333333333333333333333333333333333333333333333333333 +0422 3333333333333333333333333333333333333333333333333333333 +0423 3333333333333333333333333333333333333333333333333333333 +0424 3333333333333333333333333333333333333333333333333333333 +0425 3333333333333333333333333333333333333333333333333333333 +0426 3333333333333333333333333333333333333333333333333333333 +0427 3333333333333333333333333333333333333333333333333333333 +0428 3333333333333333333333333333333333333333333333333333333 +0429 3333333333333333333333333333333333333333333333333333333 +0430 3333333333333333333333333333333333333333333333333333333 +0431 3333333333333333333333333333333333333333333333333333333 +0432 3333333333333333333333333333333333333333333333333333333 +0433 3333333333333333333333333333333333333333333333333333333 +0434 3333333333333333333333333333333333333333333333333333333 +0435 3333333333333333333333333333333333333333333333333333333 +0436 3333333333333333333333333333333333333333333333333333333 +0437 3333333333333333333333333333333333333333333333333333333 +0438 3333333333333333333333333333333333333333333333333333333 +0439 3333333333333333333333333333333333333333333333333333333 +0440 3333333333333333333333333333333333333333333333333333333 +0441 3333333333333333333333333333333333333333333333333333333 +0442 3333333333333333333333333333333333333333333333333333333 +0443 3333333333333333333333333333333333333333333333333333333 +0444 3333333333333333333333333333333333333333333333333333333 +0445 3333333333333333333333333333333333333333333333333333333 +0446 3333333333333333333333333333333333333333333333333333333 +0447 3333333333333333333333333333333333333333333333333333333 +0448 3333333333333333333333333333333333333333333333333333333 +0449 3333333333333333333333333333333333333333333333333333333 +0450 3333333333333333333333333333333333333333333333333333333 +0451 3333333333333333333333333333333333333333333333333333333 +0452 3333333333333333333333333333333333333333333333333333333 +0453 3333333333333333333333333333333333333333333333333333333 +0454 3333333333333333333333333333333333333333333333333333333 +0455 3333333333333333333333333333333333333333333333333333333 +0456 3333333333333333333333333333333333333333333333333333333 +0457 3333333333333333333333333333333333333333333333333333333 +0458 3333333333333333333333333333333333333333333333333333333 +0459 3333333333333333333333333333333333333333333333333333333 +0460 3333333333333333333333333333333333333333333333333333333 +0461 3333333333333333333333333333333333333333333333333333333 +0462 3333333333333333333333333333333333333333333333333333333 +0463 3333333333333333333333333333333333333333333333333333333 +0464 3333333333333333333333333333333333333333333333333333333 +0465 3333333333333333333333333333333333333333333333333333333 +0466 3333333333333333333333333333333333333333333333333333333 +0467 3333333333333333333333333333333333333333333333333333333 +0468 3333333333333333333333333333333333333333333333333333333 +0469 3333333333333333333333333333333333333333333333333333333 +0470 3333333333333333333333333333333333333333333333333333333 +0471 3333333333333333333333333333333333333333333333333333333 +0472 3333333333333333333333333333333333333333333333333333333 +0473 3333333333333333333333333333333333333333333333333333333 +0474 3333333333333333333333333333333333333333333333333333333 +0475 3333333333333333333333333333333333333333333333333333333 +0476 3333333333333333333333333333333333333333333333333333333 +0477 3333333333333333333333333333333333333333333333333333333 +0478 3333333333333333333333333333333333333333333333333333333 +0479 3333333333333333333333333333333333333333333333333333333 +0480 3333333333333333333333333333333333333333333333333333333 +0481 3333333333333333333333333333333333333333333333333333333 +0482 3333333333333333333333333333333333333333333333333333333 +0483 3333333333333333333333333333333333333333333333333333333 +0484 3333333333333333333333333333333333333333333333333333333 +0485 3333333333333333333333333333333333333333333333333333333 +0486 3333333333333333333333333333333333333333333333333333333 +0487 3333333333333333333333333333333333333333333333333333333 +0488 3333333333333333333333333333333333333333333333333333333 +0489 3333333333333333333333333333333333333333333333333333333 +0490 3333333333333333333333333333333333333333333333333333333 +0491 3333333333333333333333333333333333333333333333333333333 +0492 3333333333333333333333333333333333333333333333333333333 +0493 3333333333333333333333333333333333333333333333333333333 +0494 3333333333333333333333333333333333333333333333333333333 +0495 3333333333333333333333333333333333333333333333333333333 +0496 3333333333333333333333333333333333333333333333333333333 +0497 3333333333333333333333333333333333333333333333333333333 +0498 3333333333333333333333333333333333333333333333333333333 +0499 3333333333333333333333333333333333333333333333333333333 +0500 3333333333333333333333333333333333333333333333333333333 +0501 3333333333333333333333333333333333333333333333333333333 +0502 3333333333333333333333333333333333333333333333333333333 +0503 3333333333333333333333333333333333333333333333333333333 +0504 3333333333333333333333333333333333333333333333333333333 +0505 3333333333333333333333333333333333333333333333333333333 +0506 3333333333333333333333333333333333333333333333333333333 +0507 3333333333333333333333333333333333333333333333333333333 +0508 3333333333333333333333333333333333333333333333333333333 +0509 3333333333333333333333333333333333333333333333333333333 +0510 3333333333333333333333333333333333333333333333333333333 +0511 3333333333333333333333333333333333333333333333333333333 +0512 3333333333333333333333333333333333333333333333333333333 +0513 3333333333333333333333333333333333333333333333333333333 +0514 3333333333333333333333333333333333333333333333333333333 +0515 3333333333333333333333333333333333333333333333333333333 +0516 3333333333333333333333333333333333333333333333333333333 +0517 3333333333333333333333333333333333333333333333333333333 +0518 3333333333333333333333333333333333333333333333333333333 +0519 3333333333333333333333333333333333333333333333333333333 +0520 3333333333333333333333333333333333333333333333333333333 +0521 3333333333333333333333333333333333333333333333333333333 +0522 3333333333333333333333333333333333333333333333333333333 +0523 3333333333333333333333333333333333333333333333333333333 +0524 3333333333333333333333333333333333333333333333333333333 +0525 3333333333333333333333333333333333333333333333333333333 +0526 3333333333333333333333333333333333333333333333333333333 +0527 3333333333333333333333333333333333333333333333333333333 +0528 3333333333333333333333333333333333333333333333333333333 +0529 3333333333333333333333333333333333333333333333333333333 +0530 3333333333333333333333333333333333333333333333333333333 +0531 3333333333333333333333333333333333333333333333333333333 +0532 3333333333333333333333333333333333333333333333333333333 +0533 3333333333333333333333333333333333333333333333333333333 +0534 3333333333333333333333333333333333333333333333333333333 +0535 3333333333333333333333333333333333333333333333333333333 +0536 3333333333333333333333333333333333333333333333333333333 +0537 3333333333333333333333333333333333333333333333333333333 +0538 3333333333333333333333333333333333333333333333333333333 +0539 3333333333333333333333333333333333333333333333333333333 +0540 3333333333333333333333333333333333333333333333333333333 +0541 3333333333333333333333333333333333333333333333333333333 +0542 3333333333333333333333333333333333333333333333333333333 +0543 3333333333333333333333333333333333333333333333333333333 +0544 3333333333333333333333333333333333333333333333333333333 +0545 3333333333333333333333333333333333333333333333333333333 +0546 3333333333333333333333333333333333333333333333333333333 +0547 3333333333333333333333333333333333333333333333333333333 +0548 3333333333333333333333333333333333333333333333333333333 +0549 3333333333333333333333333333333333333333333333333333333 +0550 3333333333333333333333333333333333333333333333333333333 +0551 3333333333333333333333333333333333333333333333333333333 +0552 3333333333333333333333333333333333333333333333333333333 +0553 3333333333333333333333333333333333333333333333333333333 +0554 3333333333333333333333333333333333333333333333333333333 +0555 3333333333333333333333333333333333333333333333333333333 +0556 3333333333333333333333333333333333333333333333333333333 +0557 3333333333333333333333333333333333333333333333333333333 +0558 3333333333333333333333333333333333333333333333333333333 +0559 3333333333333333333333333333333333333333333333333333333 +0560 3333333333333333333333333333333333333333333333333333333 +0561 3333333333333333333333333333333333333333333333333333333 +0562 3333333333333333333333333333333333333333333333333333333 +0563 3333333333333333333333333333333333333333333333333333333 +0564 3333333333333333333333333333333333333333333333333333333 +0565 3333333333333333333333333333333333333333333333333333333 +0566 3333333333333333333333333333333333333333333333333333333 +0567 3333333333333333333333333333333333333333333333333333333 +0568 3333333333333333333333333333333333333333333333333333333 +0569 3333333333333333333333333333333333333333333333333333333 +0570 3333333333333333333333333333333333333333333333333333333 +0571 3333333333333333333333333333333333333333333333333333333 +0572 3333333333333333333333333333333333333333333333333333333 +0573 3333333333333333333333333333333333333333333333333333333 +0574 3333333333333333333333333333333333333333333333333333333 +0575 3333333333333333333333333333333333333333333333333333333 +0576 3333333333333333333333333333333333333333333333333333333 +0577 3333333333333333333333333333333333333333333333333333333 +0578 3333333333333333333333333333333333333333333333333333333 +0579 3333333333333333333333333333333333333333333333333333333 +0580 3333333333333333333333333333333333333333333333333333333 +0581 3333333333333333333333333333333333333333333333333333333 +0582 3333333333333333333333333333333333333333333333333333333 +0583 3333333333333333333333333333333333333333333333333333333 +0584 3333333333333333333333333333333333333333333333333333333 +0585 3333333333333333333333333333333333333333333333333333333 +0586 3333333333333333333333333333333333333333333333333333333 +0587 3333333333333333333333333333333333333333333333333333333 +0588 3333333333333333333333333333333333333333333333333333333 +0589 3333333333333333333333333333333333333333333333333333333 +0590 3333333333333333333333333333333333333333333333333333333 +0591 3333333333333333333333333333333333333333333333333333333 +0592 3333333333333333333333333333333333333333333333333333333 +0593 3333333333333333333333333333333333333333333333333333333 +0594 3333333333333333333333333333333333333333333333333333333 +0595 3333333333333333333333333333333333333333333333333333333 +0596 3333333333333333333333333333333333333333333333333333333 +0597 3333333333333333333333333333333333333333333333333333333 +0598 3333333333333333333333333333333333333333333333333333333 +0599 3333333333333333333333333333333333333333333333333333333 +0600 3333333333333333333333333333333333333333333333333333333 +0601 3333333333333333333333333333333333333333333333333333333 +0602 3333333333333333333333333333333333333333333333333333333 +0603 3333333333333333333333333333333333333333333333333333333 +0604 3333333333333333333333333333333333333333333333333333333 +0605 3333333333333333333333333333333333333333333333333333333 +0606 3333333333333333333333333333333333333333333333333333333 +0607 3333333333333333333333333333333333333333333333333333333 +0608 3333333333333333333333333333333333333333333333333333333 +0609 3333333333333333333333333333333333333333333333333333333 +0610 3333333333333333333333333333333333333333333333333333333 +0611 3333333333333333333333333333333333333333333333333333333 +0612 3333333333333333333333333333333333333333333333333333333 +0613 3333333333333333333333333333333333333333333333333333333 +0614 3333333333333333333333333333333333333333333333333333333 +0615 3333333333333333333333333333333333333333333333333333333 +0616 3333333333333333333333333333333333333333333333333333333 +0617 3333333333333333333333333333333333333333333333333333333 +0618 3333333333333333333333333333333333333333333333333333333 +0619 3333333333333333333333333333333333333333333333333333333 +0620 3333333333333333333333333333333333333333333333333333333 +0621 3333333333333333333333333333333333333333333333333333333 +0622 3333333333333333333333333333333333333333333333333333333 +0623 3333333333333333333333333333333333333333333333333333333 +0624 3333333333333333333333333333333333333333333333333333333 +0625 3333333333333333333333333333333333333333333333333333333 +0626 3333333333333333333333333333333333333333333333333333333 +0627 3333333333333333333333333333333333333333333333333333333 +0628 3333333333333333333333333333333333333333333333333333333 +0629 3333333333333333333333333333333333333333333333333333333 +0630 3333333333333333333333333333333333333333333333333333333 +0631 3333333333333333333333333333333333333333333333333333333 +0632 3333333333333333333333333333333333333333333333333333333 +0633 3333333333333333333333333333333333333333333333333333333 +0634 3333333333333333333333333333333333333333333333333333333 +0635 3333333333333333333333333333333333333333333333333333333 +0636 3333333333333333333333333333333333333333333333333333333 +0637 3333333333333333333333333333333333333333333333333333333 +0638 3333333333333333333333333333333333333333333333333333333 +0639 3333333333333333333333333333333333333333333333333333333 +0640 3333333333333333333333333333333333333333333333333333333 +0641 3333333333333333333333333333333333333333333333333333333 +0642 3333333333333333333333333333333333333333333333333333333 +0643 3333333333333333333333333333333333333333333333333333333 +0644 3333333333333333333333333333333333333333333333333333333 +0645 3333333333333333333333333333333333333333333333333333333 +0646 3333333333333333333333333333333333333333333333333333333 +0647 3333333333333333333333333333333333333333333333333333333 +0648 3333333333333333333333333333333333333333333333333333333 +0649 3333333333333333333333333333333333333333333333333333333 +0650 3333333333333333333333333333333333333333333333333333333 +0651 3333333333333333333333333333333333333333333333333333333 +0652 3333333333333333333333333333333333333333333333333333333 +0653 3333333333333333333333333333333333333333333333333333333 +0654 3333333333333333333333333333333333333333333333333333333 +0655 3333333333333333333333333333333333333333333333333333333 +0656 3333333333333333333333333333333333333333333333333333333 +0657 3333333333333333333333333333333333333333333333333333333 +0658 3333333333333333333333333333333333333333333333333333333 +0659 3333333333333333333333333333333333333333333333333333333 +0660 3333333333333333333333333333333333333333333333333333333 +0661 3333333333333333333333333333333333333333333333333333333 +0662 3333333333333333333333333333333333333333333333333333333 +0663 3333333333333333333333333333333333333333333333333333333 +0664 3333333333333333333333333333333333333333333333333333333 +0665 3333333333333333333333333333333333333333333333333333333 +0666 3333333333333333333333333333333333333333333333333333333 +0667 3333333333333333333333333333333333333333333333333333333 +0668 3333333333333333333333333333333333333333333333333333333 +0669 3333333333333333333333333333333333333333333333333333333 +0670 3333333333333333333333333333333333333333333333333333333 +0671 3333333333333333333333333333333333333333333333333333333 +0672 3333333333333333333333333333333333333333333333333333333 +0673 3333333333333333333333333333333333333333333333333333333 +0674 3333333333333333333333333333333333333333333333333333333 +0675 3333333333333333333333333333333333333333333333333333333 +0676 3333333333333333333333333333333333333333333333333333333 +0677 3333333333333333333333333333333333333333333333333333333 +0678 3333333333333333333333333333333333333333333333333333333 +0679 3333333333333333333333333333333333333333333333333333333 +0680 3333333333333333333333333333333333333333333333333333333 +0681 3333333333333333333333333333333333333333333333333333333 +0682 3333333333333333333333333333333333333333333333333333333 +0683 3333333333333333333333333333333333333333333333333333333 +0684 3333333333333333333333333333333333333333333333333333333 +0685 3333333333333333333333333333333333333333333333333333333 +0686 3333333333333333333333333333333333333333333333333333333 +0687 3333333333333333333333333333333333333333333333333333333 +0688 3333333333333333333333333333333333333333333333333333333 +0689 3333333333333333333333333333333333333333333333333333333 +0690 3333333333333333333333333333333333333333333333333333333 +0691 3333333333333333333333333333333333333333333333333333333 +0692 3333333333333333333333333333333333333333333333333333333 +0693 3333333333333333333333333333333333333333333333333333333 +0694 3333333333333333333333333333333333333333333333333333333 +0695 3333333333333333333333333333333333333333333333333333333 +0696 3333333333333333333333333333333333333333333333333333333 +0697 3333333333333333333333333333333333333333333333333333333 +0698 3333333333333333333333333333333333333333333333333333333 +0699 3333333333333333333333333333333333333333333333333333333 +0700 3333333333333333333333333333333333333333333333333333333 +0701 3333333333333333333333333333333333333333333333333333333 +0702 3333333333333333333333333333333333333333333333333333333 +0703 3333333333333333333333333333333333333333333333333333333 +0704 3333333333333333333333333333333333333333333333333333333 +0705 3333333333333333333333333333333333333333333333333333333 +0706 3333333333333333333333333333333333333333333333333333333 +0707 3333333333333333333333333333333333333333333333333333333 +0708 3333333333333333333333333333333333333333333333333333333 +0709 3333333333333333333333333333333333333333333333333333333 +0710 3333333333333333333333333333333333333333333333333333333 +0711 3333333333333333333333333333333333333333333333333333333 +0712 3333333333333333333333333333333333333333333333333333333 +0713 3333333333333333333333333333333333333333333333333333333 +0714 3333333333333333333333333333333333333333333333333333333 +0715 3333333333333333333333333333333333333333333333333333333 +0716 3333333333333333333333333333333333333333333333333333333 +0717 3333333333333333333333333333333333333333333333333333333 +0718 3333333333333333333333333333333333333333333333333333333 +0719 3333333333333333333333333333333333333333333333333333333 +0720 3333333333333333333333333333333333333333333333333333333 +0721 3333333333333333333333333333333333333333333333333333333 +0722 3333333333333333333333333333333333333333333333333333333 +0723 3333333333333333333333333333333333333333333333333333333 +0724 3333333333333333333333333333333333333333333333333333333 +0725 3333333333333333333333333333333333333333333333333333333 +0726 3333333333333333333333333333333333333333333333333333333 +0727 3333333333333333333333333333333333333333333333333333333 +0728 3333333333333333333333333333333333333333333333333333333 +0729 3333333333333333333333333333333333333333333333333333333 +0730 3333333333333333333333333333333333333333333333333333333 +0731 3333333333333333333333333333333333333333333333333333333 +0732 3333333333333333333333333333333333333333333333333333333 +0733 3333333333333333333333333333333333333333333333333333333 +0734 3333333333333333333333333333333333333333333333333333333 +0735 3333333333333333333333333333333333333333333333333333333 +0736 3333333333333333333333333333333333333333333333333333333 +0737 3333333333333333333333333333333333333333333333333333333 +0738 3333333333333333333333333333333333333333333333333333333 +0739 3333333333333333333333333333333333333333333333333333333 +0740 3333333333333333333333333333333333333333333333333333333 +0741 3333333333333333333333333333333333333333333333333333333 +0742 3333333333333333333333333333333333333333333333333333333 +0743 3333333333333333333333333333333333333333333333333333333 +0744 3333333333333333333333333333333333333333333333333333333 +0745 3333333333333333333333333333333333333333333333333333333 +0746 3333333333333333333333333333333333333333333333333333333 +0747 3333333333333333333333333333333333333333333333333333333 +0748 3333333333333333333333333333333333333333333333333333333 +0749 3333333333333333333333333333333333333333333333333333333 +0750 3333333333333333333333333333333333333333333333333333333 +0751 3333333333333333333333333333333333333333333333333333333 +0752 3333333333333333333333333333333333333333333333333333333 +0753 3333333333333333333333333333333333333333333333333333333 +0754 3333333333333333333333333333333333333333333333333333333 +0755 3333333333333333333333333333333333333333333333333333333 +0756 3333333333333333333333333333333333333333333333333333333 +0757 3333333333333333333333333333333333333333333333333333333 +0758 3333333333333333333333333333333333333333333333333333333 +0759 3333333333333333333333333333333333333333333333333333333 +0760 3333333333333333333333333333333333333333333333333333333 +0761 3333333333333333333333333333333333333333333333333333333 +0762 3333333333333333333333333333333333333333333333333333333 +0763 3333333333333333333333333333333333333333333333333333333 +0764 3333333333333333333333333333333333333333333333333333333 +0765 3333333333333333333333333333333333333333333333333333333 +0766 3333333333333333333333333333333333333333333333333333333 +0767 3333333333333333333333333333333333333333333333333333333 +0768 3333333333333333333333333333333333333333333333333333333 +0769 3333333333333333333333333333333333333333333333333333333 +0770 3333333333333333333333333333333333333333333333333333333 +0771 3333333333333333333333333333333333333333333333333333333 +0772 3333333333333333333333333333333333333333333333333333333 +0773 3333333333333333333333333333333333333333333333333333333 +0774 3333333333333333333333333333333333333333333333333333333 +0775 3333333333333333333333333333333333333333333333333333333 +0776 3333333333333333333333333333333333333333333333333333333 +0777 3333333333333333333333333333333333333333333333333333333 +0778 3333333333333333333333333333333333333333333333333333333 +0779 3333333333333333333333333333333333333333333333333333333 +0780 3333333333333333333333333333333333333333333333333333333 +0781 3333333333333333333333333333333333333333333333333333333 +0782 3333333333333333333333333333333333333333333333333333333 +0783 3333333333333333333333333333333333333333333333333333333 +0784 3333333333333333333333333333333333333333333333333333333 +0785 3333333333333333333333333333333333333333333333333333333 +0786 3333333333333333333333333333333333333333333333333333333 +0787 3333333333333333333333333333333333333333333333333333333 +0788 3333333333333333333333333333333333333333333333333333333 +0789 3333333333333333333333333333333333333333333333333333333 +0790 3333333333333333333333333333333333333333333333333333333 +0791 3333333333333333333333333333333333333333333333333333333 +0792 3333333333333333333333333333333333333333333333333333333 +0793 3333333333333333333333333333333333333333333333333333333 +0794 3333333333333333333333333333333333333333333333333333333 +0795 3333333333333333333333333333333333333333333333333333333 +0796 3333333333333333333333333333333333333333333333333333333 +0797 3333333333333333333333333333333333333333333333333333333 +0798 3333333333333333333333333333333333333333333333333333333 +0799 3333333333333333333333333333333333333333333333333333333 +0800 3333333333333333333333333333333333333333333333333333333 +0801 3333333333333333333333333333333333333333333333333333333 +0802 3333333333333333333333333333333333333333333333333333333 +0803 3333333333333333333333333333333333333333333333333333333 +0804 3333333333333333333333333333333333333333333333333333333 +0805 3333333333333333333333333333333333333333333333333333333 +0806 3333333333333333333333333333333333333333333333333333333 +0807 3333333333333333333333333333333333333333333333333333333 +0808 3333333333333333333333333333333333333333333333333333333 +0809 3333333333333333333333333333333333333333333333333333333 +0810 3333333333333333333333333333333333333333333333333333333 +0811 3333333333333333333333333333333333333333333333333333333 +0812 3333333333333333333333333333333333333333333333333333333 +0813 3333333333333333333333333333333333333333333333333333333 +0814 3333333333333333333333333333333333333333333333333333333 +0815 3333333333333333333333333333333333333333333333333333333 +0816 3333333333333333333333333333333333333333333333333333333 +0817 3333333333333333333333333333333333333333333333333333333 +0818 3333333333333333333333333333333333333333333333333333333 +0819 3333333333333333333333333333333333333333333333333333333 +0820 3333333333333333333333333333333333333333333333333333333 +0821 3333333333333333333333333333333333333333333333333333333 +0822 3333333333333333333333333333333333333333333333333333333 +0823 3333333333333333333333333333333333333333333333333333333 +0824 3333333333333333333333333333333333333333333333333333333 +0825 3333333333333333333333333333333333333333333333333333333 +0826 3333333333333333333333333333333333333333333333333333333 +0827 3333333333333333333333333333333333333333333333333333333 +0828 3333333333333333333333333333333333333333333333333333333 +0829 3333333333333333333333333333333333333333333333333333333 +0830 3333333333333333333333333333333333333333333333333333333 +0831 3333333333333333333333333333333333333333333333333333333 +0832 3333333333333333333333333333333333333333333333333333333 +0833 3333333333333333333333333333333333333333333333333333333 +0834 3333333333333333333333333333333333333333333333333333333 +0835 3333333333333333333333333333333333333333333333333333333 +0836 3333333333333333333333333333333333333333333333333333333 +0837 3333333333333333333333333333333333333333333333333333333 +0838 3333333333333333333333333333333333333333333333333333333 +0839 3333333333333333333333333333333333333333333333333333333 +0840 3333333333333333333333333333333333333333333333333333333 +0841 3333333333333333333333333333333333333333333333333333333 +0842 3333333333333333333333333333333333333333333333333333333 +0843 3333333333333333333333333333333333333333333333333333333 +0844 3333333333333333333333333333333333333333333333333333333 +0845 3333333333333333333333333333333333333333333333333333333 +0846 3333333333333333333333333333333333333333333333333333333 +0847 3333333333333333333333333333333333333333333333333333333 +0848 3333333333333333333333333333333333333333333333333333333 +0849 3333333333333333333333333333333333333333333333333333333 +0850 3333333333333333333333333333333333333333333333333333333 +0851 3333333333333333333333333333333333333333333333333333333 +0852 3333333333333333333333333333333333333333333333333333333 +0853 3333333333333333333333333333333333333333333333333333333 +0854 3333333333333333333333333333333333333333333333333333333 +0855 3333333333333333333333333333333333333333333333333333333 +0856 3333333333333333333333333333333333333333333333333333333 +0857 3333333333333333333333333333333333333333333333333333333 +0858 3333333333333333333333333333333333333333333333333333333 +0859 3333333333333333333333333333333333333333333333333333333 +0860 3333333333333333333333333333333333333333333333333333333 +0861 3333333333333333333333333333333333333333333333333333333 +0862 3333333333333333333333333333333333333333333333333333333 +0863 3333333333333333333333333333333333333333333333333333333 +0864 3333333333333333333333333333333333333333333333333333333 +0865 3333333333333333333333333333333333333333333333333333333 +0866 3333333333333333333333333333333333333333333333333333333 +0867 3333333333333333333333333333333333333333333333333333333 +0868 3333333333333333333333333333333333333333333333333333333 +0869 3333333333333333333333333333333333333333333333333333333 +0870 3333333333333333333333333333333333333333333333333333333 +0871 3333333333333333333333333333333333333333333333333333333 +0872 3333333333333333333333333333333333333333333333333333333 +0873 3333333333333333333333333333333333333333333333333333333 +0874 3333333333333333333333333333333333333333333333333333333 +0875 3333333333333333333333333333333333333333333333333333333 +0876 3333333333333333333333333333333333333333333333333333333 +0877 3333333333333333333333333333333333333333333333333333333 +0878 3333333333333333333333333333333333333333333333333333333 +0879 3333333333333333333333333333333333333333333333333333333 +0880 3333333333333333333333333333333333333333333333333333333 +0881 3333333333333333333333333333333333333333333333333333333 +0882 3333333333333333333333333333333333333333333333333333333 +0883 3333333333333333333333333333333333333333333333333333333 +0884 3333333333333333333333333333333333333333333333333333333 +0885 3333333333333333333333333333333333333333333333333333333 +0886 3333333333333333333333333333333333333333333333333333333 +0887 3333333333333333333333333333333333333333333333333333333 +0888 3333333333333333333333333333333333333333333333333333333 +0889 3333333333333333333333333333333333333333333333333333333 +0890 3333333333333333333333333333333333333333333333333333333 +0891 3333333333333333333333333333333333333333333333333333333 +0892 3333333333333333333333333333333333333333333333333333333 +0893 3333333333333333333333333333333333333333333333333333333 +0894 3333333333333333333333333333333333333333333333333333333 +0895 3333333333333333333333333333333333333333333333333333333 +0896 3333333333333333333333333333333333333333333333333333333 +0897 3333333333333333333333333333333333333333333333333333333 +0898 3333333333333333333333333333333333333333333333333333333 +0899 3333333333333333333333333333333333333333333333333333333 +0900 3333333333333333333333333333333333333333333333333333333 +0901 3333333333333333333333333333333333333333333333333333333 +0902 3333333333333333333333333333333333333333333333333333333 +0903 3333333333333333333333333333333333333333333333333333333 +0904 3333333333333333333333333333333333333333333333333333333 +0905 3333333333333333333333333333333333333333333333333333333 +0906 3333333333333333333333333333333333333333333333333333333 +0907 3333333333333333333333333333333333333333333333333333333 +0908 3333333333333333333333333333333333333333333333333333333 +0909 3333333333333333333333333333333333333333333333333333333 +0910 3333333333333333333333333333333333333333333333333333333 +0911 3333333333333333333333333333333333333333333333333333333 +0912 3333333333333333333333333333333333333333333333333333333 +0913 3333333333333333333333333333333333333333333333333333333 +0914 3333333333333333333333333333333333333333333333333333333 +0915 3333333333333333333333333333333333333333333333333333333 +0916 3333333333333333333333333333333333333333333333333333333 +0917 3333333333333333333333333333333333333333333333333333333 +0918 3333333333333333333333333333333333333333333333333333333 +0919 3333333333333333333333333333333333333333333333333333333 +0920 3333333333333333333333333333333333333333333333333333333 +0921 3333333333333333333333333333333333333333333333333333333 +0922 3333333333333333333333333333333333333333333333333333333 +0923 3333333333333333333333333333333333333333333333333333333 +0924 3333333333333333333333333333333333333333333333333333333 +0925 3333333333333333333333333333333333333333333333333333333 +0926 3333333333333333333333333333333333333333333333333333333 +0927 3333333333333333333333333333333333333333333333333333333 +0928 3333333333333333333333333333333333333333333333333333333 +0929 3333333333333333333333333333333333333333333333333333333 +0930 3333333333333333333333333333333333333333333333333333333 +0931 3333333333333333333333333333333333333333333333333333333 +0932 3333333333333333333333333333333333333333333333333333333 +0933 3333333333333333333333333333333333333333333333333333333 +0934 3333333333333333333333333333333333333333333333333333333 +0935 3333333333333333333333333333333333333333333333333333333 +0936 3333333333333333333333333333333333333333333333333333333 +0937 3333333333333333333333333333333333333333333333333333333 +0938 3333333333333333333333333333333333333333333333333333333 +0939 3333333333333333333333333333333333333333333333333333333 +0940 3333333333333333333333333333333333333333333333333333333 +0941 3333333333333333333333333333333333333333333333333333333 +0942 3333333333333333333333333333333333333333333333333333333 +0943 3333333333333333333333333333333333333333333333333333333 +0944 3333333333333333333333333333333333333333333333333333333 +0945 3333333333333333333333333333333333333333333333333333333 +0946 3333333333333333333333333333333333333333333333333333333 +0947 3333333333333333333333333333333333333333333333333333333 +0948 3333333333333333333333333333333333333333333333333333333 +0949 3333333333333333333333333333333333333333333333333333333 +0950 3333333333333333333333333333333333333333333333333333333 +0951 3333333333333333333333333333333333333333333333333333333 +0952 3333333333333333333333333333333333333333333333333333333 +0953 3333333333333333333333333333333333333333333333333333333 +0954 3333333333333333333333333333333333333333333333333333333 +0955 3333333333333333333333333333333333333333333333333333333 +0956 3333333333333333333333333333333333333333333333333333333 +0957 3333333333333333333333333333333333333333333333333333333 +0958 3333333333333333333333333333333333333333333333333333333 +0959 3333333333333333333333333333333333333333333333333333333 +0960 3333333333333333333333333333333333333333333333333333333 +0961 3333333333333333333333333333333333333333333333333333333 +0962 3333333333333333333333333333333333333333333333333333333 +0963 3333333333333333333333333333333333333333333333333333333 +0964 3333333333333333333333333333333333333333333333333333333 +0965 3333333333333333333333333333333333333333333333333333333 +0966 3333333333333333333333333333333333333333333333333333333 +0967 3333333333333333333333333333333333333333333333333333333 +0968 3333333333333333333333333333333333333333333333333333333 +0969 3333333333333333333333333333333333333333333333333333333 +0970 3333333333333333333333333333333333333333333333333333333 +0971 3333333333333333333333333333333333333333333333333333333 +0972 3333333333333333333333333333333333333333333333333333333 +0973 3333333333333333333333333333333333333333333333333333333 +0974 3333333333333333333333333333333333333333333333333333333 +0975 3333333333333333333333333333333333333333333333333333333 +0976 3333333333333333333333333333333333333333333333333333333 +0977 3333333333333333333333333333333333333333333333333333333 +0978 3333333333333333333333333333333333333333333333333333333 +0979 3333333333333333333333333333333333333333333333333333333 +0980 3333333333333333333333333333333333333333333333333333333 +0981 3333333333333333333333333333333333333333333333333333333 +0982 3333333333333333333333333333333333333333333333333333333 +0983 3333333333333333333333333333333333333333333333333333333 +0984 3333333333333333333333333333333333333333333333333333333 +0985 3333333333333333333333333333333333333333333333333333333 +0986 3333333333333333333333333333333333333333333333333333333 +0987 3333333333333333333333333333333333333333333333333333333 +0988 3333333333333333333333333333333333333333333333333333333 +0989 3333333333333333333333333333333333333333333333333333333 +0990 3333333333333333333333333333333333333333333333333333333 +0991 3333333333333333333333333333333333333333333333333333333 +0992 3333333333333333333333333333333333333333333333333333333 +0993 3333333333333333333333333333333333333333333333333333333 +0994 3333333333333333333333333333333333333333333333333333333 +0995 3333333333333333333333333333333333333333333333333333333 +0996 3333333333333333333333333333333333333333333333333333333 +0997 3333333333333333333333333333333333333333333333333333333 +0998 3333333333333333333333333333333333333333333333333333333 +0999 3333333333333333333333333333333333333333333333333333333 +1000 3333333333333333333333333333333333333333333333333333333 +1001 3333333333333333333333333333333333333333333333333333333 +1002 3333333333333333333333333333333333333333333333333333333 +1003 3333333333333333333333333333333333333333333333333333333 +1004 3333333333333333333333333333333333333333333333333333333 +1005 3333333333333333333333333333333333333333333333333333333 +1006 3333333333333333333333333333333333333333333333333333333 +1007 3333333333333333333333333333333333333333333333333333333 +1008 3333333333333333333333333333333333333333333333333333333 +1009 3333333333333333333333333333333333333333333333333333333 +1010 3333333333333333333333333333333333333333333333333333333 +1011 3333333333333333333333333333333333333333333333333333333 +1012 3333333333333333333333333333333333333333333333333333333 +1013 3333333333333333333333333333333333333333333333333333333 +1014 3333333333333333333333333333333333333333333333333333333 +1015 3333333333333333333333333333333333333333333333333333333 +1016 3333333333333333333333333333333333333333333333333333333 +1017 3333333333333333333333333333333333333333333333333333333 +1018 3333333333333333333333333333333333333333333333333333333 +1019 3333333333333333333333333333333333333333333333333333333 +1020 3333333333333333333333333333333333333333333333333333333 +1021 3333333333333333333333333333333333333333333333333333333 +1022 3333333333333333333333333333333333333333333333333333333 +1023 3333333333333333333333333333333333333333333333333333333 +1024 3333333333333333333333333333333333333333333333333333333 +1025 3333333333333333333333333333333333333333333333333333333 +1026 3333333333333333333333333333333333333333333333333333333 +1027 3333333333333333333333333333333333333333333333333333333 +1028 3333333333333333333333333333333333333333333333333333333 +1029 3333333333333333333333333333333333333333333333333333333 +1030 3333333333333333333333333333333333333333333333333333333 +1031 3333333333333333333333333333333333333333333333333333333 +1032 3333333333333333333333333333333333333333333333333333333 +1033 3333333333333333333333333333333333333333333333333333333 +1034 3333333333333333333333333333333333333333333333333333333 +1035 3333333333333333333333333333333333333333333333333333333 +1036 3333333333333333333333333333333333333333333333333333333 +1037 3333333333333333333333333333333333333333333333333333333 +1038 3333333333333333333333333333333333333333333333333333333 +1039 3333333333333333333333333333333333333333333333333333333 +1040 3333333333333333333333333333333333333333333333333333333 +1041 3333333333333333333333333333333333333333333333333333333 +1042 3333333333333333333333333333333333333333333333333333333 +1043 3333333333333333333333333333333333333333333333333333333 +1044 3333333333333333333333333333333333333333333333333333333 +1045 3333333333333333333333333333333333333333333333333333333 +1046 3333333333333333333333333333333333333333333333333333333 +1047 3333333333333333333333333333333333333333333333333333333 +1048 3333333333333333333333333333333333333333333333333333333 +1049 3333333333333333333333333333333333333333333333333333333 +1050 3333333333333333333333333333333333333333333333333333333 +1051 3333333333333333333333333333333333333333333333333333333 +1052 3333333333333333333333333333333333333333333333333333333 +1053 3333333333333333333333333333333333333333333333333333333 +1054 3333333333333333333333333333333333333333333333333333333 +1055 3333333333333333333333333333333333333333333333333333333 +1056 3333333333333333333333333333333333333333333333333333333 +1057 3333333333333333333333333333333333333333333333333333333 +1058 3333333333333333333333333333333333333333333333333333333 +1059 3333333333333333333333333333333333333333333333333333333 +1060 3333333333333333333333333333333333333333333333333333333 +1061 3333333333333333333333333333333333333333333333333333333 +1062 3333333333333333333333333333333333333333333333333333333 +1063 3333333333333333333333333333333333333333333333333333333 +1064 3333333333333333333333333333333333333333333333333333333 +1065 3333333333333333333333333333333333333333333333333333333 +1066 3333333333333333333333333333333333333333333333333333333 +1067 3333333333333333333333333333333333333333333333333333333 +1068 3333333333333333333333333333333333333333333333333333333 +1069 3333333333333333333333333333333333333333333333333333333 +1070 3333333333333333333333333333333333333333333333333333333 +1071 3333333333333333333333333333333333333333333333333333333 +1072 3333333333333333333333333333333333333333333333333333333 +1073 3333333333333333333333333333333333333333333333333333333 +1074 3333333333333333333333333333333333333333333333333333333 +1075 3333333333333333333333333333333333333333333333333333333 +1076 3333333333333333333333333333333333333333333333333333333 +1077 3333333333333333333333333333333333333333333333333333333 +1078 3333333333333333333333333333333333333333333333333333333 +1079 3333333333333333333333333333333333333333333333333333333 +1080 3333333333333333333333333333333333333333333333333333333 +1081 3333333333333333333333333333333333333333333333333333333 +1082 3333333333333333333333333333333333333333333333333333333 +1083 3333333333333333333333333333333333333333333333333333333 +1084 3333333333333333333333333333333333333333333333333333333 +1085 3333333333333333333333333333333333333333333333333333333 +1086 3333333333333333333333333333333333333333333333333333333 +1087 3333333333333333333333333333333333333333333333333333333 +1088 3333333333333333333333333333333333333333333333333333333 +1089 3333333333333333333333333333333333333333333333333333333 +1090 3333333333333333333333333333333333333333333333333333333 +1091 3333333333333333333333333333333333333333333333333333333 +1092 3333333333333333333333333333333333333333333333333333333 +1093 3333333333333333333333333333333333333333333333333333333 +1094 3333333333333333333333333333333333333333333333333333333 +1095 3333333333333333333333333333333333333333333333333333333 +1096 3333333333333333333333333333333333333333333333333333333 +1097 3333333333333333333333333333333333333333333333333333333 +1098 3333333333333333333333333333333333333333333333333333333 +1099 3333333333333333333333333333333333333333333333333333333 +1100 3333333333333333333333333333333333333333333333333333333 +1101 3333333333333333333333333333333333333333333333333333333 +1102 3333333333333333333333333333333333333333333333333333333 +1103 3333333333333333333333333333333333333333333333333333333 +1104 3333333333333333333333333333333333333333333333333333333 +1105 3333333333333333333333333333333333333333333333333333333 +1106 3333333333333333333333333333333333333333333333333333333 +1107 3333333333333333333333333333333333333333333333333333333 +1108 3333333333333333333333333333333333333333333333333333333 +1109 3333333333333333333333333333333333333333333333333333333 +1110 3333333333333333333333333333333333333333333333333333333 +1111 3333333333333333333333333333333333333333333333333333333 +1112 3333333333333333333333333333333333333333333333333333333 +1113 3333333333333333333333333333333333333333333333333333333 +1114 3333333333333333333333333333333333333333333333333333333 +1115 3333333333333333333333333333333333333333333333333333333 +1116 3333333333333333333333333333333333333333333333333333333 +1117 3333333333333333333333333333333333333333333333333333333 +1118 3333333333333333333333333333333333333333333333333333333 +1119 3333333333333333333333333333333333333333333333333333333 +1120 3333333333333333333333333333333333333333333333333333333 +1121 3333333333333333333333333333333333333333333333333333333 +1122 3333333333333333333333333333333333333333333333333333333 +1123 3333333333333333333333333333333333333333333333333333333 +1124 3333333333333333333333333333333333333333333333333333333 +1125 3333333333333333333333333333333333333333333333333333333 +1126 3333333333333333333333333333333333333333333333333333333 +1127 3333333333333333333333333333333333333333333333333333333 +1128 3333333333333333333333333333333333333333333333333333333 +1129 3333333333333333333333333333333333333333333333333333333 +1130 3333333333333333333333333333333333333333333333333333333 +1131 3333333333333333333333333333333333333333333333333333333 +1132 3333333333333333333333333333333333333333333333333333333 +1133 3333333333333333333333333333333333333333333333333333333 +1134 3333333333333333333333333333333333333333333333333333333 +1135 3333333333333333333333333333333333333333333333333333333 +1136 3333333333333333333333333333333333333333333333333333333 +1137 3333333333333333333333333333333333333333333333333333333 +1138 3333333333333333333333333333333333333333333333333333333 +1139 3333333333333333333333333333333333333333333333333333333 +1140 3333333333333333333333333333333333333333333333333333333 +1141 3333333333333333333333333333333333333333333333333333333 +1142 3333333333333333333333333333333333333333333333333333333 +1143 3333333333333333333333333333333333333333333333333333333 +1144 3333333333333333333333333333333333333333333333333333333 +1145 3333333333333333333333333333333333333333333333333333333 +1146 3333333333333333333333333333333333333333333333333333333 +1147 3333333333333333333333333333333333333333333333333333333 +1148 3333333333333333333333333333333333333333333333333333333 +1149 3333333333333333333333333333333333333333333333333333333 +1150 3333333333333333333333333333333333333333333333333333333 +1151 3333333333333333333333333333333333333333333333333333333 +1152 3333333333333333333333333333333333333333333333333333333 +1153 3333333333333333333333333333333333333333333333333333333 +1154 3333333333333333333333333333333333333333333333333333333 +1155 3333333333333333333333333333333333333333333333333333333 +1156 3333333333333333333333333333333333333333333333333333333 +1157 3333333333333333333333333333333333333333333333333333333 +1158 3333333333333333333333333333333333333333333333333333333 +1159 3333333333333333333333333333333333333333333333333333333 +1160 3333333333333333333333333333333333333333333333333333333 +1161 3333333333333333333333333333333333333333333333333333333 +1162 3333333333333333333333333333333333333333333333333333333 +1163 3333333333333333333333333333333333333333333333333333333 +1164 3333333333333333333333333333333333333333333333333333333 +1165 3333333333333333333333333333333333333333333333333333333 +1166 3333333333333333333333333333333333333333333333333333333 +1167 3333333333333333333333333333333333333333333333333333333 +1168 3333333333333333333333333333333333333333333333333333333 +1169 3333333333333333333333333333333333333333333333333333333 +1170 3333333333333333333333333333333333333333333333333333333 +1171 3333333333333333333333333333333333333333333333333333333 +1172 3333333333333333333333333333333333333333333333333333333 +1173 3333333333333333333333333333333333333333333333333333333 +1174 3333333333333333333333333333333333333333333333333333333 +1175 3333333333333333333333333333333333333333333333333333333 +1176 3333333333333333333333333333333333333333333333333333333 +1177 3333333333333333333333333333333333333333333333333333333 +1178 3333333333333333333333333333333333333333333333333333333 +1179 3333333333333333333333333333333333333333333333333333333 +1180 3333333333333333333333333333333333333333333333333333333 +1181 3333333333333333333333333333333333333333333333333333333 +1182 3333333333333333333333333333333333333333333333333333333 +1183 3333333333333333333333333333333333333333333333333333333 +1184 3333333333333333333333333333333333333333333333333333333 +1185 3333333333333333333333333333333333333333333333333333333 +1186 3333333333333333333333333333333333333333333333333333333 +1187 3333333333333333333333333333333333333333333333333333333 +1188 3333333333333333333333333333333333333333333333333333333 +1189 3333333333333333333333333333333333333333333333333333333 +1190 3333333333333333333333333333333333333333333333333333333 +1191 3333333333333333333333333333333333333333333333333333333 +1192 3333333333333333333333333333333333333333333333333333333 +1193 3333333333333333333333333333333333333333333333333333333 +1194 3333333333333333333333333333333333333333333333333333333 +1195 3333333333333333333333333333333333333333333333333333333 +1196 3333333333333333333333333333333333333333333333333333333 +1197 3333333333333333333333333333333333333333333333333333333 +1198 3333333333333333333333333333333333333333333333333333333 +1199 3333333333333333333333333333333333333333333333333333333 +1200 3333333333333333333333333333333333333333333333333333333 +1201 3333333333333333333333333333333333333333333333333333333 +1202 3333333333333333333333333333333333333333333333333333333 +1203 3333333333333333333333333333333333333333333333333333333 +1204 3333333333333333333333333333333333333333333333333333333 +1205 3333333333333333333333333333333333333333333333333333333 +1206 3333333333333333333333333333333333333333333333333333333 +1207 3333333333333333333333333333333333333333333333333333333 +1208 3333333333333333333333333333333333333333333333333333333 +1209 3333333333333333333333333333333333333333333333333333333 +1210 3333333333333333333333333333333333333333333333333333333 +1211 3333333333333333333333333333333333333333333333333333333 +1212 3333333333333333333333333333333333333333333333333333333 +1213 3333333333333333333333333333333333333333333333333333333 +1214 3333333333333333333333333333333333333333333333333333333 +1215 3333333333333333333333333333333333333333333333333333333 +1216 3333333333333333333333333333333333333333333333333333333 +1217 3333333333333333333333333333333333333333333333333333333 +1218 3333333333333333333333333333333333333333333333333333333 +1219 3333333333333333333333333333333333333333333333333333333 +1220 3333333333333333333333333333333333333333333333333333333 +1221 3333333333333333333333333333333333333333333333333333333 +1222 3333333333333333333333333333333333333333333333333333333 +1223 3333333333333333333333333333333333333333333333333333333 +1224 3333333333333333333333333333333333333333333333333333333 +1225 3333333333333333333333333333333333333333333333333333333 +1226 3333333333333333333333333333333333333333333333333333333 +1227 3333333333333333333333333333333333333333333333333333333 +1228 3333333333333333333333333333333333333333333333333333333 +1229 3333333333333333333333333333333333333333333333333333333 +1230 3333333333333333333333333333333333333333333333333333333 +1231 3333333333333333333333333333333333333333333333333333333 +1232 3333333333333333333333333333333333333333333333333333333 +1233 3333333333333333333333333333333333333333333333333333333 +1234 3333333333333333333333333333333333333333333333333333333 +1235 3333333333333333333333333333333333333333333333333333333 +1236 3333333333333333333333333333333333333333333333333333333 +1237 3333333333333333333333333333333333333333333333333333333 +1238 3333333333333333333333333333333333333333333333333333333 +1239 3333333333333333333333333333333333333333333333333333333 +1240 3333333333333333333333333333333333333333333333333333333 +1241 3333333333333333333333333333333333333333333333333333333 +1242 3333333333333333333333333333333333333333333333333333333 +1243 3333333333333333333333333333333333333333333333333333333 +1244 3333333333333333333333333333333333333333333333333333333 +1245 3333333333333333333333333333333333333333333333333333333 +1246 3333333333333333333333333333333333333333333333333333333 +1247 3333333333333333333333333333333333333333333333333333333 +1248 3333333333333333333333333333333333333333333333333333333 +1249 3333333333333333333333333333333333333333333333333333333 +1250 3333333333333333333333333333333333333333333333333333333 +1251 3333333333333333333333333333333333333333333333333333333 +1252 3333333333333333333333333333333333333333333333333333333 +1253 3333333333333333333333333333333333333333333333333333333 +1254 3333333333333333333333333333333333333333333333333333333 +1255 3333333333333333333333333333333333333333333333333333333 +1256 3333333333333333333333333333333333333333333333333333333 +1257 3333333333333333333333333333333333333333333333333333333 +1258 3333333333333333333333333333333333333333333333333333333 +1259 3333333333333333333333333333333333333333333333333333333 +1260 3333333333333333333333333333333333333333333333333333333 +1261 3333333333333333333333333333333333333333333333333333333 +1262 3333333333333333333333333333333333333333333333333333333 +1263 3333333333333333333333333333333333333333333333333333333 +1264 3333333333333333333333333333333333333333333333333333333 +1265 3333333333333333333333333333333333333333333333333333333 +1266 3333333333333333333333333333333333333333333333333333333 +1267 3333333333333333333333333333333333333333333333333333333 +1268 3333333333333333333333333333333333333333333333333333333 +1269 3333333333333333333333333333333333333333333333333333333 +1270 3333333333333333333333333333333333333333333333333333333 +1271 3333333333333333333333333333333333333333333333333333333 +1272 3333333333333333333333333333333333333333333333333333333 +1273 3333333333333333333333333333333333333333333333333333333 +1274 3333333333333333333333333333333333333333333333333333333 +1275 3333333333333333333333333333333333333333333333333333333 +1276 3333333333333333333333333333333333333333333333333333333 +1277 3333333333333333333333333333333333333333333333333333333 +1278 3333333333333333333333333333333333333333333333333333333 +1279 3333333333333333333333333333333333333333333333333333333 +1280 3333333333333333333333333333333333333333333333333333333 +1281 3333333333333333333333333333333333333333333333333333333 +1282 3333333333333333333333333333333333333333333333333333333 +1283 3333333333333333333333333333333333333333333333333333333 +1284 3333333333333333333333333333333333333333333333333333333 +1285 3333333333333333333333333333333333333333333333333333333 +1286 3333333333333333333333333333333333333333333333333333333 +1287 3333333333333333333333333333333333333333333333333333333 +1288 3333333333333333333333333333333333333333333333333333333 +1289 3333333333333333333333333333333333333333333333333333333 +1290 3333333333333333333333333333333333333333333333333333333 +1291 3333333333333333333333333333333333333333333333333333333 +1292 3333333333333333333333333333333333333333333333333333333 +1293 3333333333333333333333333333333333333333333333333333333 +1294 3333333333333333333333333333333333333333333333333333333 +1295 3333333333333333333333333333333333333333333333333333333 +1296 3333333333333333333333333333333333333333333333333333333 +1297 3333333333333333333333333333333333333333333333333333333 +1298 3333333333333333333333333333333333333333333333333333333 +1299 3333333333333333333333333333333333333333333333333333333 +1300 3333333333333333333333333333333333333333333333333333333 +1301 3333333333333333333333333333333333333333333333333333333 +1302 3333333333333333333333333333333333333333333333333333333 +1303 3333333333333333333333333333333333333333333333333333333 +1304 3333333333333333333333333333333333333333333333333333333 +1305 3333333333333333333333333333333333333333333333333333333 +1306 3333333333333333333333333333333333333333333333333333333 +1307 3333333333333333333333333333333333333333333333333333333 +1308 3333333333333333333333333333333333333333333333333333333 +1309 3333333333333333333333333333333333333333333333333333333 +1310 3333333333333333333333333333333333333333333333333333333 +1311 3333333333333333333333333333333333333333333333333333333 +1312 3333333333333333333333333333333333333333333333333333333 +1313 3333333333333333333333333333333333333333333333333333333 +1314 3333333333333333333333333333333333333333333333333333333 +1315 3333333333333333333333333333333333333333333333333333333 +1316 3333333333333333333333333333333333333333333333333333333 +1317 3333333333333333333333333333333333333333333333333333333 +1318 3333333333333333333333333333333333333333333333333333333 +1319 3333333333333333333333333333333333333333333333333333333 +1320 3333333333333333333333333333333333333333333333333333333 +1321 3333333333333333333333333333333333333333333333333333333 +1322 3333333333333333333333333333333333333333333333333333333 +1323 3333333333333333333333333333333333333333333333333333333 +1324 3333333333333333333333333333333333333333333333333333333 +1325 3333333333333333333333333333333333333333333333333333333 +1326 3333333333333333333333333333333333333333333333333333333 +1327 3333333333333333333333333333333333333333333333333333333 +1328 3333333333333333333333333333333333333333333333333333333 +1329 3333333333333333333333333333333333333333333333333333333 +1330 3333333333333333333333333333333333333333333333333333333 +1331 3333333333333333333333333333333333333333333333333333333 +1332 3333333333333333333333333333333333333333333333333333333 +1333 3333333333333333333333333333333333333333333333333333333 +1334 3333333333333333333333333333333333333333333333333333333 +1335 3333333333333333333333333333333333333333333333333333333 +1336 3333333333333333333333333333333333333333333333333333333 +1337 3333333333333333333333333333333333333333333333333333333 +1338 3333333333333333333333333333333333333333333333333333333 +1339 3333333333333333333333333333333333333333333333333333333 +1340 3333333333333333333333333333333333333333333333333333333 +1341 3333333333333333333333333333333333333333333333333333333 +1342 3333333333333333333333333333333333333333333333333333333 +1343 3333333333333333333333333333333333333333333333333333333 +1344 3333333333333333333333333333333333333333333333333333333 +1345 3333333333333333333333333333333333333333333333333333333 +1346 3333333333333333333333333333333333333333333333333333333 +1347 3333333333333333333333333333333333333333333333333333333 +1348 3333333333333333333333333333333333333333333333333333333 +1349 3333333333333333333333333333333333333333333333333333333 +1350 3333333333333333333333333333333333333333333333333333333 +1351 3333333333333333333333333333333333333333333333333333333 +1352 3333333333333333333333333333333333333333333333333333333 +1353 3333333333333333333333333333333333333333333333333333333 +1354 3333333333333333333333333333333333333333333333333333333 +1355 3333333333333333333333333333333333333333333333333333333 +1356 3333333333333333333333333333333333333333333333333333333 +1357 3333333333333333333333333333333333333333333333333333333 +1358 3333333333333333333333333333333333333333333333333333333 +1359 3333333333333333333333333333333333333333333333333333333 +1360 3333333333333333333333333333333333333333333333333333333 +1361 3333333333333333333333333333333333333333333333333333333 +1362 3333333333333333333333333333333333333333333333333333333 +1363 3333333333333333333333333333333333333333333333333333333 +1364 3333333333333333333333333333333333333333333333333333333 +1365 3333333333333333333333333333333333333333333333333333333 +1366 3333333333333333333333333333333333333333333333333333333 +1367 3333333333333333333333333333333333333333333333333333333 +1368 3333333333333333333333333333333333333333333333333333333 +1369 3333333333333333333333333333333333333333333333333333333 +1370 3333333333333333333333333333333333333333333333333333333 +1371 3333333333333333333333333333333333333333333333333333333 +1372 3333333333333333333333333333333333333333333333333333333 +1373 3333333333333333333333333333333333333333333333333333333 +1374 3333333333333333333333333333333333333333333333333333333 +1375 3333333333333333333333333333333333333333333333333333333 +1376 3333333333333333333333333333333333333333333333333333333 +1377 3333333333333333333333333333333333333333333333333333333 +1378 3333333333333333333333333333333333333333333333333333333 +1379 3333333333333333333333333333333333333333333333333333333 +1380 3333333333333333333333333333333333333333333333333333333 +1381 3333333333333333333333333333333333333333333333333333333 +1382 3333333333333333333333333333333333333333333333333333333 +1383 3333333333333333333333333333333333333333333333333333333 +1384 3333333333333333333333333333333333333333333333333333333 +1385 3333333333333333333333333333333333333333333333333333333 +1386 3333333333333333333333333333333333333333333333333333333 +1387 3333333333333333333333333333333333333333333333333333333 +1388 3333333333333333333333333333333333333333333333333333333 +1389 3333333333333333333333333333333333333333333333333333333 +1390 3333333333333333333333333333333333333333333333333333333 +1391 3333333333333333333333333333333333333333333333333333333 +1392 3333333333333333333333333333333333333333333333333333333 +1393 3333333333333333333333333333333333333333333333333333333 +1394 3333333333333333333333333333333333333333333333333333333 +1395 3333333333333333333333333333333333333333333333333333333 +1396 3333333333333333333333333333333333333333333333333333333 +1397 3333333333333333333333333333333333333333333333333333333 +1398 3333333333333333333333333333333333333333333333333333333 +1399 3333333333333333333333333333333333333333333333333333333 +1400 3333333333333333333333333333333333333333333333333333333 +1401 3333333333333333333333333333333333333333333333333333333 +1402 3333333333333333333333333333333333333333333333333333333 +1403 3333333333333333333333333333333333333333333333333333333 +1404 3333333333333333333333333333333333333333333333333333333 +1405 3333333333333333333333333333333333333333333333333333333 +1406 3333333333333333333333333333333333333333333333333333333 +1407 3333333333333333333333333333333333333333333333333333333 +1408 3333333333333333333333333333333333333333333333333333333 +1409 3333333333333333333333333333333333333333333333333333333 +1410 3333333333333333333333333333333333333333333333333333333 +1411 3333333333333333333333333333333333333333333333333333333 +1412 3333333333333333333333333333333333333333333333333333333 +1413 3333333333333333333333333333333333333333333333333333333 +1414 3333333333333333333333333333333333333333333333333333333 +1415 3333333333333333333333333333333333333333333333333333333 +1416 3333333333333333333333333333333333333333333333333333333 +1417 3333333333333333333333333333333333333333333333333333333 +1418 3333333333333333333333333333333333333333333333333333333 +1419 3333333333333333333333333333333333333333333333333333333 +1420 3333333333333333333333333333333333333333333333333333333 +1421 3333333333333333333333333333333333333333333333333333333 +1422 3333333333333333333333333333333333333333333333333333333 +1423 3333333333333333333333333333333333333333333333333333333 +1424 3333333333333333333333333333333333333333333333333333333 +1425 3333333333333333333333333333333333333333333333333333333 +1426 3333333333333333333333333333333333333333333333333333333 +1427 3333333333333333333333333333333333333333333333333333333 +1428 3333333333333333333333333333333333333333333333333333333 +1429 3333333333333333333333333333333333333333333333333333333 +1430 3333333333333333333333333333333333333333333333333333333 +1431 3333333333333333333333333333333333333333333333333333333 +1432 3333333333333333333333333333333333333333333333333333333 +1433 3333333333333333333333333333333333333333333333333333333 +1434 3333333333333333333333333333333333333333333333333333333 +1435 3333333333333333333333333333333333333333333333333333333 +1436 3333333333333333333333333333333333333333333333333333333 +1437 3333333333333333333333333333333333333333333333333333333 +1438 3333333333333333333333333333333333333333333333333333333 +1439 3333333333333333333333333333333333333333333333333333333 +1440 3333333333333333333333333333333333333333333333333333333 +1441 3333333333333333333333333333333333333333333333333333333 +1442 3333333333333333333333333333333333333333333333333333333 +1443 3333333333333333333333333333333333333333333333333333333 +1444 3333333333333333333333333333333333333333333333333333333 +1445 3333333333333333333333333333333333333333333333333333333 +1446 3333333333333333333333333333333333333333333333333333333 +1447 3333333333333333333333333333333333333333333333333333333 +1448 3333333333333333333333333333333333333333333333333333333 +1449 3333333333333333333333333333333333333333333333333333333 +1450 3333333333333333333333333333333333333333333333333333333 +1451 3333333333333333333333333333333333333333333333333333333 +1452 3333333333333333333333333333333333333333333333333333333 +1453 3333333333333333333333333333333333333333333333333333333 +1454 3333333333333333333333333333333333333333333333333333333 +1455 3333333333333333333333333333333333333333333333333333333 +1456 3333333333333333333333333333333333333333333333333333333 +1457 3333333333333333333333333333333333333333333333333333333 +1458 3333333333333333333333333333333333333333333333333333333 +1459 3333333333333333333333333333333333333333333333333333333 +1460 3333333333333333333333333333333333333333333333333333333 +1461 3333333333333333333333333333333333333333333333333333333 +1462 3333333333333333333333333333333333333333333333333333333 +1463 3333333333333333333333333333333333333333333333333333333 +1464 3333333333333333333333333333333333333333333333333333333 +1465 3333333333333333333333333333333333333333333333333333333 +1466 3333333333333333333333333333333333333333333333333333333 +1467 3333333333333333333333333333333333333333333333333333333 +1468 3333333333333333333333333333333333333333333333333333333 +1469 3333333333333333333333333333333333333333333333333333333 +1470 3333333333333333333333333333333333333333333333333333333 +1471 3333333333333333333333333333333333333333333333333333333 +1472 3333333333333333333333333333333333333333333333333333333 +1473 3333333333333333333333333333333333333333333333333333333 +1474 3333333333333333333333333333333333333333333333333333333 +1475 3333333333333333333333333333333333333333333333333333333 +1476 3333333333333333333333333333333333333333333333333333333 +1477 3333333333333333333333333333333333333333333333333333333 +1478 3333333333333333333333333333333333333333333333333333333 +1479 3333333333333333333333333333333333333333333333333333333 +1480 3333333333333333333333333333333333333333333333333333333 +1481 3333333333333333333333333333333333333333333333333333333 +1482 3333333333333333333333333333333333333333333333333333333 +1483 3333333333333333333333333333333333333333333333333333333 +1484 3333333333333333333333333333333333333333333333333333333 +1485 3333333333333333333333333333333333333333333333333333333 +1486 3333333333333333333333333333333333333333333333333333333 +1487 3333333333333333333333333333333333333333333333333333333 +1488 3333333333333333333333333333333333333333333333333333333 +1489 3333333333333333333333333333333333333333333333333333333 +1490 3333333333333333333333333333333333333333333333333333333 +1491 3333333333333333333333333333333333333333333333333333333 +1492 3333333333333333333333333333333333333333333333333333333 +1493 3333333333333333333333333333333333333333333333333333333 +1494 3333333333333333333333333333333333333333333333333333333 +1495 3333333333333333333333333333333333333333333333333333333 +1496 3333333333333333333333333333333333333333333333333333333 +1497 3333333333333333333333333333333333333333333333333333333 +1498 3333333333333333333333333333333333333333333333333333333 +1499 3333333333333333333333333333333333333333333333333333333 +1500 3333333333333333333333333333333333333333333333333333333 +1501 3333333333333333333333333333333333333333333333333333333 +1502 3333333333333333333333333333333333333333333333333333333 +1503 3333333333333333333333333333333333333333333333333333333 +1504 3333333333333333333333333333333333333333333333333333333 +1505 3333333333333333333333333333333333333333333333333333333 +1506 3333333333333333333333333333333333333333333333333333333 +1507 3333333333333333333333333333333333333333333333333333333 +1508 3333333333333333333333333333333333333333333333333333333 +1509 3333333333333333333333333333333333333333333333333333333 +1510 3333333333333333333333333333333333333333333333333333333 +1511 3333333333333333333333333333333333333333333333333333333 +1512 3333333333333333333333333333333333333333333333333333333 +1513 3333333333333333333333333333333333333333333333333333333 +1514 3333333333333333333333333333333333333333333333333333333 +1515 3333333333333333333333333333333333333333333333333333333 +1516 3333333333333333333333333333333333333333333333333333333 +1517 3333333333333333333333333333333333333333333333333333333 +1518 3333333333333333333333333333333333333333333333333333333 +1519 3333333333333333333333333333333333333333333333333333333 +1520 3333333333333333333333333333333333333333333333333333333 +1521 3333333333333333333333333333333333333333333333333333333 +1522 3333333333333333333333333333333333333333333333333333333 +1523 3333333333333333333333333333333333333333333333333333333 +1524 3333333333333333333333333333333333333333333333333333333 +1525 3333333333333333333333333333333333333333333333333333333 +1526 3333333333333333333333333333333333333333333333333333333 +1527 3333333333333333333333333333333333333333333333333333333 +1528 3333333333333333333333333333333333333333333333333333333 +1529 3333333333333333333333333333333333333333333333333333333 +1530 3333333333333333333333333333333333333333333333333333333 +1531 3333333333333333333333333333333333333333333333333333333 +1532 3333333333333333333333333333333333333333333333333333333 +1533 3333333333333333333333333333333333333333333333333333333 +1534 3333333333333333333333333333333333333333333333333333333 +1535 3333333333333333333333333333333333333333333333333333333 +1536 3333333333333333333333333333333333333333333333333333333 +1537 3333333333333333333333333333333333333333333333333333333 +1538 3333333333333333333333333333333333333333333333333333333 +1539 3333333333333333333333333333333333333333333333333333333 +1540 3333333333333333333333333333333333333333333333333333333 +1541 3333333333333333333333333333333333333333333333333333333 +1542 3333333333333333333333333333333333333333333333333333333 +1543 3333333333333333333333333333333333333333333333333333333 +1544 3333333333333333333333333333333333333333333333333333333 +1545 3333333333333333333333333333333333333333333333333333333 +1546 3333333333333333333333333333333333333333333333333333333 +1547 3333333333333333333333333333333333333333333333333333333 +1548 3333333333333333333333333333333333333333333333333333333 +1549 3333333333333333333333333333333333333333333333333333333 +1550 3333333333333333333333333333333333333333333333333333333 +1551 3333333333333333333333333333333333333333333333333333333 +1552 3333333333333333333333333333333333333333333333333333333 +1553 3333333333333333333333333333333333333333333333333333333 +1554 3333333333333333333333333333333333333333333333333333333 +1555 3333333333333333333333333333333333333333333333333333333 +1556 3333333333333333333333333333333333333333333333333333333 +1557 3333333333333333333333333333333333333333333333333333333 +1558 3333333333333333333333333333333333333333333333333333333 +1559 3333333333333333333333333333333333333333333333333333333 +1560 3333333333333333333333333333333333333333333333333333333 +1561 3333333333333333333333333333333333333333333333333333333 +1562 3333333333333333333333333333333333333333333333333333333 +1563 3333333333333333333333333333333333333333333333333333333 +1564 3333333333333333333333333333333333333333333333333333333 +1565 3333333333333333333333333333333333333333333333333333333 +1566 3333333333333333333333333333333333333333333333333333333 +1567 3333333333333333333333333333333333333333333333333333333 +1568 3333333333333333333333333333333333333333333333333333333 +1569 3333333333333333333333333333333333333333333333333333333 +1570 3333333333333333333333333333333333333333333333333333333 +1571 3333333333333333333333333333333333333333333333333333333 +1572 3333333333333333333333333333333333333333333333333333333 +1573 3333333333333333333333333333333333333333333333333333333 +1574 3333333333333333333333333333333333333333333333333333333 +1575 3333333333333333333333333333333333333333333333333333333 +1576 3333333333333333333333333333333333333333333333333333333 +1577 3333333333333333333333333333333333333333333333333333333 +1578 3333333333333333333333333333333333333333333333333333333 +1579 3333333333333333333333333333333333333333333333333333333 +1580 3333333333333333333333333333333333333333333333333333333 +1581 3333333333333333333333333333333333333333333333333333333 +1582 3333333333333333333333333333333333333333333333333333333 +1583 3333333333333333333333333333333333333333333333333333333 +1584 3333333333333333333333333333333333333333333333333333333 +1585 3333333333333333333333333333333333333333333333333333333 +1586 3333333333333333333333333333333333333333333333333333333 +1587 3333333333333333333333333333333333333333333333333333333 +1588 3333333333333333333333333333333333333333333333333333333 +1589 3333333333333333333333333333333333333333333333333333333 +1590 3333333333333333333333333333333333333333333333333333333 +1591 3333333333333333333333333333333333333333333333333333333 +1592 3333333333333333333333333333333333333333333333333333333 +1593 3333333333333333333333333333333333333333333333333333333 +1594 3333333333333333333333333333333333333333333333333333333 +1595 3333333333333333333333333333333333333333333333333333333 +1596 3333333333333333333333333333333333333333333333333333333 +1597 3333333333333333333333333333333333333333333333333333333 +1598 3333333333333333333333333333333333333333333333333333333 +1599 3333333333333333333333333333333333333333333333333333333 +1600 3333333333333333333333333333333333333333333333333333333 +1601 3333333333333333333333333333333333333333333333333333333 +1602 3333333333333333333333333333333333333333333333333333333 +1603 3333333333333333333333333333333333333333333333333333333 +1604 3333333333333333333333333333333333333333333333333333333 +1605 3333333333333333333333333333333333333333333333333333333 +1606 3333333333333333333333333333333333333333333333333333333 +1607 3333333333333333333333333333333333333333333333333333333 +1608 3333333333333333333333333333333333333333333333333333333 +1609 3333333333333333333333333333333333333333333333333333333 +1610 3333333333333333333333333333333333333333333333333333333 +1611 3333333333333333333333333333333333333333333333333333333 +1612 3333333333333333333333333333333333333333333333333333333 +1613 3333333333333333333333333333333333333333333333333333333 +1614 3333333333333333333333333333333333333333333333333333333 +1615 3333333333333333333333333333333333333333333333333333333 +1616 3333333333333333333333333333333333333333333333333333333 +1617 3333333333333333333333333333333333333333333333333333333 +1618 3333333333333333333333333333333333333333333333333333333 +1619 3333333333333333333333333333333333333333333333333333333 +1620 3333333333333333333333333333333333333333333333333333333 +1621 3333333333333333333333333333333333333333333333333333333 +1622 3333333333333333333333333333333333333333333333333333333 +1623 3333333333333333333333333333333333333333333333333333333 +1624 3333333333333333333333333333333333333333333333333333333 +1625 3333333333333333333333333333333333333333333333333333333 +1626 3333333333333333333333333333333333333333333333333333333 +1627 3333333333333333333333333333333333333333333333333333333 +1628 3333333333333333333333333333333333333333333333333333333 +1629 3333333333333333333333333333333333333333333333333333333 +1630 3333333333333333333333333333333333333333333333333333333 +1631 3333333333333333333333333333333333333333333333333333333 +1632 3333333333333333333333333333333333333333333333333333333 +1633 3333333333333333333333333333333333333333333333333333333 +1634 3333333333333333333333333333333333333333333333333333333 +1635 3333333333333333333333333333333333333333333333333333333 +1636 3333333333333333333333333333333333333333333333333333333 +1637 3333333333333333333333333333333333333333333333333333333 +1638 3333333333333333333333333333333333333333333333333333333 +1639 3333333333333333333333333333333333333333333333333333333 +1640 3333333333333333333333333333333333333333333333333333333 +1641 3333333333333333333333333333333333333333333333333333333 +1642 3333333333333333333333333333333333333333333333333333333 +1643 3333333333333333333333333333333333333333333333333333333 +1644 3333333333333333333333333333333333333333333333333333333 +1645 3333333333333333333333333333333333333333333333333333333 +1646 3333333333333333333333333333333333333333333333333333333 +1647 3333333333333333333333333333333333333333333333333333333 +1648 3333333333333333333333333333333333333333333333333333333 +1649 3333333333333333333333333333333333333333333333333333333 +1650 3333333333333333333333333333333333333333333333333333333 +1651 3333333333333333333333333333333333333333333333333333333 +1652 3333333333333333333333333333333333333333333333333333333 +1653 3333333333333333333333333333333333333333333333333333333 +1654 3333333333333333333333333333333333333333333333333333333 +1655 3333333333333333333333333333333333333333333333333333333 +1656 3333333333333333333333333333333333333333333333333333333 +1657 3333333333333333333333333333333333333333333333333333333 +1658 3333333333333333333333333333333333333333333333333333333 +1659 3333333333333333333333333333333333333333333333333333333 +1660 3333333333333333333333333333333333333333333333333333333 +1661 3333333333333333333333333333333333333333333333333333333 +1662 3333333333333333333333333333333333333333333333333333333 +1663 3333333333333333333333333333333333333333333333333333333 +1664 3333333333333333333333333333333333333333333333333333333 +1665 3333333333333333333333333333333333333333333333333333333 +1666 3333333333333333333333333333333333333333333333333333333 +1667 3333333333333333333333333333333333333333333333333333333 +1668 3333333333333333333333333333333333333333333333333333333 +1669 3333333333333333333333333333333333333333333333333333333 +1670 3333333333333333333333333333333333333333333333333333333 +1671 3333333333333333333333333333333333333333333333333333333 +1672 3333333333333333333333333333333333333333333333333333333 +1673 3333333333333333333333333333333333333333333333333333333 +1674 3333333333333333333333333333333333333333333333333333333 +1675 3333333333333333333333333333333333333333333333333333333 +1676 3333333333333333333333333333333333333333333333333333333 +1677 3333333333333333333333333333333333333333333333333333333 +1678 3333333333333333333333333333333333333333333333333333333 +1679 3333333333333333333333333333333333333333333333333333333 +1680 3333333333333333333333333333333333333333333333333333333 +1681 3333333333333333333333333333333333333333333333333333333 +1682 3333333333333333333333333333333333333333333333333333333 +1683 3333333333333333333333333333333333333333333333333333333 +1684 3333333333333333333333333333333333333333333333333333333 +1685 3333333333333333333333333333333333333333333333333333333 +1686 3333333333333333333333333333333333333333333333333333333 +1687 3333333333333333333333333333333333333333333333333333333 +1688 3333333333333333333333333333333333333333333333333333333 +1689 3333333333333333333333333333333333333333333333333333333 +1690 3333333333333333333333333333333333333333333333333333333 +1691 3333333333333333333333333333333333333333333333333333333 +1692 3333333333333333333333333333333333333333333333333333333 +1693 3333333333333333333333333333333333333333333333333333333 +1694 3333333333333333333333333333333333333333333333333333333 +1695 3333333333333333333333333333333333333333333333333333333 +1696 3333333333333333333333333333333333333333333333333333333 +1697 3333333333333333333333333333333333333333333333333333333 +1698 3333333333333333333333333333333333333333333333333333333 +1699 3333333333333333333333333333333333333333333333333333333 +1700 3333333333333333333333333333333333333333333333333333333 +1701 3333333333333333333333333333333333333333333333333333333 +1702 3333333333333333333333333333333333333333333333333333333 +1703 3333333333333333333333333333333333333333333333333333333 +1704 3333333333333333333333333333333333333333333333333333333 +1705 3333333333333333333333333333333333333333333333333333333 +1706 3333333333333333333333333333333333333333333333333333333 +1707 3333333333333333333333333333333333333333333333333333333 +1708 3333333333333333333333333333333333333333333333333333333 +1709 3333333333333333333333333333333333333333333333333333333 +1710 3333333333333333333333333333333333333333333333333333333 +1711 3333333333333333333333333333333333333333333333333333333 +1712 3333333333333333333333333333333333333333333333333333333 +1713 3333333333333333333333333333333333333333333333333333333 +1714 3333333333333333333333333333333333333333333333333333333 +1715 3333333333333333333333333333333333333333333333333333333 +1716 3333333333333333333333333333333333333333333333333333333 +1717 3333333333333333333333333333333333333333333333333333333 +1718 3333333333333333333333333333333333333333333333333333333 +1719 3333333333333333333333333333333333333333333333333333333 +1720 3333333333333333333333333333333333333333333333333333333 +1721 3333333333333333333333333333333333333333333333333333333 +1722 3333333333333333333333333333333333333333333333333333333 +1723 3333333333333333333333333333333333333333333333333333333 +1724 3333333333333333333333333333333333333333333333333333333 +1725 3333333333333333333333333333333333333333333333333333333 +1726 3333333333333333333333333333333333333333333333333333333 +1727 3333333333333333333333333333333333333333333333333333333 +1728 3333333333333333333333333333333333333333333333333333333 +1729 3333333333333333333333333333333333333333333333333333333 +1730 3333333333333333333333333333333333333333333333333333333 +1731 3333333333333333333333333333333333333333333333333333333 +1732 3333333333333333333333333333333333333333333333333333333 +1733 3333333333333333333333333333333333333333333333333333333 +1734 3333333333333333333333333333333333333333333333333333333 +1735 3333333333333333333333333333333333333333333333333333333 +1736 3333333333333333333333333333333333333333333333333333333 +1737 3333333333333333333333333333333333333333333333333333333 +1738 3333333333333333333333333333333333333333333333333333333 +1739 3333333333333333333333333333333333333333333333333333333 +1740 3333333333333333333333333333333333333333333333333333333 +1741 3333333333333333333333333333333333333333333333333333333 +1742 3333333333333333333333333333333333333333333333333333333 +1743 3333333333333333333333333333333333333333333333333333333 +1744 3333333333333333333333333333333333333333333333333333333 +1745 3333333333333333333333333333333333333333333333333333333 +1746 3333333333333333333333333333333333333333333333333333333 +1747 3333333333333333333333333333333333333333333333333333333 +1748 3333333333333333333333333333333333333333333333333333333 +1749 3333333333333333333333333333333333333333333333333333333 +1750 3333333333333333333333333333333333333333333333333333333 +1751 3333333333333333333333333333333333333333333333333333333 +1752 3333333333333333333333333333333333333333333333333333333 +1753 3333333333333333333333333333333333333333333333333333333 +1754 3333333333333333333333333333333333333333333333333333333 +1755 3333333333333333333333333333333333333333333333333333333 +1756 3333333333333333333333333333333333333333333333333333333 +1757 3333333333333333333333333333333333333333333333333333333 +1758 3333333333333333333333333333333333333333333333333333333 +1759 3333333333333333333333333333333333333333333333333333333 +1760 3333333333333333333333333333333333333333333333333333333 +1761 3333333333333333333333333333333333333333333333333333333 +1762 3333333333333333333333333333333333333333333333333333333 +1763 3333333333333333333333333333333333333333333333333333333 +1764 3333333333333333333333333333333333333333333333333333333 +1765 3333333333333333333333333333333333333333333333333333333 +1766 3333333333333333333333333333333333333333333333333333333 +1767 3333333333333333333333333333333333333333333333333333333 +1768 3333333333333333333333333333333333333333333333333333333 +1769 3333333333333333333333333333333333333333333333333333333 +1770 3333333333333333333333333333333333333333333333333333333 +1771 3333333333333333333333333333333333333333333333333333333 +1772 3333333333333333333333333333333333333333333333333333333 +1773 3333333333333333333333333333333333333333333333333333333 +1774 3333333333333333333333333333333333333333333333333333333 +1775 3333333333333333333333333333333333333333333333333333333 +1776 3333333333333333333333333333333333333333333333333333333 +1777 3333333333333333333333333333333333333333333333333333333 +1778 3333333333333333333333333333333333333333333333333333333 +1779 3333333333333333333333333333333333333333333333333333333 +1780 3333333333333333333333333333333333333333333333333333333 +1781 3333333333333333333333333333333333333333333333333333333 +1782 3333333333333333333333333333333333333333333333333333333 +1783 3333333333333333333333333333333333333333333333333333333 +1784 3333333333333333333333333333333333333333333333333333333 +1785 3333333333333333333333333333333333333333333333333333333 +1786 3333333333333333333333333333333333333333333333333333333 +1787 3333333333333333333333333333333333333333333333333333333 +1788 3333333333333333333333333333333333333333333333333333333 +1789 3333333333333333333333333333333333333333333333333333333 +1790 3333333333333333333333333333333333333333333333333333333 +1791 3333333333333333333333333333333333333333333333333333333 +1792 3333333333333333333333333333333333333333333333333333333 +1793 3333333333333333333333333333333333333333333333333333333 +1794 3333333333333333333333333333333333333333333333333333333 +1795 3333333333333333333333333333333333333333333333333333333 +1796 3333333333333333333333333333333333333333333333333333333 +1797 3333333333333333333333333333333333333333333333333333333 +1798 3333333333333333333333333333333333333333333333333333333 +1799 3333333333333333333333333333333333333333333333333333333 +1800 3333333333333333333333333333333333333333333333333333333 +1801 3333333333333333333333333333333333333333333333333333333 +1802 3333333333333333333333333333333333333333333333333333333 +1803 3333333333333333333333333333333333333333333333333333333 +1804 3333333333333333333333333333333333333333333333333333333 +1805 3333333333333333333333333333333333333333333333333333333 +1806 3333333333333333333333333333333333333333333333333333333 +1807 3333333333333333333333333333333333333333333333333333333 +1808 3333333333333333333333333333333333333333333333333333333 +1809 3333333333333333333333333333333333333333333333333333333 +1810 3333333333333333333333333333333333333333333333333333333 +1811 3333333333333333333333333333333333333333333333333333333 +1812 3333333333333333333333333333333333333333333333333333333 +1813 3333333333333333333333333333333333333333333333333333333 +1814 3333333333333333333333333333333333333333333333333333333 +1815 3333333333333333333333333333333333333333333333333333333 +1816 3333333333333333333333333333333333333333333333333333333 +1817 3333333333333333333333333333333333333333333333333333333 +1818 3333333333333333333333333333333333333333333333333333333 +1819 3333333333333333333333333333333333333333333333333333333 +1820 3333333333333333333333333333333333333333333333333333333 +1821 3333333333333333333333333333333333333333333333333333333 +1822 3333333333333333333333333333333333333333333333333333333 +1823 3333333333333333333333333333333333333333333333333333333 +1824 3333333333333333333333333333333333333333333333333333333 +1825 3333333333333333333333333333333333333333333333333333333 +1826 3333333333333333333333333333333333333333333333333333333 +1827 3333333333333333333333333333333333333333333333333333333 +1828 3333333333333333333333333333333333333333333333333333333 +1829 3333333333333333333333333333333333333333333333333333333 +1830 3333333333333333333333333333333333333333333333333333333 +1831 3333333333333333333333333333333333333333333333333333333 +1832 3333333333333333333333333333333333333333333333333333333 +1833 3333333333333333333333333333333333333333333333333333333 +1834 3333333333333333333333333333333333333333333333333333333 +1835 3333333333333333333333333333333333333333333333333333333 +1836 3333333333333333333333333333333333333333333333333333333 +1837 3333333333333333333333333333333333333333333333333333333 +1838 3333333333333333333333333333333333333333333333333333333 +1839 3333333333333333333333333333333333333333333333333333333 +1840 3333333333333333333333333333333333333333333333333333333 +1841 3333333333333333333333333333333333333333333333333333333 +1842 3333333333333333333333333333333333333333333333333333333 +1843 3333333333333333333333333333333333333333333333333333333 +1844 3333333333333333333333333333333333333333333333333333333 +1845 3333333333333333333333333333333333333333333333333333333 +1846 3333333333333333333333333333333333333333333333333333333 +1847 3333333333333333333333333333333333333333333333333333333 +1848 3333333333333333333333333333333333333333333333333333333 +1849 3333333333333333333333333333333333333333333333333333333 +1850 3333333333333333333333333333333333333333333333333333333 +1851 3333333333333333333333333333333333333333333333333333333 +1852 3333333333333333333333333333333333333333333333333333333 +1853 3333333333333333333333333333333333333333333333333333333 +1854 3333333333333333333333333333333333333333333333333333333 +1855 3333333333333333333333333333333333333333333333333333333 +1856 3333333333333333333333333333333333333333333333333333333 +1857 3333333333333333333333333333333333333333333333333333333 +1858 3333333333333333333333333333333333333333333333333333333 +1859 3333333333333333333333333333333333333333333333333333333 +1860 3333333333333333333333333333333333333333333333333333333 +1861 3333333333333333333333333333333333333333333333333333333 +1862 3333333333333333333333333333333333333333333333333333333 +1863 3333333333333333333333333333333333333333333333333333333 +1864 3333333333333333333333333333333333333333333333333333333 +1865 3333333333333333333333333333333333333333333333333333333 +1866 3333333333333333333333333333333333333333333333333333333 +1867 3333333333333333333333333333333333333333333333333333333 +1868 3333333333333333333333333333333333333333333333333333333 +1869 3333333333333333333333333333333333333333333333333333333 +1870 3333333333333333333333333333333333333333333333333333333 +1871 3333333333333333333333333333333333333333333333333333333 +1872 3333333333333333333333333333333333333333333333333333333 +1873 3333333333333333333333333333333333333333333333333333333 +1874 3333333333333333333333333333333333333333333333333333333 +1875 3333333333333333333333333333333333333333333333333333333 +1876 3333333333333333333333333333333333333333333333333333333 +1877 3333333333333333333333333333333333333333333333333333333 +1878 3333333333333333333333333333333333333333333333333333333 +1879 3333333333333333333333333333333333333333333333333333333 +1880 3333333333333333333333333333333333333333333333333333333 +1881 3333333333333333333333333333333333333333333333333333333 +1882 3333333333333333333333333333333333333333333333333333333 +1883 3333333333333333333333333333333333333333333333333333333 +1884 3333333333333333333333333333333333333333333333333333333 +1885 3333333333333333333333333333333333333333333333333333333 +1886 3333333333333333333333333333333333333333333333333333333 +1887 3333333333333333333333333333333333333333333333333333333 +1888 3333333333333333333333333333333333333333333333333333333 +1889 3333333333333333333333333333333333333333333333333333333 +1890 3333333333333333333333333333333333333333333333333333333 +1891 3333333333333333333333333333333333333333333333333333333 +1892 3333333333333333333333333333333333333333333333333333333 +1893 3333333333333333333333333333333333333333333333333333333 +1894 3333333333333333333333333333333333333333333333333333333 +1895 3333333333333333333333333333333333333333333333333333333 +1896 3333333333333333333333333333333333333333333333333333333 +1897 3333333333333333333333333333333333333333333333333333333 +1898 3333333333333333333333333333333333333333333333333333333 +1899 3333333333333333333333333333333333333333333333333333333 +1900 3333333333333333333333333333333333333333333333333333333 +1901 3333333333333333333333333333333333333333333333333333333 +1902 3333333333333333333333333333333333333333333333333333333 +1903 3333333333333333333333333333333333333333333333333333333 +1904 3333333333333333333333333333333333333333333333333333333 +1905 3333333333333333333333333333333333333333333333333333333 +1906 3333333333333333333333333333333333333333333333333333333 +1907 3333333333333333333333333333333333333333333333333333333 +1908 3333333333333333333333333333333333333333333333333333333 +1909 3333333333333333333333333333333333333333333333333333333 +1910 3333333333333333333333333333333333333333333333333333333 +1911 3333333333333333333333333333333333333333333333333333333 +1912 3333333333333333333333333333333333333333333333333333333 +1913 3333333333333333333333333333333333333333333333333333333 +1914 3333333333333333333333333333333333333333333333333333333 +1915 3333333333333333333333333333333333333333333333333333333 +1916 3333333333333333333333333333333333333333333333333333333 +1917 3333333333333333333333333333333333333333333333333333333 +1918 3333333333333333333333333333333333333333333333333333333 +1919 3333333333333333333333333333333333333333333333333333333 +1920 3333333333333333333333333333333333333333333333333333333 +1921 3333333333333333333333333333333333333333333333333333333 +1922 3333333333333333333333333333333333333333333333333333333 +1923 3333333333333333333333333333333333333333333333333333333 +1924 3333333333333333333333333333333333333333333333333333333 +1925 3333333333333333333333333333333333333333333333333333333 +1926 3333333333333333333333333333333333333333333333333333333 +1927 3333333333333333333333333333333333333333333333333333333 +1928 3333333333333333333333333333333333333333333333333333333 +1929 3333333333333333333333333333333333333333333333333333333 +1930 3333333333333333333333333333333333333333333333333333333 +1931 3333333333333333333333333333333333333333333333333333333 +1932 3333333333333333333333333333333333333333333333333333333 +1933 3333333333333333333333333333333333333333333333333333333 +1934 3333333333333333333333333333333333333333333333333333333 +1935 3333333333333333333333333333333333333333333333333333333 +1936 3333333333333333333333333333333333333333333333333333333 +1937 3333333333333333333333333333333333333333333333333333333 +1938 3333333333333333333333333333333333333333333333333333333 +1939 3333333333333333333333333333333333333333333333333333333 +1940 3333333333333333333333333333333333333333333333333333333 +1941 3333333333333333333333333333333333333333333333333333333 +1942 3333333333333333333333333333333333333333333333333333333 +1943 3333333333333333333333333333333333333333333333333333333 +1944 3333333333333333333333333333333333333333333333333333333 +1945 3333333333333333333333333333333333333333333333333333333 +1946 3333333333333333333333333333333333333333333333333333333 +1947 3333333333333333333333333333333333333333333333333333333 +1948 3333333333333333333333333333333333333333333333333333333 +1949 3333333333333333333333333333333333333333333333333333333 +1950 3333333333333333333333333333333333333333333333333333333 +1951 3333333333333333333333333333333333333333333333333333333 +1952 3333333333333333333333333333333333333333333333333333333 +1953 3333333333333333333333333333333333333333333333333333333 +1954 3333333333333333333333333333333333333333333333333333333 +1955 3333333333333333333333333333333333333333333333333333333 +1956 3333333333333333333333333333333333333333333333333333333 +1957 3333333333333333333333333333333333333333333333333333333 +1958 3333333333333333333333333333333333333333333333333333333 +1959 3333333333333333333333333333333333333333333333333333333 +1960 3333333333333333333333333333333333333333333333333333333 +1961 3333333333333333333333333333333333333333333333333333333 +1962 3333333333333333333333333333333333333333333333333333333 +1963 3333333333333333333333333333333333333333333333333333333 +1964 3333333333333333333333333333333333333333333333333333333 +1965 3333333333333333333333333333333333333333333333333333333 +1966 3333333333333333333333333333333333333333333333333333333 +1967 3333333333333333333333333333333333333333333333333333333 +1968 3333333333333333333333333333333333333333333333333333333 +1969 3333333333333333333333333333333333333333333333333333333 +1970 3333333333333333333333333333333333333333333333333333333 +1971 3333333333333333333333333333333333333333333333333333333 +1972 3333333333333333333333333333333333333333333333333333333 +1973 3333333333333333333333333333333333333333333333333333333 +1974 3333333333333333333333333333333333333333333333333333333 +1975 3333333333333333333333333333333333333333333333333333333 +1976 3333333333333333333333333333333333333333333333333333333 +1977 3333333333333333333333333333333333333333333333333333333 +1978 3333333333333333333333333333333333333333333333333333333 +1979 3333333333333333333333333333333333333333333333333333333 +1980 3333333333333333333333333333333333333333333333333333333 +1981 3333333333333333333333333333333333333333333333333333333 +1982 3333333333333333333333333333333333333333333333333333333 +1983 3333333333333333333333333333333333333333333333333333333 +1984 3333333333333333333333333333333333333333333333333333333 +1985 3333333333333333333333333333333333333333333333333333333 +1986 3333333333333333333333333333333333333333333333333333333 +1987 3333333333333333333333333333333333333333333333333333333 +1988 3333333333333333333333333333333333333333333333333333333 +1989 3333333333333333333333333333333333333333333333333333333 +1990 3333333333333333333333333333333333333333333333333333333 +1991 3333333333333333333333333333333333333333333333333333333 +1992 3333333333333333333333333333333333333333333333333333333 +1993 3333333333333333333333333333333333333333333333333333333 +1994 3333333333333333333333333333333333333333333333333333333 +1995 3333333333333333333333333333333333333333333333333333333 +1996 3333333333333333333333333333333333333333333333333333333 +1997 3333333333333333333333333333333333333333333333333333333 +1998 3333333333333333333333333333333333333333333333333333333 +1999 3333333333333333333333333333333333333333333333333333333 +2000 3333333333333333333333333333333333333333333333333333333 +2001 3333333333333333333333333333333333333333333333333333333 +2002 3333333333333333333333333333333333333333333333333333333 +2003 3333333333333333333333333333333333333333333333333333333 +2004 3333333333333333333333333333333333333333333333333333333 +2005 3333333333333333333333333333333333333333333333333333333 +2006 3333333333333333333333333333333333333333333333333333333 +2007 3333333333333333333333333333333333333333333333333333333 +2008 3333333333333333333333333333333333333333333333333333333 +2009 3333333333333333333333333333333333333333333333333333333 +2010 3333333333333333333333333333333333333333333333333333333 +2011 3333333333333333333333333333333333333333333333333333333 +2012 3333333333333333333333333333333333333333333333333333333 +2013 3333333333333333333333333333333333333333333333333333333 +2014 3333333333333333333333333333333333333333333333333333333 +2015 3333333333333333333333333333333333333333333333333333333 +2016 3333333333333333333333333333333333333333333333333333333 +2017 3333333333333333333333333333333333333333333333333333333 +2018 3333333333333333333333333333333333333333333333333333333 +2019 3333333333333333333333333333333333333333333333333333333 +2020 3333333333333333333333333333333333333333333333333333333 +2021 3333333333333333333333333333333333333333333333333333333 +2022 3333333333333333333333333333333333333333333333333333333 +2023 3333333333333333333333333333333333333333333333333333333 +2024 3333333333333333333333333333333333333333333333333333333 +2025 3333333333333333333333333333333333333333333333333333333 +2026 3333333333333333333333333333333333333333333333333333333 +2027 3333333333333333333333333333333333333333333333333333333 +2028 3333333333333333333333333333333333333333333333333333333 +2029 3333333333333333333333333333333333333333333333333333333 +2030 3333333333333333333333333333333333333333333333333333333 +2031 3333333333333333333333333333333333333333333333333333333 +2032 3333333333333333333333333333333333333333333333333333333 +2033 3333333333333333333333333333333333333333333333333333333 +2034 3333333333333333333333333333333333333333333333333333333 +2035 3333333333333333333333333333333333333333333333333333333 +2036 3333333333333333333333333333333333333333333333333333333 +2037 3333333333333333333333333333333333333333333333333333333 +2038 3333333333333333333333333333333333333333333333333333333 +2039 3333333333333333333333333333333333333333333333333333333 +2040 3333333333333333333333333333333333333333333333333333333 +2041 3333333333333333333333333333333333333333333333333333333 +2042 3333333333333333333333333333333333333333333333333333333 +2043 3333333333333333333333333333333333333333333333333333333 +2044 3333333333333333333333333333333333333333333333333333333 +2045 3333333333333333333333333333333333333333333333333333333 +2046 3333333333333333333333333333333333333333333333333333333 +2047 3333333333333333333333333333333333333333333333333333333 +2048 3333333333333333333333333333333333333333333333333333333 +2049 3333333333333333333333333333333333333333333333333333333 +2050 3333333333333333333333333333333333333333333333333333333 +2051 3333333333333333333333333333333333333333333333333333333 +2052 3333333333333333333333333333333333333333333333333333333 +2053 3333333333333333333333333333333333333333333333333333333 +2054 3333333333333333333333333333333333333333333333333333333 +2055 3333333333333333333333333333333333333333333333333333333 +2056 3333333333333333333333333333333333333333333333333333333 +2057 3333333333333333333333333333333333333333333333333333333 +2058 3333333333333333333333333333333333333333333333333333333 +2059 3333333333333333333333333333333333333333333333333333333 +2060 3333333333333333333333333333333333333333333333333333333 +2061 3333333333333333333333333333333333333333333333333333333 +2062 3333333333333333333333333333333333333333333333333333333 +2063 3333333333333333333333333333333333333333333333333333333 +2064 3333333333333333333333333333333333333333333333333333333 +2065 3333333333333333333333333333333333333333333333333333333 +2066 3333333333333333333333333333333333333333333333333333333 +2067 3333333333333333333333333333333333333333333333333333333 +2068 3333333333333333333333333333333333333333333333333333333 +2069 3333333333333333333333333333333333333333333333333333333 +2070 3333333333333333333333333333333333333333333333333333333 +2071 3333333333333333333333333333333333333333333333333333333 +2072 3333333333333333333333333333333333333333333333333333333 +2073 3333333333333333333333333333333333333333333333333333333 +2074 3333333333333333333333333333333333333333333333333333333 +2075 3333333333333333333333333333333333333333333333333333333 +2076 3333333333333333333333333333333333333333333333333333333 +2077 3333333333333333333333333333333333333333333333333333333 +2078 3333333333333333333333333333333333333333333333333333333 +2079 3333333333333333333333333333333333333333333333333333333 +2080 3333333333333333333333333333333333333333333333333333333 +2081 3333333333333333333333333333333333333333333333333333333 +2082 3333333333333333333333333333333333333333333333333333333 +2083 3333333333333333333333333333333333333333333333333333333 +2084 3333333333333333333333333333333333333333333333333333333 +2085 3333333333333333333333333333333333333333333333333333333 +2086 3333333333333333333333333333333333333333333333333333333 +2087 3333333333333333333333333333333333333333333333333333333 +2088 3333333333333333333333333333333333333333333333333333333 +2089 3333333333333333333333333333333333333333333333333333333 +2090 3333333333333333333333333333333333333333333333333333333 +2091 3333333333333333333333333333333333333333333333333333333 +2092 3333333333333333333333333333333333333333333333333333333 +2093 3333333333333333333333333333333333333333333333333333333 +2094 3333333333333333333333333333333333333333333333333333333 +2095 3333333333333333333333333333333333333333333333333333333 +2096 3333333333333333333333333333333333333333333333333333333 +2097 3333333333333333333333333333333333333333333333333333333 +2098 3333333333333333333333333333333333333333333333333333333 +2099 3333333333333333333333333333333333333333333333333333333 +2100 3333333333333333333333333333333333333333333333333333333 +2101 3333333333333333333333333333333333333333333333333333333 +2102 3333333333333333333333333333333333333333333333333333333 +2103 3333333333333333333333333333333333333333333333333333333 +2104 3333333333333333333333333333333333333333333333333333333 +2105 3333333333333333333333333333333333333333333333333333333 +2106 3333333333333333333333333333333333333333333333333333333 +2107 3333333333333333333333333333333333333333333333333333333 +2108 3333333333333333333333333333333333333333333333333333333 +2109 3333333333333333333333333333333333333333333333333333333 +2110 3333333333333333333333333333333333333333333333333333333 +2111 3333333333333333333333333333333333333333333333333333333 +2112 3333333333333333333333333333333333333333333333333333333 +2113 3333333333333333333333333333333333333333333333333333333 +2114 3333333333333333333333333333333333333333333333333333333 +2115 3333333333333333333333333333333333333333333333333333333 +2116 3333333333333333333333333333333333333333333333333333333 +2117 3333333333333333333333333333333333333333333333333333333 +2118 3333333333333333333333333333333333333333333333333333333 +2119 3333333333333333333333333333333333333333333333333333333 +2120 3333333333333333333333333333333333333333333333333333333 +2121 3333333333333333333333333333333333333333333333333333333 +2122 3333333333333333333333333333333333333333333333333333333 +2123 3333333333333333333333333333333333333333333333333333333 +2124 3333333333333333333333333333333333333333333333333333333 +2125 3333333333333333333333333333333333333333333333333333333 +2126 3333333333333333333333333333333333333333333333333333333 +2127 3333333333333333333333333333333333333333333333333333333 +2128 3333333333333333333333333333333333333333333333333333333 +2129 3333333333333333333333333333333333333333333333333333333 +2130 3333333333333333333333333333333333333333333333333333333 +2131 3333333333333333333333333333333333333333333333333333333 +2132 3333333333333333333333333333333333333333333333333333333 +2133 3333333333333333333333333333333333333333333333333333333 +2134 3333333333333333333333333333333333333333333333333333333 +2135 3333333333333333333333333333333333333333333333333333333 +2136 3333333333333333333333333333333333333333333333333333333 +2137 3333333333333333333333333333333333333333333333333333333 +2138 3333333333333333333333333333333333333333333333333333333 +2139 3333333333333333333333333333333333333333333333333333333 +2140 3333333333333333333333333333333333333333333333333333333 +2141 3333333333333333333333333333333333333333333333333333333 +2142 3333333333333333333333333333333333333333333333333333333 +2143 3333333333333333333333333333333333333333333333333333333 +2144 3333333333333333333333333333333333333333333333333333333 +2145 3333333333333333333333333333333333333333333333333333333 +2146 3333333333333333333333333333333333333333333333333333333 +2147 3333333333333333333333333333333333333333333333333333333 +2148 3333333333333333333333333333333333333333333333333333333 +2149 3333333333333333333333333333333333333333333333333333333 +2150 3333333333333333333333333333333333333333333333333333333 +2151 3333333333333333333333333333333333333333333333333333333 +2152 3333333333333333333333333333333333333333333333333333333 +2153 3333333333333333333333333333333333333333333333333333333 +2154 3333333333333333333333333333333333333333333333333333333 +2155 3333333333333333333333333333333333333333333333333333333 +2156 3333333333333333333333333333333333333333333333333333333 +2157 3333333333333333333333333333333333333333333333333333333 +2158 3333333333333333333333333333333333333333333333333333333 +2159 3333333333333333333333333333333333333333333333333333333 +2160 3333333333333333333333333333333333333333333333333333333 +2161 3333333333333333333333333333333333333333333333333333333 +2162 3333333333333333333333333333333333333333333333333333333 +2163 3333333333333333333333333333333333333333333333333333333 +2164 3333333333333333333333333333333333333333333333333333333 +2165 3333333333333333333333333333333333333333333333333333333 +2166 3333333333333333333333333333333333333333333333333333333 +2167 3333333333333333333333333333333333333333333333333333333 +2168 3333333333333333333333333333333333333333333333333333333 +2169 3333333333333333333333333333333333333333333333333333333 +2170 3333333333333333333333333333333333333333333333333333333 +2171 3333333333333333333333333333333333333333333333333333333 +2172 3333333333333333333333333333333333333333333333333333333 +2173 3333333333333333333333333333333333333333333333333333333 +2174 3333333333333333333333333333333333333333333333333333333 +2175 3333333333333333333333333333333333333333333333333333333 +2176 3333333333333333333333333333333333333333333333333333333 +2177 3333333333333333333333333333333333333333333333333333333 +2178 3333333333333333333333333333333333333333333333333333333 +2179 3333333333333333333333333333333333333333333333333333333 +2180 3333333333333333333333333333333333333333333333333333333 +2181 3333333333333333333333333333333333333333333333333333333 +2182 3333333333333333333333333333333333333333333333333333333 +2183 3333333333333333333333333333333333333333333333333333333 +2184 3333333333333333333333333333333333333333333333333333333 +2185 3333333333333333333333333333333333333333333333333333333 +2186 3333333333333333333333333333333333333333333333333333333 +2187 3333333333333333333333333333333333333333333333333333333 +2188 3333333333333333333333333333333333333333333333333333333 +2189 3333333333333333333333333333333333333333333333333333333 +2190 3333333333333333333333333333333333333333333333333333333 +2191 3333333333333333333333333333333333333333333333333333333 +2192 3333333333333333333333333333333333333333333333333333333 +2193 3333333333333333333333333333333333333333333333333333333 +2194 3333333333333333333333333333333333333333333333333333333 +2195 3333333333333333333333333333333333333333333333333333333 +2196 3333333333333333333333333333333333333333333333333333333 +2197 3333333333333333333333333333333333333333333333333333333 +2198 3333333333333333333333333333333333333333333333333333333 +2199 3333333333333333333333333333333333333333333333333333333 +2200 3333333333333333333333333333333333333333333333333333333 +2201 3333333333333333333333333333333333333333333333333333333 +2202 3333333333333333333333333333333333333333333333333333333 +2203 3333333333333333333333333333333333333333333333333333333 +2204 3333333333333333333333333333333333333333333333333333333 +2205 3333333333333333333333333333333333333333333333333333333 +2206 3333333333333333333333333333333333333333333333333333333 +2207 3333333333333333333333333333333333333333333333333333333 +2208 3333333333333333333333333333333333333333333333333333333 +2209 3333333333333333333333333333333333333333333333333333333 +2210 3333333333333333333333333333333333333333333333333333333 +2211 3333333333333333333333333333333333333333333333333333333 +2212 3333333333333333333333333333333333333333333333333333333 +2213 3333333333333333333333333333333333333333333333333333333 +2214 3333333333333333333333333333333333333333333333333333333 +2215 3333333333333333333333333333333333333333333333333333333 +2216 3333333333333333333333333333333333333333333333333333333 +2217 3333333333333333333333333333333333333333333333333333333 +2218 3333333333333333333333333333333333333333333333333333333 +2219 3333333333333333333333333333333333333333333333333333333 +2220 3333333333333333333333333333333333333333333333333333333 +2221 3333333333333333333333333333333333333333333333333333333 +2222 3333333333333333333333333333333333333333333333333333333 +2223 3333333333333333333333333333333333333333333333333333333 +2224 3333333333333333333333333333333333333333333333333333333 +2225 3333333333333333333333333333333333333333333333333333333 +2226 3333333333333333333333333333333333333333333333333333333 +2227 3333333333333333333333333333333333333333333333333333333 +2228 3333333333333333333333333333333333333333333333333333333 +2229 3333333333333333333333333333333333333333333333333333333 +2230 3333333333333333333333333333333333333333333333333333333 +2231 3333333333333333333333333333333333333333333333333333333 +2232 3333333333333333333333333333333333333333333333333333333 +2233 3333333333333333333333333333333333333333333333333333333 +2234 3333333333333333333333333333333333333333333333333333333 +2235 3333333333333333333333333333333333333333333333333333333 +2236 3333333333333333333333333333333333333333333333333333333 +2237 3333333333333333333333333333333333333333333333333333333 +2238 3333333333333333333333333333333333333333333333333333333 +2239 3333333333333333333333333333333333333333333333333333333 +2240 3333333333333333333333333333333333333333333333333333333 +2241 3333333333333333333333333333333333333333333333333333333 +2242 3333333333333333333333333333333333333333333333333333333 +2243 3333333333333333333333333333333333333333333333333333333 +2244 3333333333333333333333333333333333333333333333333333333 +2245 3333333333333333333333333333333333333333333333333333333 +2246 3333333333333333333333333333333333333333333333333333333 +2247 3333333333333333333333333333333333333333333333333333333 +2248 3333333333333333333333333333333333333333333333333333333 +2249 3333333333333333333333333333333333333333333333333333333 +2250 3333333333333333333333333333333333333333333333333333333 +2251 3333333333333333333333333333333333333333333333333333333 +2252 3333333333333333333333333333333333333333333333333333333 +2253 3333333333333333333333333333333333333333333333333333333 +2254 3333333333333333333333333333333333333333333333333333333 +2255 3333333333333333333333333333333333333333333333333333333 +2256 3333333333333333333333333333333333333333333333333333333 +2257 3333333333333333333333333333333333333333333333333333333 +2258 3333333333333333333333333333333333333333333333333333333 +2259 3333333333333333333333333333333333333333333333333333333 +2260 3333333333333333333333333333333333333333333333333333333 +2261 3333333333333333333333333333333333333333333333333333333 +2262 3333333333333333333333333333333333333333333333333333333 +2263 3333333333333333333333333333333333333333333333333333333 +2264 3333333333333333333333333333333333333333333333333333333 +2265 3333333333333333333333333333333333333333333333333333333 +2266 3333333333333333333333333333333333333333333333333333333 +2267 3333333333333333333333333333333333333333333333333333333 +2268 3333333333333333333333333333333333333333333333333333333 +2269 3333333333333333333333333333333333333333333333333333333 +2270 3333333333333333333333333333333333333333333333333333333 +2271 3333333333333333333333333333333333333333333333333333333 +2272 3333333333333333333333333333333333333333333333333333333 +2273 3333333333333333333333333333333333333333333333333333333 +2274 3333333333333333333333333333333333333333333333333333333 +2275 3333333333333333333333333333333333333333333333333333333 +2276 3333333333333333333333333333333333333333333333333333333 +2277 3333333333333333333333333333333333333333333333333333333 +2278 3333333333333333333333333333333333333333333333333333333 +2279 3333333333333333333333333333333333333333333333333333333 +2280 3333333333333333333333333333333333333333333333333333333 +2281 3333333333333333333333333333333333333333333333333333333 +2282 3333333333333333333333333333333333333333333333333333333 +2283 3333333333333333333333333333333333333333333333333333333 +2284 3333333333333333333333333333333333333333333333333333333 +2285 3333333333333333333333333333333333333333333333333333333 +2286 3333333333333333333333333333333333333333333333333333333 +2287 3333333333333333333333333333333333333333333333333333333 +2288 3333333333333333333333333333333333333333333333333333333 +2289 3333333333333333333333333333333333333333333333333333333 +2290 3333333333333333333333333333333333333333333333333333333 +2291 3333333333333333333333333333333333333333333333333333333 +2292 3333333333333333333333333333333333333333333333333333333 +2293 3333333333333333333333333333333333333333333333333333333 +2294 3333333333333333333333333333333333333333333333333333333 +2295 3333333333333333333333333333333333333333333333333333333 +2296 3333333333333333333333333333333333333333333333333333333 +2297 3333333333333333333333333333333333333333333333333333333 +2298 3333333333333333333333333333333333333333333333333333333 +2299 3333333333333333333333333333333333333333333333333333333 +2300 3333333333333333333333333333333333333333333333333333333 +2301 3333333333333333333333333333333333333333333333333333333 +2302 3333333333333333333333333333333333333333333333333333333 +2303 3333333333333333333333333333333333333333333333333333333 +2304 3333333333333333333333333333333333333333333333333333333 +2305 3333333333333333333333333333333333333333333333333333333 +2306 3333333333333333333333333333333333333333333333333333333 +2307 3333333333333333333333333333333333333333333333333333333 +2308 3333333333333333333333333333333333333333333333333333333 +2309 3333333333333333333333333333333333333333333333333333333 +2310 3333333333333333333333333333333333333333333333333333333 +2311 3333333333333333333333333333333333333333333333333333333 +2312 3333333333333333333333333333333333333333333333333333333 +2313 3333333333333333333333333333333333333333333333333333333 +2314 3333333333333333333333333333333333333333333333333333333 +2315 3333333333333333333333333333333333333333333333333333333 +2316 3333333333333333333333333333333333333333333333333333333 +2317 3333333333333333333333333333333333333333333333333333333 +2318 3333333333333333333333333333333333333333333333333333333 +2319 3333333333333333333333333333333333333333333333333333333 +2320 3333333333333333333333333333333333333333333333333333333 +2321 3333333333333333333333333333333333333333333333333333333 +2322 3333333333333333333333333333333333333333333333333333333 +2323 3333333333333333333333333333333333333333333333333333333 +2324 3333333333333333333333333333333333333333333333333333333 +2325 3333333333333333333333333333333333333333333333333333333 +2326 3333333333333333333333333333333333333333333333333333333 +2327 3333333333333333333333333333333333333333333333333333333 +2328 3333333333333333333333333333333333333333333333333333333 +2329 3333333333333333333333333333333333333333333333333333333 +2330 3333333333333333333333333333333333333333333333333333333 +2331 3333333333333333333333333333333333333333333333333333333 +2332 3333333333333333333333333333333333333333333333333333333 +2333 3333333333333333333333333333333333333333333333333333333 +2334 3333333333333333333333333333333333333333333333333333333 +2335 3333333333333333333333333333333333333333333333333333333 +2336 3333333333333333333333333333333333333333333333333333333 +2337 3333333333333333333333333333333333333333333333333333333 +2338 3333333333333333333333333333333333333333333333333333333 +2339 3333333333333333333333333333333333333333333333333333333 +2340 3333333333333333333333333333333333333333333333333333333 +2341 3333333333333333333333333333333333333333333333333333333 +2342 3333333333333333333333333333333333333333333333333333333 +2343 3333333333333333333333333333333333333333333333333333333 +2344 3333333333333333333333333333333333333333333333333333333 +2345 3333333333333333333333333333333333333333333333333333333 +2346 3333333333333333333333333333333333333333333333333333333 +2347 3333333333333333333333333333333333333333333333333333333 +2348 3333333333333333333333333333333333333333333333333333333 +2349 3333333333333333333333333333333333333333333333333333333 +2350 3333333333333333333333333333333333333333333333333333333 +2351 3333333333333333333333333333333333333333333333333333333 +2352 3333333333333333333333333333333333333333333333333333333 +2353 3333333333333333333333333333333333333333333333333333333 +2354 3333333333333333333333333333333333333333333333333333333 +2355 3333333333333333333333333333333333333333333333333333333 +2356 3333333333333333333333333333333333333333333333333333333 +2357 3333333333333333333333333333333333333333333333333333333 +2358 3333333333333333333333333333333333333333333333333333333 +2359 3333333333333333333333333333333333333333333333333333333 +2360 3333333333333333333333333333333333333333333333333333333 +2361 3333333333333333333333333333333333333333333333333333333 +2362 3333333333333333333333333333333333333333333333333333333 +2363 3333333333333333333333333333333333333333333333333333333 +2364 3333333333333333333333333333333333333333333333333333333 +2365 3333333333333333333333333333333333333333333333333333333 +2366 3333333333333333333333333333333333333333333333333333333 +2367 3333333333333333333333333333333333333333333333333333333 +2368 3333333333333333333333333333333333333333333333333333333 +2369 3333333333333333333333333333333333333333333333333333333 +2370 3333333333333333333333333333333333333333333333333333333 +2371 3333333333333333333333333333333333333333333333333333333 +2372 3333333333333333333333333333333333333333333333333333333 +2373 3333333333333333333333333333333333333333333333333333333 +2374 3333333333333333333333333333333333333333333333333333333 +2375 3333333333333333333333333333333333333333333333333333333 +2376 3333333333333333333333333333333333333333333333333333333 +2377 3333333333333333333333333333333333333333333333333333333 +2378 3333333333333333333333333333333333333333333333333333333 +2379 3333333333333333333333333333333333333333333333333333333 +2380 3333333333333333333333333333333333333333333333333333333 +2381 3333333333333333333333333333333333333333333333333333333 +2382 3333333333333333333333333333333333333333333333333333333 +2383 3333333333333333333333333333333333333333333333333333333 +2384 3333333333333333333333333333333333333333333333333333333 +2385 3333333333333333333333333333333333333333333333333333333 +2386 3333333333333333333333333333333333333333333333333333333 +2387 3333333333333333333333333333333333333333333333333333333 +2388 3333333333333333333333333333333333333333333333333333333 +2389 3333333333333333333333333333333333333333333333333333333 +2390 3333333333333333333333333333333333333333333333333333333 +2391 3333333333333333333333333333333333333333333333333333333 +2392 3333333333333333333333333333333333333333333333333333333 +2393 3333333333333333333333333333333333333333333333333333333 +2394 3333333333333333333333333333333333333333333333333333333 +2395 3333333333333333333333333333333333333333333333333333333 +2396 3333333333333333333333333333333333333333333333333333333 +2397 3333333333333333333333333333333333333333333333333333333 +2398 3333333333333333333333333333333333333333333333333333333 +2399 3333333333333333333333333333333333333333333333333333333 +2400 3333333333333333333333333333333333333333333333333333333 +2401 3333333333333333333333333333333333333333333333333333333 +2402 3333333333333333333333333333333333333333333333333333333 +2403 3333333333333333333333333333333333333333333333333333333 +2404 3333333333333333333333333333333333333333333333333333333 +2405 3333333333333333333333333333333333333333333333333333333 +2406 3333333333333333333333333333333333333333333333333333333 +2407 3333333333333333333333333333333333333333333333333333333 +2408 3333333333333333333333333333333333333333333333333333333 +2409 3333333333333333333333333333333333333333333333333333333 +2410 3333333333333333333333333333333333333333333333333333333 +2411 3333333333333333333333333333333333333333333333333333333 +2412 3333333333333333333333333333333333333333333333333333333 +2413 3333333333333333333333333333333333333333333333333333333 +2414 3333333333333333333333333333333333333333333333333333333 +2415 3333333333333333333333333333333333333333333333333333333 +2416 3333333333333333333333333333333333333333333333333333333 +2417 3333333333333333333333333333333333333333333333333333333 +2418 3333333333333333333333333333333333333333333333333333333 +2419 3333333333333333333333333333333333333333333333333333333 +2420 3333333333333333333333333333333333333333333333333333333 +2421 3333333333333333333333333333333333333333333333333333333 +2422 3333333333333333333333333333333333333333333333333333333 +2423 3333333333333333333333333333333333333333333333333333333 +2424 3333333333333333333333333333333333333333333333333333333 +2425 3333333333333333333333333333333333333333333333333333333 +2426 3333333333333333333333333333333333333333333333333333333 +2427 3333333333333333333333333333333333333333333333333333333 +2428 3333333333333333333333333333333333333333333333333333333 +2429 3333333333333333333333333333333333333333333333333333333 +2430 3333333333333333333333333333333333333333333333333333333 +2431 3333333333333333333333333333333333333333333333333333333 +2432 3333333333333333333333333333333333333333333333333333333 +2433 3333333333333333333333333333333333333333333333333333333 +2434 3333333333333333333333333333333333333333333333333333333 +2435 3333333333333333333333333333333333333333333333333333333 +2436 3333333333333333333333333333333333333333333333333333333 +2437 3333333333333333333333333333333333333333333333333333333 +2438 3333333333333333333333333333333333333333333333333333333 +2439 3333333333333333333333333333333333333333333333333333333 +2440 3333333333333333333333333333333333333333333333333333333 +2441 3333333333333333333333333333333333333333333333333333333 +2442 3333333333333333333333333333333333333333333333333333333 +2443 3333333333333333333333333333333333333333333333333333333 +2444 3333333333333333333333333333333333333333333333333333333 +2445 3333333333333333333333333333333333333333333333333333333 +2446 3333333333333333333333333333333333333333333333333333333 +2447 3333333333333333333333333333333333333333333333333333333 +2448 3333333333333333333333333333333333333333333333333333333 +2449 3333333333333333333333333333333333333333333333333333333 +2450 3333333333333333333333333333333333333333333333333333333 +2451 3333333333333333333333333333333333333333333333333333333 +2452 3333333333333333333333333333333333333333333333333333333 +2453 3333333333333333333333333333333333333333333333333333333 +2454 3333333333333333333333333333333333333333333333333333333 +2455 3333333333333333333333333333333333333333333333333333333 +2456 3333333333333333333333333333333333333333333333333333333 +2457 3333333333333333333333333333333333333333333333333333333 +2458 3333333333333333333333333333333333333333333333333333333 +2459 3333333333333333333333333333333333333333333333333333333 +2460 3333333333333333333333333333333333333333333333333333333 +2461 3333333333333333333333333333333333333333333333333333333 +2462 3333333333333333333333333333333333333333333333333333333 +2463 3333333333333333333333333333333333333333333333333333333 +2464 3333333333333333333333333333333333333333333333333333333 +2465 3333333333333333333333333333333333333333333333333333333 +2466 3333333333333333333333333333333333333333333333333333333 +2467 3333333333333333333333333333333333333333333333333333333 +2468 3333333333333333333333333333333333333333333333333333333 +2469 3333333333333333333333333333333333333333333333333333333 +2470 3333333333333333333333333333333333333333333333333333333 +2471 3333333333333333333333333333333333333333333333333333333 +2472 3333333333333333333333333333333333333333333333333333333 +2473 3333333333333333333333333333333333333333333333333333333 +2474 3333333333333333333333333333333333333333333333333333333 +2475 3333333333333333333333333333333333333333333333333333333 +2476 3333333333333333333333333333333333333333333333333333333 +2477 3333333333333333333333333333333333333333333333333333333 +2478 3333333333333333333333333333333333333333333333333333333 +2479 3333333333333333333333333333333333333333333333333333333 +2480 3333333333333333333333333333333333333333333333333333333 +2481 3333333333333333333333333333333333333333333333333333333 +2482 3333333333333333333333333333333333333333333333333333333 +2483 3333333333333333333333333333333333333333333333333333333 +2484 3333333333333333333333333333333333333333333333333333333 +2485 3333333333333333333333333333333333333333333333333333333 +2486 3333333333333333333333333333333333333333333333333333333 +2487 3333333333333333333333333333333333333333333333333333333 +2488 3333333333333333333333333333333333333333333333333333333 +2489 3333333333333333333333333333333333333333333333333333333 +2490 3333333333333333333333333333333333333333333333333333333 +2491 3333333333333333333333333333333333333333333333333333333 +2492 3333333333333333333333333333333333333333333333333333333 +2493 3333333333333333333333333333333333333333333333333333333 +2494 3333333333333333333333333333333333333333333333333333333 +2495 3333333333333333333333333333333333333333333333333333333 +2496 3333333333333333333333333333333333333333333333333333333 +2497 3333333333333333333333333333333333333333333333333333333 +2498 3333333333333333333333333333333333333333333333333333333 +2499 3333333333333333333333333333333333333333333333333333333 +2500 3333333333333333333333333333333333333333333333333333333 +2501 3333333333333333333333333333333333333333333333333333333 +2502 3333333333333333333333333333333333333333333333333333333 +2503 3333333333333333333333333333333333333333333333333333333 +2504 3333333333333333333333333333333333333333333333333333333 +2505 3333333333333333333333333333333333333333333333333333333 +2506 3333333333333333333333333333333333333333333333333333333 +2507 3333333333333333333333333333333333333333333333333333333 +2508 3333333333333333333333333333333333333333333333333333333 +2509 3333333333333333333333333333333333333333333333333333333 +2510 3333333333333333333333333333333333333333333333333333333 +2511 3333333333333333333333333333333333333333333333333333333 +2512 3333333333333333333333333333333333333333333333333333333 +2513 3333333333333333333333333333333333333333333333333333333 +2514 3333333333333333333333333333333333333333333333333333333 +2515 3333333333333333333333333333333333333333333333333333333 +2516 3333333333333333333333333333333333333333333333333333333 +2517 3333333333333333333333333333333333333333333333333333333 +2518 3333333333333333333333333333333333333333333333333333333 +2519 3333333333333333333333333333333333333333333333333333333 +2520 3333333333333333333333333333333333333333333333333333333 +2521 3333333333333333333333333333333333333333333333333333333 +2522 3333333333333333333333333333333333333333333333333333333 +2523 3333333333333333333333333333333333333333333333333333333 +2524 3333333333333333333333333333333333333333333333333333333 +2525 3333333333333333333333333333333333333333333333333333333 +2526 3333333333333333333333333333333333333333333333333333333 +2527 3333333333333333333333333333333333333333333333333333333 +2528 3333333333333333333333333333333333333333333333333333333 +2529 3333333333333333333333333333333333333333333333333333333 +2530 3333333333333333333333333333333333333333333333333333333 +2531 3333333333333333333333333333333333333333333333333333333 +2532 3333333333333333333333333333333333333333333333333333333 +2533 3333333333333333333333333333333333333333333333333333333 +2534 3333333333333333333333333333333333333333333333333333333 +2535 3333333333333333333333333333333333333333333333333333333 +2536 3333333333333333333333333333333333333333333333333333333 +2537 3333333333333333333333333333333333333333333333333333333 +2538 3333333333333333333333333333333333333333333333333333333 +2539 3333333333333333333333333333333333333333333333333333333 +2540 3333333333333333333333333333333333333333333333333333333 +2541 3333333333333333333333333333333333333333333333333333333 +2542 3333333333333333333333333333333333333333333333333333333 +2543 3333333333333333333333333333333333333333333333333333333 +2544 3333333333333333333333333333333333333333333333333333333 +2545 3333333333333333333333333333333333333333333333333333333 +2546 3333333333333333333333333333333333333333333333333333333 +2547 3333333333333333333333333333333333333333333333333333333 +2548 3333333333333333333333333333333333333333333333333333333 +2549 3333333333333333333333333333333333333333333333333333333 +2550 3333333333333333333333333333333333333333333333333333333 +2551 3333333333333333333333333333333333333333333333333333333 +2552 3333333333333333333333333333333333333333333333333333333 +2553 3333333333333333333333333333333333333333333333333333333 +2554 3333333333333333333333333333333333333333333333333333333 +2555 3333333333333333333333333333333333333333333333333333333 +2556 3333333333333333333333333333333333333333333333333333333 +2557 3333333333333333333333333333333333333333333333333333333 +2558 3333333333333333333333333333333333333333333333333333333 +2559 3333333333333333333333333333333333333333333333333333333 +2560 3333333333333333333333333333333333333333333333333333333 +2561 3333333333333333333333333333333333333333333333333333333 +2562 3333333333333333333333333333333333333333333333333333333 +2563 3333333333333333333333333333333333333333333333333333333 +2564 3333333333333333333333333333333333333333333333333333333 +2565 3333333333333333333333333333333333333333333333333333333 +2566 3333333333333333333333333333333333333333333333333333333 +2567 3333333333333333333333333333333333333333333333333333333 +2568 3333333333333333333333333333333333333333333333333333333 +2569 3333333333333333333333333333333333333333333333333333333 +2570 3333333333333333333333333333333333333333333333333333333 +2571 3333333333333333333333333333333333333333333333333333333 +2572 3333333333333333333333333333333333333333333333333333333 +2573 3333333333333333333333333333333333333333333333333333333 +2574 3333333333333333333333333333333333333333333333333333333 +2575 3333333333333333333333333333333333333333333333333333333 +2576 3333333333333333333333333333333333333333333333333333333 +2577 3333333333333333333333333333333333333333333333333333333 +2578 3333333333333333333333333333333333333333333333333333333 +2579 3333333333333333333333333333333333333333333333333333333 +2580 3333333333333333333333333333333333333333333333333333333 +2581 3333333333333333333333333333333333333333333333333333333 +2582 3333333333333333333333333333333333333333333333333333333 +2583 3333333333333333333333333333333333333333333333333333333 +2584 3333333333333333333333333333333333333333333333333333333 +2585 3333333333333333333333333333333333333333333333333333333 +2586 3333333333333333333333333333333333333333333333333333333 +2587 3333333333333333333333333333333333333333333333333333333 +2588 3333333333333333333333333333333333333333333333333333333 +2589 3333333333333333333333333333333333333333333333333333333 +2590 3333333333333333333333333333333333333333333333333333333 +2591 3333333333333333333333333333333333333333333333333333333 +2592 3333333333333333333333333333333333333333333333333333333 +2593 3333333333333333333333333333333333333333333333333333333 +2594 3333333333333333333333333333333333333333333333333333333 +2595 3333333333333333333333333333333333333333333333333333333 +2596 3333333333333333333333333333333333333333333333333333333 +2597 3333333333333333333333333333333333333333333333333333333 +2598 3333333333333333333333333333333333333333333333333333333 +2599 3333333333333333333333333333333333333333333333333333333 +2600 3333333333333333333333333333333333333333333333333333333 +2601 3333333333333333333333333333333333333333333333333333333 +2602 3333333333333333333333333333333333333333333333333333333 +2603 3333333333333333333333333333333333333333333333333333333 +2604 3333333333333333333333333333333333333333333333333333333 +2605 3333333333333333333333333333333333333333333333333333333 +2606 3333333333333333333333333333333333333333333333333333333 +2607 3333333333333333333333333333333333333333333333333333333 +2608 3333333333333333333333333333333333333333333333333333333 +2609 3333333333333333333333333333333333333333333333333333333 +2610 3333333333333333333333333333333333333333333333333333333 +2611 3333333333333333333333333333333333333333333333333333333 +2612 3333333333333333333333333333333333333333333333333333333 +2613 3333333333333333333333333333333333333333333333333333333 +2614 3333333333333333333333333333333333333333333333333333333 +2615 3333333333333333333333333333333333333333333333333333333 +2616 3333333333333333333333333333333333333333333333333333333 +2617 3333333333333333333333333333333333333333333333333333333 +2618 3333333333333333333333333333333333333333333333333333333 +2619 3333333333333333333333333333333333333333333333333333333 +2620 3333333333333333333333333333333333333333333333333333333 +2621 3333333333333333333333333333333333333333333333333333333 +2622 3333333333333333333333333333333333333333333333333333333 +2623 3333333333333333333333333333333333333333333333333333333 +2624 3333333333333333333333333333333333333333333333333333333 +2625 3333333333333333333333333333333333333333333333333333333 +2626 3333333333333333333333333333333333333333333333333333333 +2627 3333333333333333333333333333333333333333333333333333333 +2628 3333333333333333333333333333333333333333333333333333333 +2629 3333333333333333333333333333333333333333333333333333333 +2630 3333333333333333333333333333333333333333333333333333333 +2631 3333333333333333333333333333333333333333333333333333333 +2632 3333333333333333333333333333333333333333333333333333333 +2633 3333333333333333333333333333333333333333333333333333333 +2634 3333333333333333333333333333333333333333333333333333333 +2635 3333333333333333333333333333333333333333333333333333333 +2636 3333333333333333333333333333333333333333333333333333333 +2637 3333333333333333333333333333333333333333333333333333333 +2638 3333333333333333333333333333333333333333333333333333333 +2639 3333333333333333333333333333333333333333333333333333333 +2640 3333333333333333333333333333333333333333333333333333333 +2641 3333333333333333333333333333333333333333333333333333333 +2642 3333333333333333333333333333333333333333333333333333333 +2643 3333333333333333333333333333333333333333333333333333333 +2644 3333333333333333333333333333333333333333333333333333333 +2645 3333333333333333333333333333333333333333333333333333333 +2646 3333333333333333333333333333333333333333333333333333333 +2647 3333333333333333333333333333333333333333333333333333333 +2648 3333333333333333333333333333333333333333333333333333333 +2649 3333333333333333333333333333333333333333333333333333333 +2650 3333333333333333333333333333333333333333333333333333333 +2651 3333333333333333333333333333333333333333333333333333333 +2652 3333333333333333333333333333333333333333333333333333333 +2653 3333333333333333333333333333333333333333333333333333333 +2654 3333333333333333333333333333333333333333333333333333333 +2655 3333333333333333333333333333333333333333333333333333333 +2656 3333333333333333333333333333333333333333333333333333333 +2657 3333333333333333333333333333333333333333333333333333333 +2658 3333333333333333333333333333333333333333333333333333333 +2659 3333333333333333333333333333333333333333333333333333333 +2660 3333333333333333333333333333333333333333333333333333333 +2661 3333333333333333333333333333333333333333333333333333333 +2662 3333333333333333333333333333333333333333333333333333333 +2663 3333333333333333333333333333333333333333333333333333333 +2664 3333333333333333333333333333333333333333333333333333333 +2665 3333333333333333333333333333333333333333333333333333333 +2666 3333333333333333333333333333333333333333333333333333333 +2667 3333333333333333333333333333333333333333333333333333333 +2668 3333333333333333333333333333333333333333333333333333333 +2669 3333333333333333333333333333333333333333333333333333333 +2670 3333333333333333333333333333333333333333333333333333333 +2671 3333333333333333333333333333333333333333333333333333333 +2672 3333333333333333333333333333333333333333333333333333333 +2673 3333333333333333333333333333333333333333333333333333333 +2674 3333333333333333333333333333333333333333333333333333333 +2675 3333333333333333333333333333333333333333333333333333333 +2676 3333333333333333333333333333333333333333333333333333333 +2677 3333333333333333333333333333333333333333333333333333333 +2678 3333333333333333333333333333333333333333333333333333333 +2679 3333333333333333333333333333333333333333333333333333333 +2680 3333333333333333333333333333333333333333333333333333333 +2681 3333333333333333333333333333333333333333333333333333333 +2682 3333333333333333333333333333333333333333333333333333333 +2683 3333333333333333333333333333333333333333333333333333333 +2684 3333333333333333333333333333333333333333333333333333333 +2685 3333333333333333333333333333333333333333333333333333333 +2686 3333333333333333333333333333333333333333333333333333333 +2687 3333333333333333333333333333333333333333333333333333333 +2688 3333333333333333333333333333333333333333333333333333333 +2689 3333333333333333333333333333333333333333333333333333333 +2690 3333333333333333333333333333333333333333333333333333333 +2691 3333333333333333333333333333333333333333333333333333333 +2692 3333333333333333333333333333333333333333333333333333333 +2693 3333333333333333333333333333333333333333333333333333333 +2694 3333333333333333333333333333333333333333333333333333333 +2695 3333333333333333333333333333333333333333333333333333333 +2696 3333333333333333333333333333333333333333333333333333333 +2697 3333333333333333333333333333333333333333333333333333333 +2698 3333333333333333333333333333333333333333333333333333333 +2699 3333333333333333333333333333333333333333333333333333333 +2700 3333333333333333333333333333333333333333333333333333333 +2701 3333333333333333333333333333333333333333333333333333333 +2702 3333333333333333333333333333333333333333333333333333333 +2703 3333333333333333333333333333333333333333333333333333333 +2704 3333333333333333333333333333333333333333333333333333333 +2705 3333333333333333333333333333333333333333333333333333333 +2706 3333333333333333333333333333333333333333333333333333333 +2707 3333333333333333333333333333333333333333333333333333333 +2708 3333333333333333333333333333333333333333333333333333333 +2709 3333333333333333333333333333333333333333333333333333333 +2710 3333333333333333333333333333333333333333333333333333333 +2711 3333333333333333333333333333333333333333333333333333333 +2712 3333333333333333333333333333333333333333333333333333333 +2713 3333333333333333333333333333333333333333333333333333333 +2714 3333333333333333333333333333333333333333333333333333333 +2715 3333333333333333333333333333333333333333333333333333333 +2716 3333333333333333333333333333333333333333333333333333333 +2717 3333333333333333333333333333333333333333333333333333333 +2718 3333333333333333333333333333333333333333333333333333333 +2719 3333333333333333333333333333333333333333333333333333333 +2720 3333333333333333333333333333333333333333333333333333333 +2721 3333333333333333333333333333333333333333333333333333333 +2722 3333333333333333333333333333333333333333333333333333333 +2723 3333333333333333333333333333333333333333333333333333333 +2724 3333333333333333333333333333333333333333333333333333333 +2725 3333333333333333333333333333333333333333333333333333333 +2726 3333333333333333333333333333333333333333333333333333333 +2727 3333333333333333333333333333333333333333333333333333333 +2728 3333333333333333333333333333333333333333333333333333333 +2729 3333333333333333333333333333333333333333333333333333333 +2730 3333333333333333333333333333333333333333333333333333333 +2731 3333333333333333333333333333333333333333333333333333333 +2732 3333333333333333333333333333333333333333333333333333333 +2733 3333333333333333333333333333333333333333333333333333333 +2734 3333333333333333333333333333333333333333333333333333333 +2735 3333333333333333333333333333333333333333333333333333333 +2736 3333333333333333333333333333333333333333333333333333333 +2737 3333333333333333333333333333333333333333333333333333333 +2738 3333333333333333333333333333333333333333333333333333333 +2739 3333333333333333333333333333333333333333333333333333333 +2740 3333333333333333333333333333333333333333333333333333333 +2741 3333333333333333333333333333333333333333333333333333333 +2742 3333333333333333333333333333333333333333333333333333333 +2743 3333333333333333333333333333333333333333333333333333333 +2744 3333333333333333333333333333333333333333333333333333333 +2745 3333333333333333333333333333333333333333333333333333333 +2746 3333333333333333333333333333333333333333333333333333333 +2747 3333333333333333333333333333333333333333333333333333333 +2748 3333333333333333333333333333333333333333333333333333333 +2749 3333333333333333333333333333333333333333333333333333333 +2750 3333333333333333333333333333333333333333333333333333333 +2751 3333333333333333333333333333333333333333333333333333333 +2752 3333333333333333333333333333333333333333333333333333333 +2753 3333333333333333333333333333333333333333333333333333333 +2754 3333333333333333333333333333333333333333333333333333333 +2755 3333333333333333333333333333333333333333333333333333333 +2756 3333333333333333333333333333333333333333333333333333333 +2757 3333333333333333333333333333333333333333333333333333333 +2758 3333333333333333333333333333333333333333333333333333333 +2759 3333333333333333333333333333333333333333333333333333333 +2760 3333333333333333333333333333333333333333333333333333333 +2761 3333333333333333333333333333333333333333333333333333333 +2762 3333333333333333333333333333333333333333333333333333333 +2763 3333333333333333333333333333333333333333333333333333333 +2764 3333333333333333333333333333333333333333333333333333333 +2765 3333333333333333333333333333333333333333333333333333333 +2766 3333333333333333333333333333333333333333333333333333333 +2767 3333333333333333333333333333333333333333333333333333333 +2768 3333333333333333333333333333333333333333333333333333333 +2769 3333333333333333333333333333333333333333333333333333333 +2770 3333333333333333333333333333333333333333333333333333333 +2771 3333333333333333333333333333333333333333333333333333333 +2772 3333333333333333333333333333333333333333333333333333333 +2773 3333333333333333333333333333333333333333333333333333333 +2774 3333333333333333333333333333333333333333333333333333333 +2775 3333333333333333333333333333333333333333333333333333333 +2776 3333333333333333333333333333333333333333333333333333333 +2777 3333333333333333333333333333333333333333333333333333333 +2778 3333333333333333333333333333333333333333333333333333333 +2779 3333333333333333333333333333333333333333333333333333333 +2780 3333333333333333333333333333333333333333333333333333333 +2781 3333333333333333333333333333333333333333333333333333333 +2782 3333333333333333333333333333333333333333333333333333333 +2783 3333333333333333333333333333333333333333333333333333333 +2784 3333333333333333333333333333333333333333333333333333333 +2785 3333333333333333333333333333333333333333333333333333333 +2786 3333333333333333333333333333333333333333333333333333333 +2787 3333333333333333333333333333333333333333333333333333333 +2788 3333333333333333333333333333333333333333333333333333333 +2789 3333333333333333333333333333333333333333333333333333333 +2790 3333333333333333333333333333333333333333333333333333333 +2791 3333333333333333333333333333333333333333333333333333333 +2792 3333333333333333333333333333333333333333333333333333333 +2793 3333333333333333333333333333333333333333333333333333333 +2794 3333333333333333333333333333333333333333333333333333333 +2795 3333333333333333333333333333333333333333333333333333333 +2796 3333333333333333333333333333333333333333333333333333333 +2797 3333333333333333333333333333333333333333333333333333333 +2798 3333333333333333333333333333333333333333333333333333333 +2799 3333333333333333333333333333333333333333333333333333333 +2800 3333333333333333333333333333333333333333333333333333333 +2801 3333333333333333333333333333333333333333333333333333333 +2802 3333333333333333333333333333333333333333333333333333333 +2803 3333333333333333333333333333333333333333333333333333333 +2804 3333333333333333333333333333333333333333333333333333333 +2805 3333333333333333333333333333333333333333333333333333333 +2806 3333333333333333333333333333333333333333333333333333333 +2807 3333333333333333333333333333333333333333333333333333333 +2808 3333333333333333333333333333333333333333333333333333333 +2809 3333333333333333333333333333333333333333333333333333333 +2810 3333333333333333333333333333333333333333333333333333333 +2811 3333333333333333333333333333333333333333333333333333333 +2812 3333333333333333333333333333333333333333333333333333333 +2813 3333333333333333333333333333333333333333333333333333333 +2814 3333333333333333333333333333333333333333333333333333333 +2815 3333333333333333333333333333333333333333333333333333333 +2816 3333333333333333333333333333333333333333333333333333333 +2817 3333333333333333333333333333333333333333333333333333333 +2818 3333333333333333333333333333333333333333333333333333333 +2819 3333333333333333333333333333333333333333333333333333333 +2820 3333333333333333333333333333333333333333333333333333333 +2821 3333333333333333333333333333333333333333333333333333333 +2822 3333333333333333333333333333333333333333333333333333333 +2823 3333333333333333333333333333333333333333333333333333333 +2824 3333333333333333333333333333333333333333333333333333333 +2825 3333333333333333333333333333333333333333333333333333333 +2826 3333333333333333333333333333333333333333333333333333333 +2827 3333333333333333333333333333333333333333333333333333333 +2828 3333333333333333333333333333333333333333333333333333333 +2829 3333333333333333333333333333333333333333333333333333333 +2830 3333333333333333333333333333333333333333333333333333333 +2831 3333333333333333333333333333333333333333333333333333333 +2832 3333333333333333333333333333333333333333333333333333333 +2833 3333333333333333333333333333333333333333333333333333333 +2834 3333333333333333333333333333333333333333333333333333333 +2835 3333333333333333333333333333333333333333333333333333333 +2836 3333333333333333333333333333333333333333333333333333333 +2837 3333333333333333333333333333333333333333333333333333333 +2838 3333333333333333333333333333333333333333333333333333333 +2839 3333333333333333333333333333333333333333333333333333333 +2840 3333333333333333333333333333333333333333333333333333333 +2841 3333333333333333333333333333333333333333333333333333333 +2842 3333333333333333333333333333333333333333333333333333333 +2843 3333333333333333333333333333333333333333333333333333333 +2844 3333333333333333333333333333333333333333333333333333333 +2845 3333333333333333333333333333333333333333333333333333333 +2846 3333333333333333333333333333333333333333333333333333333 +2847 3333333333333333333333333333333333333333333333333333333 +2848 3333333333333333333333333333333333333333333333333333333 +2849 3333333333333333333333333333333333333333333333333333333 +2850 3333333333333333333333333333333333333333333333333333333 +2851 3333333333333333333333333333333333333333333333333333333 +2852 3333333333333333333333333333333333333333333333333333333 +2853 3333333333333333333333333333333333333333333333333333333 +2854 3333333333333333333333333333333333333333333333333333333 +2855 3333333333333333333333333333333333333333333333333333333 +2856 3333333333333333333333333333333333333333333333333333333 +2857 3333333333333333333333333333333333333333333333333333333 +2858 3333333333333333333333333333333333333333333333333333333 +2859 3333333333333333333333333333333333333333333333333333333 +2860 3333333333333333333333333333333333333333333333333333333 +2861 3333333333333333333333333333333333333333333333333333333 +2862 3333333333333333333333333333333333333333333333333333333 +2863 3333333333333333333333333333333333333333333333333333333 +2864 3333333333333333333333333333333333333333333333333333333 +2865 3333333333333333333333333333333333333333333333333333333 +2866 3333333333333333333333333333333333333333333333333333333 +2867 3333333333333333333333333333333333333333333333333333333 +2868 3333333333333333333333333333333333333333333333333333333 +2869 3333333333333333333333333333333333333333333333333333333 +2870 3333333333333333333333333333333333333333333333333333333 +2871 3333333333333333333333333333333333333333333333333333333 +2872 3333333333333333333333333333333333333333333333333333333 +2873 3333333333333333333333333333333333333333333333333333333 +2874 3333333333333333333333333333333333333333333333333333333 +2875 3333333333333333333333333333333333333333333333333333333 +2876 3333333333333333333333333333333333333333333333333333333 +2877 3333333333333333333333333333333333333333333333333333333 +2878 3333333333333333333333333333333333333333333333333333333 +2879 3333333333333333333333333333333333333333333333333333333 +2880 3333333333333333333333333333333333333333333333333333333 +2881 3333333333333333333333333333333333333333333333333333333 +2882 3333333333333333333333333333333333333333333333333333333 +2883 3333333333333333333333333333333333333333333333333333333 +2884 3333333333333333333333333333333333333333333333333333333 +2885 3333333333333333333333333333333333333333333333333333333 +2886 3333333333333333333333333333333333333333333333333333333 +2887 3333333333333333333333333333333333333333333333333333333 +2888 3333333333333333333333333333333333333333333333333333333 +2889 3333333333333333333333333333333333333333333333333333333 +2890 3333333333333333333333333333333333333333333333333333333 +2891 3333333333333333333333333333333333333333333333333333333 +2892 3333333333333333333333333333333333333333333333333333333 +2893 3333333333333333333333333333333333333333333333333333333 +2894 3333333333333333333333333333333333333333333333333333333 +2895 3333333333333333333333333333333333333333333333333333333 +2896 3333333333333333333333333333333333333333333333333333333 +2897 3333333333333333333333333333333333333333333333333333333 +2898 3333333333333333333333333333333333333333333333333333333 +2899 3333333333333333333333333333333333333333333333333333333 +2900 3333333333333333333333333333333333333333333333333333333 +2901 3333333333333333333333333333333333333333333333333333333 +2902 3333333333333333333333333333333333333333333333333333333 +2903 3333333333333333333333333333333333333333333333333333333 +2904 3333333333333333333333333333333333333333333333333333333 +2905 3333333333333333333333333333333333333333333333333333333 +2906 3333333333333333333333333333333333333333333333333333333 +2907 3333333333333333333333333333333333333333333333333333333 +2908 3333333333333333333333333333333333333333333333333333333 +2909 3333333333333333333333333333333333333333333333333333333 +2910 3333333333333333333333333333333333333333333333333333333 +2911 3333333333333333333333333333333333333333333333333333333 +2912 3333333333333333333333333333333333333333333333333333333 +2913 3333333333333333333333333333333333333333333333333333333 +2914 3333333333333333333333333333333333333333333333333333333 +2915 3333333333333333333333333333333333333333333333333333333 +2916 3333333333333333333333333333333333333333333333333333333 +2917 3333333333333333333333333333333333333333333333333333333 +2918 3333333333333333333333333333333333333333333333333333333 +2919 3333333333333333333333333333333333333333333333333333333 +2920 3333333333333333333333333333333333333333333333333333333 +2921 3333333333333333333333333333333333333333333333333333333 +2922 3333333333333333333333333333333333333333333333333333333 +2923 3333333333333333333333333333333333333333333333333333333 +2924 3333333333333333333333333333333333333333333333333333333 +2925 3333333333333333333333333333333333333333333333333333333 +2926 3333333333333333333333333333333333333333333333333333333 +2927 3333333333333333333333333333333333333333333333333333333 +2928 3333333333333333333333333333333333333333333333333333333 +2929 3333333333333333333333333333333333333333333333333333333 +2930 3333333333333333333333333333333333333333333333333333333 +2931 3333333333333333333333333333333333333333333333333333333 +2932 3333333333333333333333333333333333333333333333333333333 +2933 3333333333333333333333333333333333333333333333333333333 +2934 3333333333333333333333333333333333333333333333333333333 +2935 3333333333333333333333333333333333333333333333333333333 +2936 3333333333333333333333333333333333333333333333333333333 +2937 3333333333333333333333333333333333333333333333333333333 +2938 3333333333333333333333333333333333333333333333333333333 +2939 3333333333333333333333333333333333333333333333333333333 +2940 3333333333333333333333333333333333333333333333333333333 +2941 3333333333333333333333333333333333333333333333333333333 +2942 3333333333333333333333333333333333333333333333333333333 +2943 3333333333333333333333333333333333333333333333333333333 +2944 3333333333333333333333333333333333333333333333333333333 +2945 3333333333333333333333333333333333333333333333333333333 +2946 3333333333333333333333333333333333333333333333333333333 +2947 3333333333333333333333333333333333333333333333333333333 +2948 3333333333333333333333333333333333333333333333333333333 +2949 3333333333333333333333333333333333333333333333333333333 +2950 3333333333333333333333333333333333333333333333333333333 +2951 3333333333333333333333333333333333333333333333333333333 +2952 3333333333333333333333333333333333333333333333333333333 +2953 3333333333333333333333333333333333333333333333333333333 +2954 3333333333333333333333333333333333333333333333333333333 +2955 3333333333333333333333333333333333333333333333333333333 +2956 3333333333333333333333333333333333333333333333333333333 +2957 3333333333333333333333333333333333333333333333333333333 +2958 3333333333333333333333333333333333333333333333333333333 +2959 3333333333333333333333333333333333333333333333333333333 +2960 3333333333333333333333333333333333333333333333333333333 +2961 3333333333333333333333333333333333333333333333333333333 +2962 3333333333333333333333333333333333333333333333333333333 +2963 3333333333333333333333333333333333333333333333333333333 +2964 3333333333333333333333333333333333333333333333333333333 +2965 3333333333333333333333333333333333333333333333333333333 +2966 3333333333333333333333333333333333333333333333333333333 +2967 3333333333333333333333333333333333333333333333333333333 +2968 3333333333333333333333333333333333333333333333333333333 +2969 3333333333333333333333333333333333333333333333333333333 +2970 3333333333333333333333333333333333333333333333333333333 +2971 3333333333333333333333333333333333333333333333333333333 +2972 3333333333333333333333333333333333333333333333333333333 +2973 3333333333333333333333333333333333333333333333333333333 +2974 3333333333333333333333333333333333333333333333333333333 +2975 3333333333333333333333333333333333333333333333333333333 +2976 3333333333333333333333333333333333333333333333333333333 +2977 3333333333333333333333333333333333333333333333333333333 +2978 3333333333333333333333333333333333333333333333333333333 +2979 3333333333333333333333333333333333333333333333333333333 +2980 3333333333333333333333333333333333333333333333333333333 +2981 3333333333333333333333333333333333333333333333333333333 +2982 3333333333333333333333333333333333333333333333333333333 +2983 3333333333333333333333333333333333333333333333333333333 +2984 3333333333333333333333333333333333333333333333333333333 +2985 3333333333333333333333333333333333333333333333333333333 +2986 3333333333333333333333333333333333333333333333333333333 +2987 3333333333333333333333333333333333333333333333333333333 +2988 3333333333333333333333333333333333333333333333333333333 +2989 3333333333333333333333333333333333333333333333333333333 +2990 3333333333333333333333333333333333333333333333333333333 +2991 3333333333333333333333333333333333333333333333333333333 +2992 3333333333333333333333333333333333333333333333333333333 +2993 3333333333333333333333333333333333333333333333333333333 +2994 3333333333333333333333333333333333333333333333333333333 +2995 3333333333333333333333333333333333333333333333333333333 +2996 3333333333333333333333333333333333333333333333333333333 +2997 3333333333333333333333333333333333333333333333333333333 +2998 3333333333333333333333333333333333333333333333333333333 +2999 3333333333333333333333333333333333333333333333333333333 +3000 3333333333333333333333333333333333333333333333333333333 +3001 3333333333333333333333333333333333333333333333333333333 +3002 3333333333333333333333333333333333333333333333333333333 +3003 3333333333333333333333333333333333333333333333333333333 +3004 3333333333333333333333333333333333333333333333333333333 +3005 3333333333333333333333333333333333333333333333333333333 +3006 3333333333333333333333333333333333333333333333333333333 +3007 3333333333333333333333333333333333333333333333333333333 +3008 3333333333333333333333333333333333333333333333333333333 +3009 3333333333333333333333333333333333333333333333333333333 +3010 3333333333333333333333333333333333333333333333333333333 +3011 3333333333333333333333333333333333333333333333333333333 +3012 3333333333333333333333333333333333333333333333333333333 +3013 3333333333333333333333333333333333333333333333333333333 +3014 3333333333333333333333333333333333333333333333333333333 +3015 3333333333333333333333333333333333333333333333333333333 +3016 3333333333333333333333333333333333333333333333333333333 +3017 3333333333333333333333333333333333333333333333333333333 +3018 3333333333333333333333333333333333333333333333333333333 +3019 3333333333333333333333333333333333333333333333333333333 +3020 3333333333333333333333333333333333333333333333333333333 +3021 3333333333333333333333333333333333333333333333333333333 +3022 3333333333333333333333333333333333333333333333333333333 +3023 3333333333333333333333333333333333333333333333333333333 +3024 3333333333333333333333333333333333333333333333333333333 +3025 3333333333333333333333333333333333333333333333333333333 +3026 3333333333333333333333333333333333333333333333333333333 +3027 3333333333333333333333333333333333333333333333333333333 +3028 3333333333333333333333333333333333333333333333333333333 +3029 3333333333333333333333333333333333333333333333333333333 +3030 3333333333333333333333333333333333333333333333333333333 +3031 3333333333333333333333333333333333333333333333333333333 +3032 3333333333333333333333333333333333333333333333333333333 +3033 3333333333333333333333333333333333333333333333333333333 +3034 3333333333333333333333333333333333333333333333333333333 +3035 3333333333333333333333333333333333333333333333333333333 +3036 3333333333333333333333333333333333333333333333333333333 +3037 3333333333333333333333333333333333333333333333333333333 +3038 3333333333333333333333333333333333333333333333333333333 +3039 3333333333333333333333333333333333333333333333333333333 +3040 3333333333333333333333333333333333333333333333333333333 +3041 3333333333333333333333333333333333333333333333333333333 +3042 3333333333333333333333333333333333333333333333333333333 +3043 3333333333333333333333333333333333333333333333333333333 +3044 3333333333333333333333333333333333333333333333333333333 +3045 3333333333333333333333333333333333333333333333333333333 +3046 3333333333333333333333333333333333333333333333333333333 +3047 3333333333333333333333333333333333333333333333333333333 +3048 3333333333333333333333333333333333333333333333333333333 +3049 3333333333333333333333333333333333333333333333333333333 +3050 3333333333333333333333333333333333333333333333333333333 +3051 3333333333333333333333333333333333333333333333333333333 +3052 3333333333333333333333333333333333333333333333333333333 +3053 3333333333333333333333333333333333333333333333333333333 +3054 3333333333333333333333333333333333333333333333333333333 +3055 3333333333333333333333333333333333333333333333333333333 +3056 3333333333333333333333333333333333333333333333333333333 +3057 3333333333333333333333333333333333333333333333333333333 +3058 3333333333333333333333333333333333333333333333333333333 +3059 3333333333333333333333333333333333333333333333333333333 +3060 3333333333333333333333333333333333333333333333333333333 +3061 3333333333333333333333333333333333333333333333333333333 +3062 3333333333333333333333333333333333333333333333333333333 +3063 3333333333333333333333333333333333333333333333333333333 +3064 3333333333333333333333333333333333333333333333333333333 +3065 3333333333333333333333333333333333333333333333333333333 +3066 3333333333333333333333333333333333333333333333333333333 +3067 3333333333333333333333333333333333333333333333333333333 +3068 3333333333333333333333333333333333333333333333333333333 +3069 3333333333333333333333333333333333333333333333333333333 +3070 3333333333333333333333333333333333333333333333333333333 +3071 3333333333333333333333333333333333333333333333333333333 +3072 3333333333333333333333333333333333333333333333333333333 +3073 3333333333333333333333333333333333333333333333333333333 +3074 3333333333333333333333333333333333333333333333333333333 +3075 3333333333333333333333333333333333333333333333333333333 +3076 3333333333333333333333333333333333333333333333333333333 +3077 3333333333333333333333333333333333333333333333333333333 +3078 3333333333333333333333333333333333333333333333333333333 +3079 3333333333333333333333333333333333333333333333333333333 +3080 3333333333333333333333333333333333333333333333333333333 +3081 3333333333333333333333333333333333333333333333333333333 +3082 3333333333333333333333333333333333333333333333333333333 +3083 3333333333333333333333333333333333333333333333333333333 +3084 3333333333333333333333333333333333333333333333333333333 +3085 3333333333333333333333333333333333333333333333333333333 +3086 3333333333333333333333333333333333333333333333333333333 +3087 3333333333333333333333333333333333333333333333333333333 +3088 3333333333333333333333333333333333333333333333333333333 +3089 3333333333333333333333333333333333333333333333333333333 +3090 3333333333333333333333333333333333333333333333333333333 +3091 3333333333333333333333333333333333333333333333333333333 +3092 3333333333333333333333333333333333333333333333333333333 +3093 3333333333333333333333333333333333333333333333333333333 +3094 3333333333333333333333333333333333333333333333333333333 +3095 3333333333333333333333333333333333333333333333333333333 +3096 3333333333333333333333333333333333333333333333333333333 +3097 3333333333333333333333333333333333333333333333333333333 +3098 3333333333333333333333333333333333333333333333333333333 +3099 3333333333333333333333333333333333333333333333333333333 +3100 3333333333333333333333333333333333333333333333333333333 +3101 3333333333333333333333333333333333333333333333333333333 +3102 3333333333333333333333333333333333333333333333333333333 +3103 3333333333333333333333333333333333333333333333333333333 +3104 3333333333333333333333333333333333333333333333333333333 +3105 3333333333333333333333333333333333333333333333333333333 +3106 3333333333333333333333333333333333333333333333333333333 +3107 3333333333333333333333333333333333333333333333333333333 +3108 3333333333333333333333333333333333333333333333333333333 +3109 3333333333333333333333333333333333333333333333333333333 +3110 3333333333333333333333333333333333333333333333333333333 +3111 3333333333333333333333333333333333333333333333333333333 +3112 3333333333333333333333333333333333333333333333333333333 +3113 3333333333333333333333333333333333333333333333333333333 +3114 3333333333333333333333333333333333333333333333333333333 +3115 3333333333333333333333333333333333333333333333333333333 +3116 3333333333333333333333333333333333333333333333333333333 +3117 3333333333333333333333333333333333333333333333333333333 +3118 3333333333333333333333333333333333333333333333333333333 +3119 3333333333333333333333333333333333333333333333333333333 +3120 3333333333333333333333333333333333333333333333333333333 +3121 3333333333333333333333333333333333333333333333333333333 +3122 3333333333333333333333333333333333333333333333333333333 +3123 3333333333333333333333333333333333333333333333333333333 +3124 3333333333333333333333333333333333333333333333333333333 +3125 3333333333333333333333333333333333333333333333333333333 +3126 3333333333333333333333333333333333333333333333333333333 +3127 3333333333333333333333333333333333333333333333333333333 +3128 3333333333333333333333333333333333333333333333333333333 +3129 3333333333333333333333333333333333333333333333333333333 +3130 3333333333333333333333333333333333333333333333333333333 +3131 3333333333333333333333333333333333333333333333333333333 +3132 3333333333333333333333333333333333333333333333333333333 +3133 3333333333333333333333333333333333333333333333333333333 +3134 3333333333333333333333333333333333333333333333333333333 +3135 3333333333333333333333333333333333333333333333333333333 +3136 3333333333333333333333333333333333333333333333333333333 +3137 3333333333333333333333333333333333333333333333333333333 +3138 3333333333333333333333333333333333333333333333333333333 +3139 3333333333333333333333333333333333333333333333333333333 +3140 3333333333333333333333333333333333333333333333333333333 +3141 3333333333333333333333333333333333333333333333333333333 +3142 3333333333333333333333333333333333333333333333333333333 +3143 3333333333333333333333333333333333333333333333333333333 +3144 3333333333333333333333333333333333333333333333333333333 +3145 3333333333333333333333333333333333333333333333333333333 +3146 3333333333333333333333333333333333333333333333333333333 +3147 3333333333333333333333333333333333333333333333333333333 +3148 3333333333333333333333333333333333333333333333333333333 +3149 3333333333333333333333333333333333333333333333333333333 +3150 3333333333333333333333333333333333333333333333333333333 +3151 3333333333333333333333333333333333333333333333333333333 +3152 3333333333333333333333333333333333333333333333333333333 +3153 3333333333333333333333333333333333333333333333333333333 +3154 3333333333333333333333333333333333333333333333333333333 +3155 3333333333333333333333333333333333333333333333333333333 +3156 3333333333333333333333333333333333333333333333333333333 +3157 3333333333333333333333333333333333333333333333333333333 +3158 3333333333333333333333333333333333333333333333333333333 +3159 3333333333333333333333333333333333333333333333333333333 +3160 3333333333333333333333333333333333333333333333333333333 +3161 3333333333333333333333333333333333333333333333333333333 +3162 3333333333333333333333333333333333333333333333333333333 +3163 3333333333333333333333333333333333333333333333333333333 +3164 3333333333333333333333333333333333333333333333333333333 +3165 3333333333333333333333333333333333333333333333333333333 +3166 3333333333333333333333333333333333333333333333333333333 +3167 3333333333333333333333333333333333333333333333333333333 +3168 3333333333333333333333333333333333333333333333333333333 +3169 3333333333333333333333333333333333333333333333333333333 +3170 3333333333333333333333333333333333333333333333333333333 +3171 3333333333333333333333333333333333333333333333333333333 +3172 3333333333333333333333333333333333333333333333333333333 +3173 3333333333333333333333333333333333333333333333333333333 +3174 3333333333333333333333333333333333333333333333333333333 +3175 3333333333333333333333333333333333333333333333333333333 +3176 3333333333333333333333333333333333333333333333333333333 +3177 3333333333333333333333333333333333333333333333333333333 +3178 3333333333333333333333333333333333333333333333333333333 +3179 3333333333333333333333333333333333333333333333333333333 +3180 3333333333333333333333333333333333333333333333333333333 +3181 3333333333333333333333333333333333333333333333333333333 +3182 3333333333333333333333333333333333333333333333333333333 +3183 3333333333333333333333333333333333333333333333333333333 +3184 3333333333333333333333333333333333333333333333333333333 +3185 3333333333333333333333333333333333333333333333333333333 +3186 3333333333333333333333333333333333333333333333333333333 +3187 3333333333333333333333333333333333333333333333333333333 +3188 3333333333333333333333333333333333333333333333333333333 +3189 3333333333333333333333333333333333333333333333333333333 +3190 3333333333333333333333333333333333333333333333333333333 +3191 3333333333333333333333333333333333333333333333333333333 +3192 3333333333333333333333333333333333333333333333333333333 +3193 3333333333333333333333333333333333333333333333333333333 +3194 3333333333333333333333333333333333333333333333333333333 +3195 3333333333333333333333333333333333333333333333333333333 +3196 3333333333333333333333333333333333333333333333333333333 +3197 3333333333333333333333333333333333333333333333333333333 +3198 3333333333333333333333333333333333333333333333333333333 +3199 3333333333333333333333333333333333333333333333333333333 +3200 3333333333333333333333333333333333333333333333333333333 +3201 3333333333333333333333333333333333333333333333333333333 +3202 3333333333333333333333333333333333333333333333333333333 +3203 3333333333333333333333333333333333333333333333333333333 +3204 3333333333333333333333333333333333333333333333333333333 +3205 3333333333333333333333333333333333333333333333333333333 +3206 3333333333333333333333333333333333333333333333333333333 +3207 3333333333333333333333333333333333333333333333333333333 +3208 3333333333333333333333333333333333333333333333333333333 +3209 3333333333333333333333333333333333333333333333333333333 +3210 3333333333333333333333333333333333333333333333333333333 +3211 3333333333333333333333333333333333333333333333333333333 +3212 3333333333333333333333333333333333333333333333333333333 +3213 3333333333333333333333333333333333333333333333333333333 +3214 3333333333333333333333333333333333333333333333333333333 +3215 3333333333333333333333333333333333333333333333333333333 +3216 3333333333333333333333333333333333333333333333333333333 +3217 3333333333333333333333333333333333333333333333333333333 +3218 3333333333333333333333333333333333333333333333333333333 +3219 3333333333333333333333333333333333333333333333333333333 +3220 3333333333333333333333333333333333333333333333333333333 +3221 3333333333333333333333333333333333333333333333333333333 +3222 3333333333333333333333333333333333333333333333333333333 +3223 3333333333333333333333333333333333333333333333333333333 +3224 3333333333333333333333333333333333333333333333333333333 +3225 3333333333333333333333333333333333333333333333333333333 +3226 3333333333333333333333333333333333333333333333333333333 +3227 3333333333333333333333333333333333333333333333333333333 +3228 3333333333333333333333333333333333333333333333333333333 +3229 3333333333333333333333333333333333333333333333333333333 +3230 3333333333333333333333333333333333333333333333333333333 +3231 3333333333333333333333333333333333333333333333333333333 +3232 3333333333333333333333333333333333333333333333333333333 +3233 3333333333333333333333333333333333333333333333333333333 +3234 3333333333333333333333333333333333333333333333333333333 +3235 3333333333333333333333333333333333333333333333333333333 +3236 3333333333333333333333333333333333333333333333333333333 +3237 3333333333333333333333333333333333333333333333333333333 +3238 3333333333333333333333333333333333333333333333333333333 +3239 3333333333333333333333333333333333333333333333333333333 +3240 3333333333333333333333333333333333333333333333333333333 +3241 3333333333333333333333333333333333333333333333333333333 +3242 3333333333333333333333333333333333333333333333333333333 +3243 3333333333333333333333333333333333333333333333333333333 +3244 3333333333333333333333333333333333333333333333333333333 +3245 3333333333333333333333333333333333333333333333333333333 +3246 3333333333333333333333333333333333333333333333333333333 +3247 3333333333333333333333333333333333333333333333333333333 +3248 3333333333333333333333333333333333333333333333333333333 +3249 3333333333333333333333333333333333333333333333333333333 +3250 3333333333333333333333333333333333333333333333333333333 +3251 3333333333333333333333333333333333333333333333333333333 +3252 3333333333333333333333333333333333333333333333333333333 +3253 3333333333333333333333333333333333333333333333333333333 +3254 3333333333333333333333333333333333333333333333333333333 +3255 3333333333333333333333333333333333333333333333333333333 +3256 3333333333333333333333333333333333333333333333333333333 +3257 3333333333333333333333333333333333333333333333333333333 +3258 3333333333333333333333333333333333333333333333333333333 +3259 3333333333333333333333333333333333333333333333333333333 +3260 3333333333333333333333333333333333333333333333333333333 +3261 3333333333333333333333333333333333333333333333333333333 +3262 3333333333333333333333333333333333333333333333333333333 +3263 3333333333333333333333333333333333333333333333333333333 +3264 3333333333333333333333333333333333333333333333333333333 +3265 3333333333333333333333333333333333333333333333333333333 +3266 3333333333333333333333333333333333333333333333333333333 +3267 3333333333333333333333333333333333333333333333333333333 +3268 3333333333333333333333333333333333333333333333333333333 +3269 3333333333333333333333333333333333333333333333333333333 +3270 3333333333333333333333333333333333333333333333333333333 +3271 3333333333333333333333333333333333333333333333333333333 +3272 3333333333333333333333333333333333333333333333333333333 +3273 3333333333333333333333333333333333333333333333333333333 +3274 3333333333333333333333333333333333333333333333333333333 +3275 3333333333333333333333333333333333333333333333333333333 +3276 3333333333333333333333333333333333333333333333333333333 +3277 3333333333333333333333333333333333333333333333333333333 +3278 3333333333333333333333333333333333333333333333333333333 +3279 3333333333333333333333333333333333333333333333333333333 +3280 3333333333333333333333333333333333333333333333333333333 +3281 3333333333333333333333333333333333333333333333333333333 +3282 3333333333333333333333333333333333333333333333333333333 +3283 3333333333333333333333333333333333333333333333333333333 +3284 3333333333333333333333333333333333333333333333333333333 +3285 3333333333333333333333333333333333333333333333333333333 +3286 3333333333333333333333333333333333333333333333333333333 +3287 3333333333333333333333333333333333333333333333333333333 +3288 3333333333333333333333333333333333333333333333333333333 +3289 3333333333333333333333333333333333333333333333333333333 +3290 3333333333333333333333333333333333333333333333333333333 +3291 3333333333333333333333333333333333333333333333333333333 +3292 3333333333333333333333333333333333333333333333333333333 +3293 3333333333333333333333333333333333333333333333333333333 +3294 3333333333333333333333333333333333333333333333333333333 +3295 3333333333333333333333333333333333333333333333333333333 +3296 3333333333333333333333333333333333333333333333333333333 +3297 3333333333333333333333333333333333333333333333333333333 +3298 3333333333333333333333333333333333333333333333333333333 +3299 3333333333333333333333333333333333333333333333333333333 +3300 3333333333333333333333333333333333333333333333333333333 +3301 3333333333333333333333333333333333333333333333333333333 +3302 3333333333333333333333333333333333333333333333333333333 +3303 3333333333333333333333333333333333333333333333333333333 +3304 3333333333333333333333333333333333333333333333333333333 +3305 3333333333333333333333333333333333333333333333333333333 +3306 3333333333333333333333333333333333333333333333333333333 +3307 3333333333333333333333333333333333333333333333333333333 +3308 3333333333333333333333333333333333333333333333333333333 +3309 3333333333333333333333333333333333333333333333333333333 +3310 3333333333333333333333333333333333333333333333333333333 +3311 3333333333333333333333333333333333333333333333333333333 +3312 3333333333333333333333333333333333333333333333333333333 +3313 3333333333333333333333333333333333333333333333333333333 +3314 3333333333333333333333333333333333333333333333333333333 +3315 3333333333333333333333333333333333333333333333333333333 +3316 3333333333333333333333333333333333333333333333333333333 +3317 3333333333333333333333333333333333333333333333333333333 +3318 3333333333333333333333333333333333333333333333333333333 +3319 3333333333333333333333333333333333333333333333333333333 +3320 3333333333333333333333333333333333333333333333333333333 +3321 3333333333333333333333333333333333333333333333333333333 +3322 3333333333333333333333333333333333333333333333333333333 +3323 3333333333333333333333333333333333333333333333333333333 +3324 3333333333333333333333333333333333333333333333333333333 +3325 3333333333333333333333333333333333333333333333333333333 +3326 3333333333333333333333333333333333333333333333333333333 +3327 3333333333333333333333333333333333333333333333333333333 +3328 3333333333333333333333333333333333333333333333333333333 +3329 3333333333333333333333333333333333333333333333333333333 +3330 3333333333333333333333333333333333333333333333333333333 +3331 3333333333333333333333333333333333333333333333333333333 +3332 3333333333333333333333333333333333333333333333333333333 +3333 3333333333333333333333333333333333333333333333333333333 +3334 3333333333333333333333333333333333333333333333333333333 +3335 3333333333333333333333333333333333333333333333333333333 +3336 3333333333333333333333333333333333333333333333333333333 +3337 3333333333333333333333333333333333333333333333333333333 +3338 3333333333333333333333333333333333333333333333333333333 +3339 3333333333333333333333333333333333333333333333333333333 +3340 3333333333333333333333333333333333333333333333333333333 +3341 3333333333333333333333333333333333333333333333333333333 +3342 3333333333333333333333333333333333333333333333333333333 +3343 3333333333333333333333333333333333333333333333333333333 +3344 3333333333333333333333333333333333333333333333333333333 +3345 3333333333333333333333333333333333333333333333333333333 +3346 3333333333333333333333333333333333333333333333333333333 +3347 3333333333333333333333333333333333333333333333333333333 +3348 3333333333333333333333333333333333333333333333333333333 +3349 3333333333333333333333333333333333333333333333333333333 +3350 3333333333333333333333333333333333333333333333333333333 +3351 3333333333333333333333333333333333333333333333333333333 +3352 3333333333333333333333333333333333333333333333333333333 +3353 3333333333333333333333333333333333333333333333333333333 +3354 3333333333333333333333333333333333333333333333333333333 +3355 3333333333333333333333333333333333333333333333333333333 +3356 3333333333333333333333333333333333333333333333333333333 +3357 3333333333333333333333333333333333333333333333333333333 +3358 3333333333333333333333333333333333333333333333333333333 +3359 3333333333333333333333333333333333333333333333333333333 +3360 3333333333333333333333333333333333333333333333333333333 +3361 3333333333333333333333333333333333333333333333333333333 +3362 3333333333333333333333333333333333333333333333333333333 +3363 3333333333333333333333333333333333333333333333333333333 +3364 3333333333333333333333333333333333333333333333333333333 +3365 3333333333333333333333333333333333333333333333333333333 +3366 3333333333333333333333333333333333333333333333333333333 +3367 3333333333333333333333333333333333333333333333333333333 +3368 3333333333333333333333333333333333333333333333333333333 +3369 3333333333333333333333333333333333333333333333333333333 +3370 3333333333333333333333333333333333333333333333333333333 +3371 3333333333333333333333333333333333333333333333333333333 +3372 3333333333333333333333333333333333333333333333333333333 +3373 3333333333333333333333333333333333333333333333333333333 +3374 3333333333333333333333333333333333333333333333333333333 +3375 3333333333333333333333333333333333333333333333333333333 +3376 3333333333333333333333333333333333333333333333333333333 +3377 3333333333333333333333333333333333333333333333333333333 +3378 3333333333333333333333333333333333333333333333333333333 +3379 3333333333333333333333333333333333333333333333333333333 +3380 3333333333333333333333333333333333333333333333333333333 +3381 3333333333333333333333333333333333333333333333333333333 +3382 3333333333333333333333333333333333333333333333333333333 +3383 3333333333333333333333333333333333333333333333333333333 +3384 3333333333333333333333333333333333333333333333333333333 +3385 3333333333333333333333333333333333333333333333333333333 +3386 3333333333333333333333333333333333333333333333333333333 +3387 3333333333333333333333333333333333333333333333333333333 +3388 3333333333333333333333333333333333333333333333333333333 +3389 3333333333333333333333333333333333333333333333333333333 +3390 3333333333333333333333333333333333333333333333333333333 +3391 3333333333333333333333333333333333333333333333333333333 +3392 3333333333333333333333333333333333333333333333333333333 +3393 3333333333333333333333333333333333333333333333333333333 +3394 3333333333333333333333333333333333333333333333333333333 +3395 3333333333333333333333333333333333333333333333333333333 +3396 3333333333333333333333333333333333333333333333333333333 +3397 3333333333333333333333333333333333333333333333333333333 +3398 3333333333333333333333333333333333333333333333333333333 +3399 3333333333333333333333333333333333333333333333333333333 +3400 3333333333333333333333333333333333333333333333333333333 +3401 3333333333333333333333333333333333333333333333333333333 +3402 3333333333333333333333333333333333333333333333333333333 +3403 3333333333333333333333333333333333333333333333333333333 +3404 3333333333333333333333333333333333333333333333333333333 +3405 3333333333333333333333333333333333333333333333333333333 +3406 3333333333333333333333333333333333333333333333333333333 +3407 3333333333333333333333333333333333333333333333333333333 +3408 3333333333333333333333333333333333333333333333333333333 +3409 3333333333333333333333333333333333333333333333333333333 +3410 3333333333333333333333333333333333333333333333333333333 +3411 3333333333333333333333333333333333333333333333333333333 +3412 3333333333333333333333333333333333333333333333333333333 +3413 3333333333333333333333333333333333333333333333333333333 +3414 3333333333333333333333333333333333333333333333333333333 +3415 3333333333333333333333333333333333333333333333333333333 +3416 3333333333333333333333333333333333333333333333333333333 +3417 3333333333333333333333333333333333333333333333333333333 +3418 3333333333333333333333333333333333333333333333333333333 +3419 3333333333333333333333333333333333333333333333333333333 +3420 3333333333333333333333333333333333333333333333333333333 +3421 3333333333333333333333333333333333333333333333333333333 +3422 3333333333333333333333333333333333333333333333333333333 +3423 3333333333333333333333333333333333333333333333333333333 +3424 3333333333333333333333333333333333333333333333333333333 +3425 3333333333333333333333333333333333333333333333333333333 +3426 3333333333333333333333333333333333333333333333333333333 +3427 3333333333333333333333333333333333333333333333333333333 +3428 3333333333333333333333333333333333333333333333333333333 +3429 3333333333333333333333333333333333333333333333333333333 +3430 3333333333333333333333333333333333333333333333333333333 +3431 3333333333333333333333333333333333333333333333333333333 +3432 3333333333333333333333333333333333333333333333333333333 +3433 3333333333333333333333333333333333333333333333333333333 +3434 3333333333333333333333333333333333333333333333333333333 +3435 3333333333333333333333333333333333333333333333333333333 +3436 3333333333333333333333333333333333333333333333333333333 +3437 3333333333333333333333333333333333333333333333333333333 +3438 3333333333333333333333333333333333333333333333333333333 +3439 3333333333333333333333333333333333333333333333333333333 +3440 3333333333333333333333333333333333333333333333333333333 +3441 3333333333333333333333333333333333333333333333333333333 +3442 3333333333333333333333333333333333333333333333333333333 +3443 3333333333333333333333333333333333333333333333333333333 +3444 3333333333333333333333333333333333333333333333333333333 +3445 3333333333333333333333333333333333333333333333333333333 +3446 3333333333333333333333333333333333333333333333333333333 +3447 3333333333333333333333333333333333333333333333333333333 +3448 3333333333333333333333333333333333333333333333333333333 +3449 3333333333333333333333333333333333333333333333333333333 +3450 3333333333333333333333333333333333333333333333333333333 +3451 3333333333333333333333333333333333333333333333333333333 +3452 3333333333333333333333333333333333333333333333333333333 +3453 3333333333333333333333333333333333333333333333333333333 +3454 3333333333333333333333333333333333333333333333333333333 +3455 3333333333333333333333333333333333333333333333333333333 +3456 3333333333333333333333333333333333333333333333333333333 +3457 3333333333333333333333333333333333333333333333333333333 +3458 3333333333333333333333333333333333333333333333333333333 +3459 3333333333333333333333333333333333333333333333333333333 +3460 3333333333333333333333333333333333333333333333333333333 +3461 3333333333333333333333333333333333333333333333333333333 +3462 3333333333333333333333333333333333333333333333333333333 +3463 3333333333333333333333333333333333333333333333333333333 +3464 3333333333333333333333333333333333333333333333333333333 +3465 3333333333333333333333333333333333333333333333333333333 +3466 3333333333333333333333333333333333333333333333333333333 +3467 3333333333333333333333333333333333333333333333333333333 +3468 3333333333333333333333333333333333333333333333333333333 +3469 3333333333333333333333333333333333333333333333333333333 +3470 3333333333333333333333333333333333333333333333333333333 +3471 3333333333333333333333333333333333333333333333333333333 +3472 3333333333333333333333333333333333333333333333333333333 +3473 3333333333333333333333333333333333333333333333333333333 +3474 3333333333333333333333333333333333333333333333333333333 +3475 3333333333333333333333333333333333333333333333333333333 +3476 3333333333333333333333333333333333333333333333333333333 +3477 3333333333333333333333333333333333333333333333333333333 +3478 3333333333333333333333333333333333333333333333333333333 +3479 3333333333333333333333333333333333333333333333333333333 +3480 3333333333333333333333333333333333333333333333333333333 +3481 3333333333333333333333333333333333333333333333333333333 +3482 3333333333333333333333333333333333333333333333333333333 +3483 3333333333333333333333333333333333333333333333333333333 +3484 3333333333333333333333333333333333333333333333333333333 +3485 3333333333333333333333333333333333333333333333333333333 +3486 3333333333333333333333333333333333333333333333333333333 +3487 3333333333333333333333333333333333333333333333333333333 +3488 3333333333333333333333333333333333333333333333333333333 +3489 3333333333333333333333333333333333333333333333333333333 +3490 3333333333333333333333333333333333333333333333333333333 +3491 3333333333333333333333333333333333333333333333333333333 +3492 3333333333333333333333333333333333333333333333333333333 +3493 3333333333333333333333333333333333333333333333333333333 +3494 3333333333333333333333333333333333333333333333333333333 +3495 3333333333333333333333333333333333333333333333333333333 +3496 3333333333333333333333333333333333333333333333333333333 +3497 3333333333333333333333333333333333333333333333333333333 +3498 3333333333333333333333333333333333333333333333333333333 +3499 3333333333333333333333333333333333333333333333333333333 +3500 3333333333333333333333333333333333333333333333333333333 +3501 3333333333333333333333333333333333333333333333333333333 +3502 3333333333333333333333333333333333333333333333333333333 +3503 3333333333333333333333333333333333333333333333333333333 +3504 3333333333333333333333333333333333333333333333333333333 +3505 3333333333333333333333333333333333333333333333333333333 +3506 3333333333333333333333333333333333333333333333333333333 +3507 3333333333333333333333333333333333333333333333333333333 +3508 3333333333333333333333333333333333333333333333333333333 +3509 3333333333333333333333333333333333333333333333333333333 +3510 3333333333333333333333333333333333333333333333333333333 +3511 3333333333333333333333333333333333333333333333333333333 +3512 3333333333333333333333333333333333333333333333333333333 +3513 3333333333333333333333333333333333333333333333333333333 +3514 3333333333333333333333333333333333333333333333333333333 +3515 3333333333333333333333333333333333333333333333333333333 +3516 3333333333333333333333333333333333333333333333333333333 +3517 3333333333333333333333333333333333333333333333333333333 +3518 3333333333333333333333333333333333333333333333333333333 +3519 3333333333333333333333333333333333333333333333333333333 +3520 3333333333333333333333333333333333333333333333333333333 +3521 3333333333333333333333333333333333333333333333333333333 +3522 3333333333333333333333333333333333333333333333333333333 +3523 3333333333333333333333333333333333333333333333333333333 +3524 3333333333333333333333333333333333333333333333333333333 +3525 3333333333333333333333333333333333333333333333333333333 +3526 3333333333333333333333333333333333333333333333333333333 +3527 3333333333333333333333333333333333333333333333333333333 +3528 3333333333333333333333333333333333333333333333333333333 +3529 3333333333333333333333333333333333333333333333333333333 +3530 3333333333333333333333333333333333333333333333333333333 +3531 3333333333333333333333333333333333333333333333333333333 +3532 3333333333333333333333333333333333333333333333333333333 +3533 3333333333333333333333333333333333333333333333333333333 +3534 3333333333333333333333333333333333333333333333333333333 +3535 3333333333333333333333333333333333333333333333333333333 +3536 3333333333333333333333333333333333333333333333333333333 +3537 3333333333333333333333333333333333333333333333333333333 +3538 3333333333333333333333333333333333333333333333333333333 +3539 3333333333333333333333333333333333333333333333333333333 +3540 3333333333333333333333333333333333333333333333333333333 +3541 3333333333333333333333333333333333333333333333333333333 +3542 3333333333333333333333333333333333333333333333333333333 +3543 3333333333333333333333333333333333333333333333333333333 +3544 3333333333333333333333333333333333333333333333333333333 +3545 3333333333333333333333333333333333333333333333333333333 +3546 3333333333333333333333333333333333333333333333333333333 +3547 3333333333333333333333333333333333333333333333333333333 +3548 3333333333333333333333333333333333333333333333333333333 +3549 3333333333333333333333333333333333333333333333333333333 +3550 3333333333333333333333333333333333333333333333333333333 +3551 3333333333333333333333333333333333333333333333333333333 +3552 3333333333333333333333333333333333333333333333333333333 +3553 3333333333333333333333333333333333333333333333333333333 +3554 3333333333333333333333333333333333333333333333333333333 +3555 3333333333333333333333333333333333333333333333333333333 +3556 3333333333333333333333333333333333333333333333333333333 +3557 3333333333333333333333333333333333333333333333333333333 +3558 3333333333333333333333333333333333333333333333333333333 +3559 3333333333333333333333333333333333333333333333333333333 +3560 3333333333333333333333333333333333333333333333333333333 +3561 3333333333333333333333333333333333333333333333333333333 +3562 3333333333333333333333333333333333333333333333333333333 +3563 3333333333333333333333333333333333333333333333333333333 +3564 3333333333333333333333333333333333333333333333333333333 +3565 3333333333333333333333333333333333333333333333333333333 +3566 3333333333333333333333333333333333333333333333333333333 +3567 3333333333333333333333333333333333333333333333333333333 +3568 3333333333333333333333333333333333333333333333333333333 +3569 3333333333333333333333333333333333333333333333333333333 +3570 3333333333333333333333333333333333333333333333333333333 +3571 3333333333333333333333333333333333333333333333333333333 +3572 3333333333333333333333333333333333333333333333333333333 +3573 3333333333333333333333333333333333333333333333333333333 +3574 3333333333333333333333333333333333333333333333333333333 +3575 3333333333333333333333333333333333333333333333333333333 +3576 3333333333333333333333333333333333333333333333333333333 +3577 3333333333333333333333333333333333333333333333333333333 +3578 3333333333333333333333333333333333333333333333333333333 +3579 3333333333333333333333333333333333333333333333333333333 +3580 3333333333333333333333333333333333333333333333333333333 +3581 3333333333333333333333333333333333333333333333333333333 +3582 3333333333333333333333333333333333333333333333333333333 +3583 3333333333333333333333333333333333333333333333333333333 +3584 3333333333333333333333333333333333333333333333333333333 +3585 3333333333333333333333333333333333333333333333333333333 +3586 3333333333333333333333333333333333333333333333333333333 +3587 3333333333333333333333333333333333333333333333333333333 +3588 3333333333333333333333333333333333333333333333333333333 +3589 3333333333333333333333333333333333333333333333333333333 +3590 3333333333333333333333333333333333333333333333333333333 +3591 3333333333333333333333333333333333333333333333333333333 +3592 3333333333333333333333333333333333333333333333333333333 +3593 3333333333333333333333333333333333333333333333333333333 +3594 3333333333333333333333333333333333333333333333333333333 +3595 3333333333333333333333333333333333333333333333333333333 +3596 3333333333333333333333333333333333333333333333333333333 +3597 3333333333333333333333333333333333333333333333333333333 +3598 3333333333333333333333333333333333333333333333333333333 +3599 3333333333333333333333333333333333333333333333333333333 +3600 3333333333333333333333333333333333333333333333333333333 +3601 3333333333333333333333333333333333333333333333333333333 +3602 3333333333333333333333333333333333333333333333333333333 +3603 3333333333333333333333333333333333333333333333333333333 +3604 3333333333333333333333333333333333333333333333333333333 +3605 3333333333333333333333333333333333333333333333333333333 +3606 3333333333333333333333333333333333333333333333333333333 +3607 3333333333333333333333333333333333333333333333333333333 +3608 3333333333333333333333333333333333333333333333333333333 +3609 3333333333333333333333333333333333333333333333333333333 +3610 3333333333333333333333333333333333333333333333333333333 +3611 3333333333333333333333333333333333333333333333333333333 +3612 3333333333333333333333333333333333333333333333333333333 +3613 3333333333333333333333333333333333333333333333333333333 +3614 3333333333333333333333333333333333333333333333333333333 +3615 3333333333333333333333333333333333333333333333333333333 +3616 3333333333333333333333333333333333333333333333333333333 +3617 3333333333333333333333333333333333333333333333333333333 +3618 3333333333333333333333333333333333333333333333333333333 +3619 3333333333333333333333333333333333333333333333333333333 +3620 3333333333333333333333333333333333333333333333333333333 +3621 3333333333333333333333333333333333333333333333333333333 +3622 3333333333333333333333333333333333333333333333333333333 +3623 3333333333333333333333333333333333333333333333333333333 +3624 3333333333333333333333333333333333333333333333333333333 +3625 3333333333333333333333333333333333333333333333333333333 +3626 3333333333333333333333333333333333333333333333333333333 +3627 3333333333333333333333333333333333333333333333333333333 +3628 3333333333333333333333333333333333333333333333333333333 +3629 3333333333333333333333333333333333333333333333333333333 +3630 3333333333333333333333333333333333333333333333333333333 +3631 3333333333333333333333333333333333333333333333333333333 +3632 3333333333333333333333333333333333333333333333333333333 +3633 3333333333333333333333333333333333333333333333333333333 +3634 3333333333333333333333333333333333333333333333333333333 +3635 3333333333333333333333333333333333333333333333333333333 +3636 3333333333333333333333333333333333333333333333333333333 +3637 3333333333333333333333333333333333333333333333333333333 +3638 3333333333333333333333333333333333333333333333333333333 +3639 3333333333333333333333333333333333333333333333333333333 +3640 3333333333333333333333333333333333333333333333333333333 +3641 3333333333333333333333333333333333333333333333333333333 +3642 3333333333333333333333333333333333333333333333333333333 +3643 3333333333333333333333333333333333333333333333333333333 +3644 3333333333333333333333333333333333333333333333333333333 +3645 3333333333333333333333333333333333333333333333333333333 +3646 3333333333333333333333333333333333333333333333333333333 +3647 3333333333333333333333333333333333333333333333333333333 +3648 3333333333333333333333333333333333333333333333333333333 +3649 3333333333333333333333333333333333333333333333333333333 +3650 3333333333333333333333333333333333333333333333333333333 +3651 3333333333333333333333333333333333333333333333333333333 +3652 3333333333333333333333333333333333333333333333333333333 +3653 3333333333333333333333333333333333333333333333333333333 +3654 3333333333333333333333333333333333333333333333333333333 +3655 3333333333333333333333333333333333333333333333333333333 +3656 3333333333333333333333333333333333333333333333333333333 +3657 3333333333333333333333333333333333333333333333333333333 +3658 3333333333333333333333333333333333333333333333333333333 +3659 3333333333333333333333333333333333333333333333333333333 +3660 3333333333333333333333333333333333333333333333333333333 +3661 3333333333333333333333333333333333333333333333333333333 +3662 3333333333333333333333333333333333333333333333333333333 +3663 3333333333333333333333333333333333333333333333333333333 +3664 3333333333333333333333333333333333333333333333333333333 +3665 3333333333333333333333333333333333333333333333333333333 +3666 3333333333333333333333333333333333333333333333333333333 +3667 3333333333333333333333333333333333333333333333333333333 +3668 3333333333333333333333333333333333333333333333333333333 +3669 3333333333333333333333333333333333333333333333333333333 +3670 3333333333333333333333333333333333333333333333333333333 +3671 3333333333333333333333333333333333333333333333333333333 +3672 3333333333333333333333333333333333333333333333333333333 +3673 3333333333333333333333333333333333333333333333333333333 +3674 3333333333333333333333333333333333333333333333333333333 +3675 3333333333333333333333333333333333333333333333333333333 +3676 3333333333333333333333333333333333333333333333333333333 +3677 3333333333333333333333333333333333333333333333333333333 +3678 3333333333333333333333333333333333333333333333333333333 +3679 3333333333333333333333333333333333333333333333333333333 +3680 3333333333333333333333333333333333333333333333333333333 +3681 3333333333333333333333333333333333333333333333333333333 +3682 3333333333333333333333333333333333333333333333333333333 +3683 3333333333333333333333333333333333333333333333333333333 +3684 3333333333333333333333333333333333333333333333333333333 +3685 3333333333333333333333333333333333333333333333333333333 +3686 3333333333333333333333333333333333333333333333333333333 +3687 3333333333333333333333333333333333333333333333333333333 +3688 3333333333333333333333333333333333333333333333333333333 +3689 3333333333333333333333333333333333333333333333333333333 +3690 3333333333333333333333333333333333333333333333333333333 +3691 3333333333333333333333333333333333333333333333333333333 +3692 3333333333333333333333333333333333333333333333333333333 +3693 3333333333333333333333333333333333333333333333333333333 +3694 3333333333333333333333333333333333333333333333333333333 +3695 3333333333333333333333333333333333333333333333333333333 +3696 3333333333333333333333333333333333333333333333333333333 +3697 3333333333333333333333333333333333333333333333333333333 +3698 3333333333333333333333333333333333333333333333333333333 +3699 3333333333333333333333333333333333333333333333333333333 +3700 3333333333333333333333333333333333333333333333333333333 +3701 3333333333333333333333333333333333333333333333333333333 +3702 3333333333333333333333333333333333333333333333333333333 +3703 3333333333333333333333333333333333333333333333333333333 +3704 3333333333333333333333333333333333333333333333333333333 +3705 3333333333333333333333333333333333333333333333333333333 +3706 3333333333333333333333333333333333333333333333333333333 +3707 3333333333333333333333333333333333333333333333333333333 +3708 3333333333333333333333333333333333333333333333333333333 +3709 3333333333333333333333333333333333333333333333333333333 +3710 3333333333333333333333333333333333333333333333333333333 +3711 3333333333333333333333333333333333333333333333333333333 +3712 3333333333333333333333333333333333333333333333333333333 +3713 3333333333333333333333333333333333333333333333333333333 +3714 3333333333333333333333333333333333333333333333333333333 +3715 3333333333333333333333333333333333333333333333333333333 +3716 3333333333333333333333333333333333333333333333333333333 +3717 3333333333333333333333333333333333333333333333333333333 +3718 3333333333333333333333333333333333333333333333333333333 +3719 3333333333333333333333333333333333333333333333333333333 +3720 3333333333333333333333333333333333333333333333333333333 +3721 3333333333333333333333333333333333333333333333333333333 +3722 3333333333333333333333333333333333333333333333333333333 +3723 3333333333333333333333333333333333333333333333333333333 +3724 3333333333333333333333333333333333333333333333333333333 +3725 3333333333333333333333333333333333333333333333333333333 +3726 3333333333333333333333333333333333333333333333333333333 +3727 3333333333333333333333333333333333333333333333333333333 +3728 3333333333333333333333333333333333333333333333333333333 +3729 3333333333333333333333333333333333333333333333333333333 +3730 3333333333333333333333333333333333333333333333333333333 +3731 3333333333333333333333333333333333333333333333333333333 +3732 3333333333333333333333333333333333333333333333333333333 +3733 3333333333333333333333333333333333333333333333333333333 +3734 3333333333333333333333333333333333333333333333333333333 +3735 3333333333333333333333333333333333333333333333333333333 +3736 3333333333333333333333333333333333333333333333333333333 +3737 3333333333333333333333333333333333333333333333333333333 +3738 3333333333333333333333333333333333333333333333333333333 +3739 3333333333333333333333333333333333333333333333333333333 +3740 3333333333333333333333333333333333333333333333333333333 +3741 3333333333333333333333333333333333333333333333333333333 +3742 3333333333333333333333333333333333333333333333333333333 +3743 3333333333333333333333333333333333333333333333333333333 +3744 3333333333333333333333333333333333333333333333333333333 +3745 3333333333333333333333333333333333333333333333333333333 +3746 3333333333333333333333333333333333333333333333333333333 +3747 3333333333333333333333333333333333333333333333333333333 +3748 3333333333333333333333333333333333333333333333333333333 +3749 3333333333333333333333333333333333333333333333333333333 +3750 3333333333333333333333333333333333333333333333333333333 +3751 3333333333333333333333333333333333333333333333333333333 +3752 3333333333333333333333333333333333333333333333333333333 +3753 3333333333333333333333333333333333333333333333333333333 +3754 3333333333333333333333333333333333333333333333333333333 +3755 3333333333333333333333333333333333333333333333333333333 +3756 3333333333333333333333333333333333333333333333333333333 +3757 3333333333333333333333333333333333333333333333333333333 +3758 3333333333333333333333333333333333333333333333333333333 +3759 3333333333333333333333333333333333333333333333333333333 +3760 3333333333333333333333333333333333333333333333333333333 +3761 3333333333333333333333333333333333333333333333333333333 +3762 3333333333333333333333333333333333333333333333333333333 +3763 3333333333333333333333333333333333333333333333333333333 +3764 3333333333333333333333333333333333333333333333333333333 +3765 3333333333333333333333333333333333333333333333333333333 +3766 3333333333333333333333333333333333333333333333333333333 +3767 3333333333333333333333333333333333333333333333333333333 +3768 3333333333333333333333333333333333333333333333333333333 +3769 3333333333333333333333333333333333333333333333333333333 +3770 3333333333333333333333333333333333333333333333333333333 +3771 3333333333333333333333333333333333333333333333333333333 +3772 3333333333333333333333333333333333333333333333333333333 +3773 3333333333333333333333333333333333333333333333333333333 +3774 3333333333333333333333333333333333333333333333333333333 +3775 3333333333333333333333333333333333333333333333333333333 +3776 3333333333333333333333333333333333333333333333333333333 +3777 3333333333333333333333333333333333333333333333333333333 +3778 3333333333333333333333333333333333333333333333333333333 +3779 3333333333333333333333333333333333333333333333333333333 +3780 3333333333333333333333333333333333333333333333333333333 +3781 3333333333333333333333333333333333333333333333333333333 +3782 3333333333333333333333333333333333333333333333333333333 +3783 3333333333333333333333333333333333333333333333333333333 +3784 3333333333333333333333333333333333333333333333333333333 +3785 3333333333333333333333333333333333333333333333333333333 +3786 3333333333333333333333333333333333333333333333333333333 +3787 3333333333333333333333333333333333333333333333333333333 +3788 3333333333333333333333333333333333333333333333333333333 +3789 3333333333333333333333333333333333333333333333333333333 +3790 3333333333333333333333333333333333333333333333333333333 +3791 3333333333333333333333333333333333333333333333333333333 +3792 3333333333333333333333333333333333333333333333333333333 +3793 3333333333333333333333333333333333333333333333333333333 +3794 3333333333333333333333333333333333333333333333333333333 +3795 3333333333333333333333333333333333333333333333333333333 +3796 3333333333333333333333333333333333333333333333333333333 +3797 3333333333333333333333333333333333333333333333333333333 +3798 3333333333333333333333333333333333333333333333333333333 +3799 3333333333333333333333333333333333333333333333333333333 +3800 3333333333333333333333333333333333333333333333333333333 +3801 3333333333333333333333333333333333333333333333333333333 +3802 3333333333333333333333333333333333333333333333333333333 +3803 3333333333333333333333333333333333333333333333333333333 +3804 3333333333333333333333333333333333333333333333333333333 +3805 3333333333333333333333333333333333333333333333333333333 +3806 3333333333333333333333333333333333333333333333333333333 +3807 3333333333333333333333333333333333333333333333333333333 +3808 3333333333333333333333333333333333333333333333333333333 +3809 3333333333333333333333333333333333333333333333333333333 +3810 3333333333333333333333333333333333333333333333333333333 +3811 3333333333333333333333333333333333333333333333333333333 +3812 3333333333333333333333333333333333333333333333333333333 +3813 3333333333333333333333333333333333333333333333333333333 +3814 3333333333333333333333333333333333333333333333333333333 +3815 3333333333333333333333333333333333333333333333333333333 +3816 3333333333333333333333333333333333333333333333333333333 +3817 3333333333333333333333333333333333333333333333333333333 +3818 3333333333333333333333333333333333333333333333333333333 +3819 3333333333333333333333333333333333333333333333333333333 +3820 3333333333333333333333333333333333333333333333333333333 +3821 3333333333333333333333333333333333333333333333333333333 +3822 3333333333333333333333333333333333333333333333333333333 +3823 3333333333333333333333333333333333333333333333333333333 +3824 3333333333333333333333333333333333333333333333333333333 +3825 3333333333333333333333333333333333333333333333333333333 +3826 3333333333333333333333333333333333333333333333333333333 +3827 3333333333333333333333333333333333333333333333333333333 +3828 3333333333333333333333333333333333333333333333333333333 +3829 3333333333333333333333333333333333333333333333333333333 +3830 3333333333333333333333333333333333333333333333333333333 +3831 3333333333333333333333333333333333333333333333333333333 +3832 3333333333333333333333333333333333333333333333333333333 +3833 3333333333333333333333333333333333333333333333333333333 +3834 3333333333333333333333333333333333333333333333333333333 +3835 3333333333333333333333333333333333333333333333333333333 +3836 3333333333333333333333333333333333333333333333333333333 +3837 3333333333333333333333333333333333333333333333333333333 +3838 3333333333333333333333333333333333333333333333333333333 +3839 3333333333333333333333333333333333333333333333333333333 +3840 3333333333333333333333333333333333333333333333333333333 +3841 3333333333333333333333333333333333333333333333333333333 +3842 3333333333333333333333333333333333333333333333333333333 +3843 3333333333333333333333333333333333333333333333333333333 +3844 3333333333333333333333333333333333333333333333333333333 +3845 3333333333333333333333333333333333333333333333333333333 +3846 3333333333333333333333333333333333333333333333333333333 +3847 3333333333333333333333333333333333333333333333333333333 +3848 3333333333333333333333333333333333333333333333333333333 +3849 3333333333333333333333333333333333333333333333333333333 +3850 3333333333333333333333333333333333333333333333333333333 +3851 3333333333333333333333333333333333333333333333333333333 +3852 3333333333333333333333333333333333333333333333333333333 +3853 3333333333333333333333333333333333333333333333333333333 +3854 3333333333333333333333333333333333333333333333333333333 +3855 3333333333333333333333333333333333333333333333333333333 +3856 3333333333333333333333333333333333333333333333333333333 +3857 3333333333333333333333333333333333333333333333333333333 +3858 3333333333333333333333333333333333333333333333333333333 +3859 3333333333333333333333333333333333333333333333333333333 +3860 3333333333333333333333333333333333333333333333333333333 +3861 3333333333333333333333333333333333333333333333333333333 +3862 3333333333333333333333333333333333333333333333333333333 +3863 3333333333333333333333333333333333333333333333333333333 +3864 3333333333333333333333333333333333333333333333333333333 +3865 3333333333333333333333333333333333333333333333333333333 +3866 3333333333333333333333333333333333333333333333333333333 +3867 3333333333333333333333333333333333333333333333333333333 +3868 3333333333333333333333333333333333333333333333333333333 +3869 3333333333333333333333333333333333333333333333333333333 +3870 3333333333333333333333333333333333333333333333333333333 +3871 3333333333333333333333333333333333333333333333333333333 +3872 3333333333333333333333333333333333333333333333333333333 +3873 3333333333333333333333333333333333333333333333333333333 +3874 3333333333333333333333333333333333333333333333333333333 +3875 3333333333333333333333333333333333333333333333333333333 +3876 3333333333333333333333333333333333333333333333333333333 +3877 3333333333333333333333333333333333333333333333333333333 +3878 3333333333333333333333333333333333333333333333333333333 +3879 3333333333333333333333333333333333333333333333333333333 +3880 3333333333333333333333333333333333333333333333333333333 +3881 3333333333333333333333333333333333333333333333333333333 +3882 3333333333333333333333333333333333333333333333333333333 +3883 3333333333333333333333333333333333333333333333333333333 +3884 3333333333333333333333333333333333333333333333333333333 +3885 3333333333333333333333333333333333333333333333333333333 +3886 3333333333333333333333333333333333333333333333333333333 +3887 3333333333333333333333333333333333333333333333333333333 +3888 3333333333333333333333333333333333333333333333333333333 +3889 3333333333333333333333333333333333333333333333333333333 +3890 3333333333333333333333333333333333333333333333333333333 +3891 3333333333333333333333333333333333333333333333333333333 +3892 3333333333333333333333333333333333333333333333333333333 +3893 3333333333333333333333333333333333333333333333333333333 +3894 3333333333333333333333333333333333333333333333333333333 +3895 3333333333333333333333333333333333333333333333333333333 +3896 3333333333333333333333333333333333333333333333333333333 +3897 3333333333333333333333333333333333333333333333333333333 +3898 3333333333333333333333333333333333333333333333333333333 +3899 3333333333333333333333333333333333333333333333333333333 +3900 3333333333333333333333333333333333333333333333333333333 +3901 3333333333333333333333333333333333333333333333333333333 +3902 3333333333333333333333333333333333333333333333333333333 +3903 3333333333333333333333333333333333333333333333333333333 +3904 3333333333333333333333333333333333333333333333333333333 +3905 3333333333333333333333333333333333333333333333333333333 +3906 3333333333333333333333333333333333333333333333333333333 +3907 3333333333333333333333333333333333333333333333333333333 +3908 3333333333333333333333333333333333333333333333333333333 +3909 3333333333333333333333333333333333333333333333333333333 +3910 3333333333333333333333333333333333333333333333333333333 +3911 3333333333333333333333333333333333333333333333333333333 +3912 3333333333333333333333333333333333333333333333333333333 +3913 3333333333333333333333333333333333333333333333333333333 +3914 3333333333333333333333333333333333333333333333333333333 +3915 3333333333333333333333333333333333333333333333333333333 +3916 3333333333333333333333333333333333333333333333333333333 +3917 3333333333333333333333333333333333333333333333333333333 +3918 3333333333333333333333333333333333333333333333333333333 +3919 3333333333333333333333333333333333333333333333333333333 +3920 3333333333333333333333333333333333333333333333333333333 +3921 3333333333333333333333333333333333333333333333333333333 +3922 3333333333333333333333333333333333333333333333333333333 +3923 3333333333333333333333333333333333333333333333333333333 +3924 3333333333333333333333333333333333333333333333333333333 +3925 3333333333333333333333333333333333333333333333333333333 +3926 3333333333333333333333333333333333333333333333333333333 +3927 3333333333333333333333333333333333333333333333333333333 +3928 3333333333333333333333333333333333333333333333333333333 +3929 3333333333333333333333333333333333333333333333333333333 +3930 3333333333333333333333333333333333333333333333333333333 +3931 3333333333333333333333333333333333333333333333333333333 +3932 3333333333333333333333333333333333333333333333333333333 +3933 3333333333333333333333333333333333333333333333333333333 +3934 3333333333333333333333333333333333333333333333333333333 +3935 3333333333333333333333333333333333333333333333333333333 +3936 3333333333333333333333333333333333333333333333333333333 +3937 3333333333333333333333333333333333333333333333333333333 +3938 3333333333333333333333333333333333333333333333333333333 +3939 3333333333333333333333333333333333333333333333333333333 +3940 3333333333333333333333333333333333333333333333333333333 +3941 3333333333333333333333333333333333333333333333333333333 +3942 3333333333333333333333333333333333333333333333333333333 +3943 3333333333333333333333333333333333333333333333333333333 +3944 3333333333333333333333333333333333333333333333333333333 +3945 3333333333333333333333333333333333333333333333333333333 +3946 3333333333333333333333333333333333333333333333333333333 +3947 3333333333333333333333333333333333333333333333333333333 +3948 3333333333333333333333333333333333333333333333333333333 +3949 3333333333333333333333333333333333333333333333333333333 +3950 3333333333333333333333333333333333333333333333333333333 +3951 3333333333333333333333333333333333333333333333333333333 +3952 3333333333333333333333333333333333333333333333333333333 +3953 3333333333333333333333333333333333333333333333333333333 +3954 3333333333333333333333333333333333333333333333333333333 +3955 3333333333333333333333333333333333333333333333333333333 +3956 3333333333333333333333333333333333333333333333333333333 +3957 3333333333333333333333333333333333333333333333333333333 +3958 3333333333333333333333333333333333333333333333333333333 +3959 3333333333333333333333333333333333333333333333333333333 +3960 3333333333333333333333333333333333333333333333333333333 +3961 3333333333333333333333333333333333333333333333333333333 +3962 3333333333333333333333333333333333333333333333333333333 +3963 3333333333333333333333333333333333333333333333333333333 +3964 3333333333333333333333333333333333333333333333333333333 +3965 3333333333333333333333333333333333333333333333333333333 +3966 3333333333333333333333333333333333333333333333333333333 +3967 3333333333333333333333333333333333333333333333333333333 +3968 3333333333333333333333333333333333333333333333333333333 +3969 3333333333333333333333333333333333333333333333333333333 +3970 3333333333333333333333333333333333333333333333333333333 +3971 3333333333333333333333333333333333333333333333333333333 +3972 3333333333333333333333333333333333333333333333333333333 +3973 3333333333333333333333333333333333333333333333333333333 +3974 3333333333333333333333333333333333333333333333333333333 +3975 3333333333333333333333333333333333333333333333333333333 +3976 3333333333333333333333333333333333333333333333333333333 +3977 3333333333333333333333333333333333333333333333333333333 +3978 3333333333333333333333333333333333333333333333333333333 +3979 3333333333333333333333333333333333333333333333333333333 +3980 3333333333333333333333333333333333333333333333333333333 +3981 3333333333333333333333333333333333333333333333333333333 +3982 3333333333333333333333333333333333333333333333333333333 +3983 3333333333333333333333333333333333333333333333333333333 +3984 3333333333333333333333333333333333333333333333333333333 +3985 3333333333333333333333333333333333333333333333333333333 +3986 3333333333333333333333333333333333333333333333333333333 +3987 3333333333333333333333333333333333333333333333333333333 +3988 3333333333333333333333333333333333333333333333333333333 +3989 3333333333333333333333333333333333333333333333333333333 +3990 3333333333333333333333333333333333333333333333333333333 +3991 3333333333333333333333333333333333333333333333333333333 +3992 3333333333333333333333333333333333333333333333333333333 +3993 3333333333333333333333333333333333333333333333333333333 +3994 3333333333333333333333333333333333333333333333333333333 +3995 3333333333333333333333333333333333333333333333333333333 +3996 3333333333333333333333333333333333333333333333333333333 +3997 3333333333333333333333333333333333333333333333333333333 +3998 3333333333333333333333333333333333333333333333333333333 +3999 3333333333333333333333333333333333333333333333333333333 +4000 3333333333333333333333333333333333333333333333333333333 +4001 3333333333333333333333333333333333333333333333333333333 +4002 3333333333333333333333333333333333333333333333333333333 +4003 3333333333333333333333333333333333333333333333333333333 +4004 3333333333333333333333333333333333333333333333333333333 +4005 3333333333333333333333333333333333333333333333333333333 +4006 3333333333333333333333333333333333333333333333333333333 +4007 3333333333333333333333333333333333333333333333333333333 +4008 3333333333333333333333333333333333333333333333333333333 +4009 3333333333333333333333333333333333333333333333333333333 +4010 3333333333333333333333333333333333333333333333333333333 +4011 3333333333333333333333333333333333333333333333333333333 +4012 3333333333333333333333333333333333333333333333333333333 +4013 3333333333333333333333333333333333333333333333333333333 +4014 3333333333333333333333333333333333333333333333333333333 +4015 3333333333333333333333333333333333333333333333333333333 +4016 3333333333333333333333333333333333333333333333333333333 +4017 3333333333333333333333333333333333333333333333333333333 +4018 3333333333333333333333333333333333333333333333333333333 +4019 3333333333333333333333333333333333333333333333333333333 +4020 3333333333333333333333333333333333333333333333333333333 +4021 3333333333333333333333333333333333333333333333333333333 +4022 3333333333333333333333333333333333333333333333333333333 +4023 3333333333333333333333333333333333333333333333333333333 +4024 3333333333333333333333333333333333333333333333333333333 +4025 3333333333333333333333333333333333333333333333333333333 +4026 3333333333333333333333333333333333333333333333333333333 +4027 3333333333333333333333333333333333333333333333333333333 +4028 3333333333333333333333333333333333333333333333333333333 +4029 3333333333333333333333333333333333333333333333333333333 +4030 3333333333333333333333333333333333333333333333333333333 +4031 3333333333333333333333333333333333333333333333333333333 +4032 3333333333333333333333333333333333333333333333333333333 +4033 3333333333333333333333333333333333333333333333333333333 +4034 3333333333333333333333333333333333333333333333333333333 +4035 3333333333333333333333333333333333333333333333333333333 +4036 3333333333333333333333333333333333333333333333333333333 +4037 3333333333333333333333333333333333333333333333333333333 +4038 3333333333333333333333333333333333333333333333333333333 +4039 3333333333333333333333333333333333333333333333333333333 +4040 3333333333333333333333333333333333333333333333333333333 +4041 3333333333333333333333333333333333333333333333333333333 +4042 3333333333333333333333333333333333333333333333333333333 +4043 3333333333333333333333333333333333333333333333333333333 +4044 3333333333333333333333333333333333333333333333333333333 +4045 3333333333333333333333333333333333333333333333333333333 +4046 3333333333333333333333333333333333333333333333333333333 +4047 3333333333333333333333333333333333333333333333333333333 +4048 3333333333333333333333333333333333333333333333333333333 +4049 3333333333333333333333333333333333333333333333333333333 +4050 3333333333333333333333333333333333333333333333333333333 +4051 3333333333333333333333333333333333333333333333333333333 +4052 3333333333333333333333333333333333333333333333333333333 +4053 3333333333333333333333333333333333333333333333333333333 +4054 3333333333333333333333333333333333333333333333333333333 +4055 3333333333333333333333333333333333333333333333333333333 +4056 3333333333333333333333333333333333333333333333333333333 +4057 3333333333333333333333333333333333333333333333333333333 +4058 3333333333333333333333333333333333333333333333333333333 +4059 3333333333333333333333333333333333333333333333333333333 +4060 3333333333333333333333333333333333333333333333333333333 +4061 3333333333333333333333333333333333333333333333333333333 +4062 3333333333333333333333333333333333333333333333333333333 +4063 3333333333333333333333333333333333333333333333333333333 +4064 3333333333333333333333333333333333333333333333333333333 +4065 3333333333333333333333333333333333333333333333333333333 +4066 3333333333333333333333333333333333333333333333333333333 +4067 3333333333333333333333333333333333333333333333333333333 +4068 3333333333333333333333333333333333333333333333333333333 +4069 3333333333333333333333333333333333333333333333333333333 +4070 3333333333333333333333333333333333333333333333333333333 +4071 3333333333333333333333333333333333333333333333333333333 +4072 3333333333333333333333333333333333333333333333333333333 +4073 3333333333333333333333333333333333333333333333333333333 +4074 3333333333333333333333333333333333333333333333333333333 +4075 3333333333333333333333333333333333333333333333333333333 +4076 3333333333333333333333333333333333333333333333333333333 +4077 3333333333333333333333333333333333333333333333333333333 +4078 3333333333333333333333333333333333333333333333333333333 +4079 3333333333333333333333333333333333333333333333333333333 +4080 3333333333333333333333333333333333333333333333333333333 +4081 3333333333333333333333333333333333333333333333333333333 +4082 3333333333333333333333333333333333333333333333333333333 +4083 3333333333333333333333333333333333333333333333333333333 +4084 3333333333333333333333333333333333333333333333333333333 +4085 3333333333333333333333333333333333333333333333333333333 +4086 3333333333333333333333333333333333333333333333333333333 +4087 3333333333333333333333333333333333333333333333333333333 +4088 3333333333333333333333333333333333333333333333333333333 +4089 3333333333333333333333333333333333333333333333333333333 +4090 3333333333333333333333333333333333333333333333333333333 +4091 3333333333333333333333333333333333333333333333333333333 +4092 3333333333333333333333333333333333333333333333333333333 +4093 3333333333333333333333333333333333333333333333333333333 +4094 3333333333333333333333333333333333333333333333333333333 +4095 3333333333333333333333333333333333333333333333333333333 +4096 3333333333333333333333333333333333333333333333333333333 +4097 3333333333333333333333333333333333333333333333333333333 +4098 3333333333333333333333333333333333333333333333333333333 +4099 3333333333333333333333333333333333333333333333333333333 +4100 3333333333333333333333333333333333333333333333333333333 +4101 3333333333333333333333333333333333333333333333333333333 +4102 3333333333333333333333333333333333333333333333333333333 +4103 3333333333333333333333333333333333333333333333333333333 +4104 3333333333333333333333333333333333333333333333333333333 +4105 3333333333333333333333333333333333333333333333333333333 +4106 3333333333333333333333333333333333333333333333333333333 +4107 3333333333333333333333333333333333333333333333333333333 +4108 3333333333333333333333333333333333333333333333333333333 +4109 3333333333333333333333333333333333333333333333333333333 +4110 3333333333333333333333333333333333333333333333333333333 +4111 3333333333333333333333333333333333333333333333333333333 +4112 3333333333333333333333333333333333333333333333333333333 +4113 3333333333333333333333333333333333333333333333333333333 +4114 3333333333333333333333333333333333333333333333333333333 +4115 3333333333333333333333333333333333333333333333333333333 +4116 3333333333333333333333333333333333333333333333333333333 +4117 3333333333333333333333333333333333333333333333333333333 +4118 3333333333333333333333333333333333333333333333333333333 +4119 3333333333333333333333333333333333333333333333333333333 +4120 3333333333333333333333333333333333333333333333333333333 +4121 3333333333333333333333333333333333333333333333333333333 +4122 3333333333333333333333333333333333333333333333333333333 +4123 3333333333333333333333333333333333333333333333333333333 +4124 3333333333333333333333333333333333333333333333333333333 +4125 3333333333333333333333333333333333333333333333333333333 +4126 3333333333333333333333333333333333333333333333333333333 +4127 3333333333333333333333333333333333333333333333333333333 +4128 3333333333333333333333333333333333333333333333333333333 +4129 3333333333333333333333333333333333333333333333333333333 +4130 3333333333333333333333333333333333333333333333333333333 +4131 3333333333333333333333333333333333333333333333333333333 +4132 3333333333333333333333333333333333333333333333333333333 +4133 3333333333333333333333333333333333333333333333333333333 +4134 3333333333333333333333333333333333333333333333333333333 +4135 3333333333333333333333333333333333333333333333333333333 +4136 3333333333333333333333333333333333333333333333333333333 +4137 3333333333333333333333333333333333333333333333333333333 +4138 3333333333333333333333333333333333333333333333333333333 +4139 3333333333333333333333333333333333333333333333333333333 +4140 3333333333333333333333333333333333333333333333333333333 +4141 3333333333333333333333333333333333333333333333333333333 +4142 3333333333333333333333333333333333333333333333333333333 +4143 3333333333333333333333333333333333333333333333333333333 +4144 3333333333333333333333333333333333333333333333333333333 +4145 3333333333333333333333333333333333333333333333333333333 +4146 3333333333333333333333333333333333333333333333333333333 +4147 3333333333333333333333333333333333333333333333333333333 +4148 3333333333333333333333333333333333333333333333333333333 +4149 3333333333333333333333333333333333333333333333333333333 +4150 3333333333333333333333333333333333333333333333333333333 +4151 3333333333333333333333333333333333333333333333333333333 +4152 3333333333333333333333333333333333333333333333333333333 +4153 3333333333333333333333333333333333333333333333333333333 +4154 3333333333333333333333333333333333333333333333333333333 +4155 3333333333333333333333333333333333333333333333333333333 +4156 3333333333333333333333333333333333333333333333333333333 +4157 3333333333333333333333333333333333333333333333333333333 +4158 3333333333333333333333333333333333333333333333333333333 +4159 3333333333333333333333333333333333333333333333333333333 +4160 3333333333333333333333333333333333333333333333333333333 +4161 3333333333333333333333333333333333333333333333333333333 +4162 3333333333333333333333333333333333333333333333333333333 +4163 3333333333333333333333333333333333333333333333333333333 +4164 3333333333333333333333333333333333333333333333333333333 +4165 3333333333333333333333333333333333333333333333333333333 +4166 3333333333333333333333333333333333333333333333333333333 +4167 3333333333333333333333333333333333333333333333333333333 +4168 3333333333333333333333333333333333333333333333333333333 +4169 3333333333333333333333333333333333333333333333333333333 +4170 3333333333333333333333333333333333333333333333333333333 +4171 3333333333333333333333333333333333333333333333333333333 +4172 3333333333333333333333333333333333333333333333333333333 +4173 3333333333333333333333333333333333333333333333333333333 +4174 3333333333333333333333333333333333333333333333333333333 +4175 3333333333333333333333333333333333333333333333333333333 +4176 3333333333333333333333333333333333333333333333333333333 +4177 3333333333333333333333333333333333333333333333333333333 +4178 3333333333333333333333333333333333333333333333333333333 +4179 3333333333333333333333333333333333333333333333333333333 +4180 3333333333333333333333333333333333333333333333333333333 +4181 3333333333333333333333333333333333333333333333333333333 +4182 3333333333333333333333333333333333333333333333333333333 +4183 3333333333333333333333333333333333333333333333333333333 +4184 3333333333333333333333333333333333333333333333333333333 +4185 3333333333333333333333333333333333333333333333333333333 +4186 3333333333333333333333333333333333333333333333333333333 +4187 3333333333333333333333333333333333333333333333333333333 +4188 3333333333333333333333333333333333333333333333333333333 +4189 3333333333333333333333333333333333333333333333333333333 +4190 3333333333333333333333333333333333333333333333333333333 +4191 3333333333333333333333333333333333333333333333333333333 +4192 3333333333333333333333333333333333333333333333333333333 +4193 3333333333333333333333333333333333333333333333333333333 +4194 3333333333333333333333333333333333333333333333333333333 +4195 3333333333333333333333333333333333333333333333333333333 +4196 3333333333333333333333333333333333333333333333333333333 +4197 3333333333333333333333333333333333333333333333333333333 +4198 3333333333333333333333333333333333333333333333333333333 +4199 3333333333333333333333333333333333333333333333333333333 +4200 3333333333333333333333333333333333333333333333333333333 +4201 3333333333333333333333333333333333333333333333333333333 +4202 3333333333333333333333333333333333333333333333333333333 +4203 3333333333333333333333333333333333333333333333333333333 +4204 3333333333333333333333333333333333333333333333333333333 +4205 3333333333333333333333333333333333333333333333333333333 +4206 3333333333333333333333333333333333333333333333333333333 +4207 3333333333333333333333333333333333333333333333333333333 +4208 3333333333333333333333333333333333333333333333333333333 +4209 3333333333333333333333333333333333333333333333333333333 +4210 3333333333333333333333333333333333333333333333333333333 +4211 3333333333333333333333333333333333333333333333333333333 +4212 3333333333333333333333333333333333333333333333333333333 +4213 3333333333333333333333333333333333333333333333333333333 +4214 3333333333333333333333333333333333333333333333333333333 +4215 3333333333333333333333333333333333333333333333333333333 +4216 3333333333333333333333333333333333333333333333333333333 +4217 3333333333333333333333333333333333333333333333333333333 +4218 3333333333333333333333333333333333333333333333333333333 +4219 3333333333333333333333333333333333333333333333333333333 +4220 3333333333333333333333333333333333333333333333333333333 +4221 3333333333333333333333333333333333333333333333333333333 +4222 3333333333333333333333333333333333333333333333333333333 +4223 3333333333333333333333333333333333333333333333333333333 +4224 3333333333333333333333333333333333333333333333333333333 +4225 3333333333333333333333333333333333333333333333333333333 +4226 3333333333333333333333333333333333333333333333333333333 +4227 3333333333333333333333333333333333333333333333333333333 +4228 3333333333333333333333333333333333333333333333333333333 +4229 3333333333333333333333333333333333333333333333333333333 +4230 3333333333333333333333333333333333333333333333333333333 +4231 3333333333333333333333333333333333333333333333333333333 +4232 3333333333333333333333333333333333333333333333333333333 +4233 3333333333333333333333333333333333333333333333333333333 +4234 3333333333333333333333333333333333333333333333333333333 +4235 3333333333333333333333333333333333333333333333333333333 +4236 3333333333333333333333333333333333333333333333333333333 +4237 3333333333333333333333333333333333333333333333333333333 +4238 3333333333333333333333333333333333333333333333333333333 +4239 3333333333333333333333333333333333333333333333333333333 +4240 3333333333333333333333333333333333333333333333333333333 +4241 3333333333333333333333333333333333333333333333333333333 +4242 3333333333333333333333333333333333333333333333333333333 +4243 3333333333333333333333333333333333333333333333333333333 +4244 3333333333333333333333333333333333333333333333333333333 +4245 3333333333333333333333333333333333333333333333333333333 +4246 3333333333333333333333333333333333333333333333333333333 +4247 3333333333333333333333333333333333333333333333333333333 +4248 3333333333333333333333333333333333333333333333333333333 +4249 3333333333333333333333333333333333333333333333333333333 +4250 3333333333333333333333333333333333333333333333333333333 +4251 3333333333333333333333333333333333333333333333333333333 +4252 3333333333333333333333333333333333333333333333333333333 +4253 3333333333333333333333333333333333333333333333333333333 +4254 3333333333333333333333333333333333333333333333333333333 +4255 3333333333333333333333333333333333333333333333333333333 +4256 3333333333333333333333333333333333333333333333333333333 +4257 3333333333333333333333333333333333333333333333333333333 +4258 3333333333333333333333333333333333333333333333333333333 +4259 3333333333333333333333333333333333333333333333333333333 +4260 3333333333333333333333333333333333333333333333333333333 +4261 3333333333333333333333333333333333333333333333333333333 +4262 3333333333333333333333333333333333333333333333333333333 +4263 3333333333333333333333333333333333333333333333333333333 +4264 3333333333333333333333333333333333333333333333333333333 +4265 3333333333333333333333333333333333333333333333333333333 +4266 3333333333333333333333333333333333333333333333333333333 +4267 3333333333333333333333333333333333333333333333333333333 +4268 3333333333333333333333333333333333333333333333333333333 +4269 3333333333333333333333333333333333333333333333333333333 +4270 3333333333333333333333333333333333333333333333333333333 +4271 3333333333333333333333333333333333333333333333333333333 +4272 3333333333333333333333333333333333333333333333333333333 +4273 3333333333333333333333333333333333333333333333333333333 +4274 3333333333333333333333333333333333333333333333333333333 +4275 3333333333333333333333333333333333333333333333333333333 +4276 3333333333333333333333333333333333333333333333333333333 +4277 3333333333333333333333333333333333333333333333333333333 +4278 3333333333333333333333333333333333333333333333333333333 +4279 3333333333333333333333333333333333333333333333333333333 +4280 3333333333333333333333333333333333333333333333333333333 +4281 3333333333333333333333333333333333333333333333333333333 +4282 3333333333333333333333333333333333333333333333333333333 +4283 3333333333333333333333333333333333333333333333333333333 +4284 3333333333333333333333333333333333333333333333333333333 +4285 3333333333333333333333333333333333333333333333333333333 +4286 3333333333333333333333333333333333333333333333333333333 +4287 3333333333333333333333333333333333333333333333333333333 +4288 3333333333333333333333333333333333333333333333333333333 +4289 3333333333333333333333333333333333333333333333333333333 +4290 3333333333333333333333333333333333333333333333333333333 +4291 3333333333333333333333333333333333333333333333333333333 +4292 3333333333333333333333333333333333333333333333333333333 +4293 3333333333333333333333333333333333333333333333333333333 +4294 3333333333333333333333333333333333333333333333333333333 +4295 3333333333333333333333333333333333333333333333333333333 +4296 3333333333333333333333333333333333333333333333333333333 +4297 3333333333333333333333333333333333333333333333333333333 +4298 3333333333333333333333333333333333333333333333333333333 +4299 3333333333333333333333333333333333333333333333333333333 +4300 3333333333333333333333333333333333333333333333333333333 +4301 3333333333333333333333333333333333333333333333333333333 +4302 3333333333333333333333333333333333333333333333333333333 +4303 3333333333333333333333333333333333333333333333333333333 +4304 3333333333333333333333333333333333333333333333333333333 +4305 3333333333333333333333333333333333333333333333333333333 +4306 3333333333333333333333333333333333333333333333333333333 +4307 3333333333333333333333333333333333333333333333333333333 +4308 3333333333333333333333333333333333333333333333333333333 +4309 3333333333333333333333333333333333333333333333333333333 +4310 3333333333333333333333333333333333333333333333333333333 +4311 3333333333333333333333333333333333333333333333333333333 +4312 3333333333333333333333333333333333333333333333333333333 +4313 3333333333333333333333333333333333333333333333333333333 +4314 3333333333333333333333333333333333333333333333333333333 +4315 3333333333333333333333333333333333333333333333333333333 +4316 3333333333333333333333333333333333333333333333333333333 +4317 3333333333333333333333333333333333333333333333333333333 +4318 3333333333333333333333333333333333333333333333333333333 +4319 3333333333333333333333333333333333333333333333333333333 +4320 3333333333333333333333333333333333333333333333333333333 +4321 3333333333333333333333333333333333333333333333333333333 +4322 3333333333333333333333333333333333333333333333333333333 +4323 3333333333333333333333333333333333333333333333333333333 +4324 3333333333333333333333333333333333333333333333333333333 +4325 3333333333333333333333333333333333333333333333333333333 +4326 3333333333333333333333333333333333333333333333333333333 +4327 3333333333333333333333333333333333333333333333333333333 +4328 3333333333333333333333333333333333333333333333333333333 +4329 3333333333333333333333333333333333333333333333333333333 +4330 3333333333333333333333333333333333333333333333333333333 +4331 3333333333333333333333333333333333333333333333333333333 +4332 3333333333333333333333333333333333333333333333333333333 +4333 3333333333333333333333333333333333333333333333333333333 +4334 3333333333333333333333333333333333333333333333333333333 +4335 3333333333333333333333333333333333333333333333333333333 +4336 3333333333333333333333333333333333333333333333333333333 +4337 3333333333333333333333333333333333333333333333333333333 +4338 3333333333333333333333333333333333333333333333333333333 +4339 3333333333333333333333333333333333333333333333333333333 +4340 3333333333333333333333333333333333333333333333333333333 +4341 3333333333333333333333333333333333333333333333333333333 +4342 3333333333333333333333333333333333333333333333333333333 +4343 3333333333333333333333333333333333333333333333333333333 +4344 3333333333333333333333333333333333333333333333333333333 +4345 3333333333333333333333333333333333333333333333333333333 +4346 3333333333333333333333333333333333333333333333333333333 +4347 3333333333333333333333333333333333333333333333333333333 +4348 3333333333333333333333333333333333333333333333333333333 +4349 3333333333333333333333333333333333333333333333333333333 +4350 3333333333333333333333333333333333333333333333333333333 +4351 3333333333333333333333333333333333333333333333333333333 +4352 3333333333333333333333333333333333333333333333333333333 +4353 3333333333333333333333333333333333333333333333333333333 +4354 3333333333333333333333333333333333333333333333333333333 +4355 3333333333333333333333333333333333333333333333333333333 +4356 3333333333333333333333333333333333333333333333333333333 +4357 3333333333333333333333333333333333333333333333333333333 +4358 3333333333333333333333333333333333333333333333333333333 +4359 3333333333333333333333333333333333333333333333333333333 +4360 3333333333333333333333333333333333333333333333333333333 +4361 3333333333333333333333333333333333333333333333333333333 +4362 3333333333333333333333333333333333333333333333333333333 +4363 3333333333333333333333333333333333333333333333333333333 +4364 3333333333333333333333333333333333333333333333333333333 +4365 3333333333333333333333333333333333333333333333333333333 +4366 3333333333333333333333333333333333333333333333333333333 +4367 3333333333333333333333333333333333333333333333333333333 +4368 3333333333333333333333333333333333333333333333333333333 +4369 3333333333333333333333333333333333333333333333333333333 +4370 3333333333333333333333333333333333333333333333333333333 +4371 3333333333333333333333333333333333333333333333333333333 +4372 3333333333333333333333333333333333333333333333333333333 +4373 3333333333333333333333333333333333333333333333333333333 +4374 3333333333333333333333333333333333333333333333333333333 +4375 3333333333333333333333333333333333333333333333333333333 +4376 3333333333333333333333333333333333333333333333333333333 +4377 3333333333333333333333333333333333333333333333333333333 +4378 3333333333333333333333333333333333333333333333333333333 +4379 3333333333333333333333333333333333333333333333333333333 +4380 3333333333333333333333333333333333333333333333333333333 +4381 3333333333333333333333333333333333333333333333333333333 +4382 3333333333333333333333333333333333333333333333333333333 +4383 3333333333333333333333333333333333333333333333333333333 +4384 3333333333333333333333333333333333333333333333333333333 +4385 3333333333333333333333333333333333333333333333333333333 +4386 3333333333333333333333333333333333333333333333333333333 +4387 3333333333333333333333333333333333333333333333333333333 +4388 3333333333333333333333333333333333333333333333333333333 +4389 3333333333333333333333333333333333333333333333333333333 +4390 3333333333333333333333333333333333333333333333333333333 +4391 3333333333333333333333333333333333333333333333333333333 +4392 3333333333333333333333333333333333333333333333333333333 +4393 3333333333333333333333333333333333333333333333333333333 +4394 3333333333333333333333333333333333333333333333333333333 +4395 3333333333333333333333333333333333333333333333333333333 +4396 3333333333333333333333333333333333333333333333333333333 +4397 3333333333333333333333333333333333333333333333333333333 +4398 3333333333333333333333333333333333333333333333333333333 +4399 3333333333333333333333333333333333333333333333333333333 +4400 3333333333333333333333333333333333333333333333333333333 +4401 3333333333333333333333333333333333333333333333333333333 +4402 3333333333333333333333333333333333333333333333333333333 +4403 3333333333333333333333333333333333333333333333333333333 +4404 3333333333333333333333333333333333333333333333333333333 +4405 3333333333333333333333333333333333333333333333333333333 +4406 3333333333333333333333333333333333333333333333333333333 +4407 3333333333333333333333333333333333333333333333333333333 +4408 3333333333333333333333333333333333333333333333333333333 +4409 3333333333333333333333333333333333333333333333333333333 +4410 3333333333333333333333333333333333333333333333333333333 +4411 3333333333333333333333333333333333333333333333333333333 +4412 3333333333333333333333333333333333333333333333333333333 +4413 3333333333333333333333333333333333333333333333333333333 +4414 3333333333333333333333333333333333333333333333333333333 +4415 3333333333333333333333333333333333333333333333333333333 +4416 3333333333333333333333333333333333333333333333333333333 +4417 3333333333333333333333333333333333333333333333333333333 +4418 3333333333333333333333333333333333333333333333333333333 +4419 3333333333333333333333333333333333333333333333333333333 +4420 3333333333333333333333333333333333333333333333333333333 +4421 3333333333333333333333333333333333333333333333333333333 +4422 3333333333333333333333333333333333333333333333333333333 +4423 3333333333333333333333333333333333333333333333333333333 +4424 3333333333333333333333333333333333333333333333333333333 +4425 3333333333333333333333333333333333333333333333333333333 +4426 3333333333333333333333333333333333333333333333333333333 +4427 3333333333333333333333333333333333333333333333333333333 +4428 3333333333333333333333333333333333333333333333333333333 +4429 3333333333333333333333333333333333333333333333333333333 +4430 3333333333333333333333333333333333333333333333333333333 +4431 3333333333333333333333333333333333333333333333333333333 +4432 3333333333333333333333333333333333333333333333333333333 +4433 3333333333333333333333333333333333333333333333333333333 +4434 3333333333333333333333333333333333333333333333333333333 +4435 3333333333333333333333333333333333333333333333333333333 +4436 3333333333333333333333333333333333333333333333333333333 +4437 3333333333333333333333333333333333333333333333333333333 +4438 3333333333333333333333333333333333333333333333333333333 +4439 3333333333333333333333333333333333333333333333333333333 +4440 3333333333333333333333333333333333333333333333333333333 +4441 3333333333333333333333333333333333333333333333333333333 +4442 3333333333333333333333333333333333333333333333333333333 +4443 3333333333333333333333333333333333333333333333333333333 +4444 3333333333333333333333333333333333333333333333333333333 +4445 3333333333333333333333333333333333333333333333333333333 +4446 3333333333333333333333333333333333333333333333333333333 +4447 3333333333333333333333333333333333333333333333333333333 +4448 3333333333333333333333333333333333333333333333333333333 +4449 3333333333333333333333333333333333333333333333333333333 +4450 3333333333333333333333333333333333333333333333333333333 +4451 3333333333333333333333333333333333333333333333333333333 +4452 3333333333333333333333333333333333333333333333333333333 +4453 3333333333333333333333333333333333333333333333333333333 +4454 3333333333333333333333333333333333333333333333333333333 +4455 3333333333333333333333333333333333333333333333333333333 +4456 3333333333333333333333333333333333333333333333333333333 +4457 3333333333333333333333333333333333333333333333333333333 +4458 3333333333333333333333333333333333333333333333333333333 +4459 3333333333333333333333333333333333333333333333333333333 +4460 3333333333333333333333333333333333333333333333333333333 +4461 3333333333333333333333333333333333333333333333333333333 +4462 3333333333333333333333333333333333333333333333333333333 +4463 3333333333333333333333333333333333333333333333333333333 +4464 3333333333333333333333333333333333333333333333333333333 +4465 3333333333333333333333333333333333333333333333333333333 +4466 3333333333333333333333333333333333333333333333333333333 +4467 3333333333333333333333333333333333333333333333333333333 +4468 3333333333333333333333333333333333333333333333333333333 +4469 3333333333333333333333333333333333333333333333333333333 +4470 3333333333333333333333333333333333333333333333333333333 +4471 3333333333333333333333333333333333333333333333333333333 +4472 3333333333333333333333333333333333333333333333333333333 +4473 3333333333333333333333333333333333333333333333333333333 +4474 3333333333333333333333333333333333333333333333333333333 +4475 3333333333333333333333333333333333333333333333333333333 +4476 3333333333333333333333333333333333333333333333333333333 +4477 3333333333333333333333333333333333333333333333333333333 +4478 3333333333333333333333333333333333333333333333333333333 +4479 3333333333333333333333333333333333333333333333333333333 +4480 3333333333333333333333333333333333333333333333333333333 +4481 3333333333333333333333333333333333333333333333333333333 +4482 3333333333333333333333333333333333333333333333333333333 +4483 3333333333333333333333333333333333333333333333333333333 +4484 3333333333333333333333333333333333333333333333333333333 +4485 3333333333333333333333333333333333333333333333333333333 +4486 3333333333333333333333333333333333333333333333333333333 +4487 3333333333333333333333333333333333333333333333333333333 +4488 3333333333333333333333333333333333333333333333333333333 +4489 3333333333333333333333333333333333333333333333333333333 +4490 3333333333333333333333333333333333333333333333333333333 +4491 3333333333333333333333333333333333333333333333333333333 +4492 3333333333333333333333333333333333333333333333333333333 +4493 3333333333333333333333333333333333333333333333333333333 +4494 3333333333333333333333333333333333333333333333333333333 +4495 3333333333333333333333333333333333333333333333333333333 +4496 3333333333333333333333333333333333333333333333333333333 +4497 3333333333333333333333333333333333333333333333333333333 +4498 3333333333333333333333333333333333333333333333333333333 +4499 3333333333333333333333333333333333333333333333333333333 +4500 3333333333333333333333333333333333333333333333333333333 +4501 3333333333333333333333333333333333333333333333333333333 +4502 3333333333333333333333333333333333333333333333333333333 +4503 3333333333333333333333333333333333333333333333333333333 +4504 3333333333333333333333333333333333333333333333333333333 +4505 3333333333333333333333333333333333333333333333333333333 +4506 3333333333333333333333333333333333333333333333333333333 +4507 3333333333333333333333333333333333333333333333333333333 +4508 3333333333333333333333333333333333333333333333333333333 +4509 3333333333333333333333333333333333333333333333333333333 +4510 3333333333333333333333333333333333333333333333333333333 +4511 3333333333333333333333333333333333333333333333333333333 +4512 3333333333333333333333333333333333333333333333333333333 +4513 3333333333333333333333333333333333333333333333333333333 +4514 3333333333333333333333333333333333333333333333333333333 +4515 3333333333333333333333333333333333333333333333333333333 +4516 3333333333333333333333333333333333333333333333333333333 +4517 3333333333333333333333333333333333333333333333333333333 +4518 3333333333333333333333333333333333333333333333333333333 +4519 3333333333333333333333333333333333333333333333333333333 +4520 3333333333333333333333333333333333333333333333333333333 +4521 3333333333333333333333333333333333333333333333333333333 +4522 3333333333333333333333333333333333333333333333333333333 +4523 3333333333333333333333333333333333333333333333333333333 +4524 3333333333333333333333333333333333333333333333333333333 +4525 3333333333333333333333333333333333333333333333333333333 +4526 3333333333333333333333333333333333333333333333333333333 +4527 3333333333333333333333333333333333333333333333333333333 +4528 3333333333333333333333333333333333333333333333333333333 +4529 3333333333333333333333333333333333333333333333333333333 +4530 3333333333333333333333333333333333333333333333333333333 +4531 3333333333333333333333333333333333333333333333333333333 +4532 3333333333333333333333333333333333333333333333333333333 +4533 3333333333333333333333333333333333333333333333333333333 +4534 3333333333333333333333333333333333333333333333333333333 +4535 3333333333333333333333333333333333333333333333333333333 +4536 3333333333333333333333333333333333333333333333333333333 +4537 3333333333333333333333333333333333333333333333333333333 +4538 3333333333333333333333333333333333333333333333333333333 +4539 3333333333333333333333333333333333333333333333333333333 +4540 3333333333333333333333333333333333333333333333333333333 +4541 3333333333333333333333333333333333333333333333333333333 +4542 3333333333333333333333333333333333333333333333333333333 +4543 3333333333333333333333333333333333333333333333333333333 +4544 3333333333333333333333333333333333333333333333333333333 +4545 3333333333333333333333333333333333333333333333333333333 +4546 3333333333333333333333333333333333333333333333333333333 +4547 3333333333333333333333333333333333333333333333333333333 +4548 3333333333333333333333333333333333333333333333333333333 +4549 3333333333333333333333333333333333333333333333333333333 +4550 3333333333333333333333333333333333333333333333333333333 +4551 3333333333333333333333333333333333333333333333333333333 +4552 3333333333333333333333333333333333333333333333333333333 +4553 3333333333333333333333333333333333333333333333333333333 +4554 3333333333333333333333333333333333333333333333333333333 +4555 3333333333333333333333333333333333333333333333333333333 +4556 3333333333333333333333333333333333333333333333333333333 +4557 3333333333333333333333333333333333333333333333333333333 +4558 3333333333333333333333333333333333333333333333333333333 +4559 3333333333333333333333333333333333333333333333333333333 +4560 3333333333333333333333333333333333333333333333333333333 +4561 3333333333333333333333333333333333333333333333333333333 +4562 3333333333333333333333333333333333333333333333333333333 +4563 3333333333333333333333333333333333333333333333333333333 +4564 3333333333333333333333333333333333333333333333333333333 +4565 3333333333333333333333333333333333333333333333333333333 +4566 3333333333333333333333333333333333333333333333333333333 +4567 3333333333333333333333333333333333333333333333333333333 +4568 3333333333333333333333333333333333333333333333333333333 +4569 3333333333333333333333333333333333333333333333333333333 +4570 3333333333333333333333333333333333333333333333333333333 +4571 3333333333333333333333333333333333333333333333333333333 +4572 3333333333333333333333333333333333333333333333333333333 +4573 3333333333333333333333333333333333333333333333333333333 +4574 3333333333333333333333333333333333333333333333333333333 +4575 3333333333333333333333333333333333333333333333333333333 +4576 3333333333333333333333333333333333333333333333333333333 +4577 3333333333333333333333333333333333333333333333333333333 +4578 3333333333333333333333333333333333333333333333333333333 +4579 3333333333333333333333333333333333333333333333333333333 +4580 3333333333333333333333333333333333333333333333333333333 +4581 3333333333333333333333333333333333333333333333333333333 +4582 3333333333333333333333333333333333333333333333333333333 +4583 3333333333333333333333333333333333333333333333333333333 +4584 3333333333333333333333333333333333333333333333333333333 +4585 3333333333333333333333333333333333333333333333333333333 +4586 3333333333333333333333333333333333333333333333333333333 +4587 3333333333333333333333333333333333333333333333333333333 +4588 3333333333333333333333333333333333333333333333333333333 +4589 3333333333333333333333333333333333333333333333333333333 +4590 3333333333333333333333333333333333333333333333333333333 +4591 3333333333333333333333333333333333333333333333333333333 +4592 3333333333333333333333333333333333333333333333333333333 +4593 3333333333333333333333333333333333333333333333333333333 +4594 3333333333333333333333333333333333333333333333333333333 +4595 3333333333333333333333333333333333333333333333333333333 +4596 3333333333333333333333333333333333333333333333333333333 +4597 3333333333333333333333333333333333333333333333333333333 +4598 3333333333333333333333333333333333333333333333333333333 +4599 3333333333333333333333333333333333333333333333333333333 +4600 3333333333333333333333333333333333333333333333333333333 +4601 3333333333333333333333333333333333333333333333333333333 +4602 3333333333333333333333333333333333333333333333333333333 +4603 3333333333333333333333333333333333333333333333333333333 +4604 3333333333333333333333333333333333333333333333333333333 +4605 3333333333333333333333333333333333333333333333333333333 +4606 3333333333333333333333333333333333333333333333333333333 +4607 3333333333333333333333333333333333333333333333333333333 +4608 3333333333333333333333333333333333333333333333333333333 +4609 3333333333333333333333333333333333333333333333333333333 +4610 3333333333333333333333333333333333333333333333333333333 +4611 3333333333333333333333333333333333333333333333333333333 +4612 3333333333333333333333333333333333333333333333333333333 +4613 3333333333333333333333333333333333333333333333333333333 +4614 3333333333333333333333333333333333333333333333333333333 +4615 3333333333333333333333333333333333333333333333333333333 +4616 3333333333333333333333333333333333333333333333333333333 +4617 3333333333333333333333333333333333333333333333333333333 +4618 3333333333333333333333333333333333333333333333333333333 +4619 3333333333333333333333333333333333333333333333333333333 +4620 3333333333333333333333333333333333333333333333333333333 +4621 3333333333333333333333333333333333333333333333333333333 +4622 3333333333333333333333333333333333333333333333333333333 +4623 3333333333333333333333333333333333333333333333333333333 +4624 3333333333333333333333333333333333333333333333333333333 +4625 3333333333333333333333333333333333333333333333333333333 +4626 3333333333333333333333333333333333333333333333333333333 +4627 3333333333333333333333333333333333333333333333333333333 +4628 3333333333333333333333333333333333333333333333333333333 +4629 3333333333333333333333333333333333333333333333333333333 +4630 3333333333333333333333333333333333333333333333333333333 +4631 3333333333333333333333333333333333333333333333333333333 +4632 3333333333333333333333333333333333333333333333333333333 +4633 3333333333333333333333333333333333333333333333333333333 +4634 3333333333333333333333333333333333333333333333333333333 +4635 3333333333333333333333333333333333333333333333333333333 +4636 3333333333333333333333333333333333333333333333333333333 +4637 3333333333333333333333333333333333333333333333333333333 +4638 3333333333333333333333333333333333333333333333333333333 +4639 3333333333333333333333333333333333333333333333333333333 +4640 3333333333333333333333333333333333333333333333333333333 +4641 3333333333333333333333333333333333333333333333333333333 +4642 3333333333333333333333333333333333333333333333333333333 +4643 3333333333333333333333333333333333333333333333333333333 +4644 3333333333333333333333333333333333333333333333333333333 +4645 3333333333333333333333333333333333333333333333333333333 +4646 3333333333333333333333333333333333333333333333333333333 +4647 3333333333333333333333333333333333333333333333333333333 +4648 3333333333333333333333333333333333333333333333333333333 +4649 3333333333333333333333333333333333333333333333333333333 +4650 3333333333333333333333333333333333333333333333333333333 +4651 3333333333333333333333333333333333333333333333333333333 +4652 3333333333333333333333333333333333333333333333333333333 +4653 3333333333333333333333333333333333333333333333333333333 +4654 3333333333333333333333333333333333333333333333333333333 +4655 3333333333333333333333333333333333333333333333333333333 +4656 3333333333333333333333333333333333333333333333333333333 +4657 3333333333333333333333333333333333333333333333333333333 +4658 3333333333333333333333333333333333333333333333333333333 +4659 3333333333333333333333333333333333333333333333333333333 +4660 3333333333333333333333333333333333333333333333333333333 +4661 3333333333333333333333333333333333333333333333333333333 +4662 3333333333333333333333333333333333333333333333333333333 +4663 3333333333333333333333333333333333333333333333333333333 +4664 3333333333333333333333333333333333333333333333333333333 +4665 3333333333333333333333333333333333333333333333333333333 +4666 3333333333333333333333333333333333333333333333333333333 +4667 3333333333333333333333333333333333333333333333333333333 +4668 3333333333333333333333333333333333333333333333333333333 +4669 3333333333333333333333333333333333333333333333333333333 +4670 3333333333333333333333333333333333333333333333333333333 +4671 3333333333333333333333333333333333333333333333333333333 +4672 3333333333333333333333333333333333333333333333333333333 +4673 3333333333333333333333333333333333333333333333333333333 +4674 3333333333333333333333333333333333333333333333333333333 +4675 3333333333333333333333333333333333333333333333333333333 +4676 3333333333333333333333333333333333333333333333333333333 +4677 3333333333333333333333333333333333333333333333333333333 +4678 3333333333333333333333333333333333333333333333333333333 +4679 3333333333333333333333333333333333333333333333333333333 +4680 3333333333333333333333333333333333333333333333333333333 +4681 3333333333333333333333333333333333333333333333333333333 +4682 3333333333333333333333333333333333333333333333333333333 +4683 3333333333333333333333333333333333333333333333333333333 +4684 3333333333333333333333333333333333333333333333333333333 +4685 3333333333333333333333333333333333333333333333333333333 +4686 3333333333333333333333333333333333333333333333333333333 +4687 3333333333333333333333333333333333333333333333333333333 +4688 3333333333333333333333333333333333333333333333333333333 +4689 3333333333333333333333333333333333333333333333333333333 +4690 3333333333333333333333333333333333333333333333333333333 +4691 3333333333333333333333333333333333333333333333333333333 +4692 3333333333333333333333333333333333333333333333333333333 +4693 3333333333333333333333333333333333333333333333333333333 +4694 3333333333333333333333333333333333333333333333333333333 +4695 3333333333333333333333333333333333333333333333333333333 +4696 3333333333333333333333333333333333333333333333333333333 +4697 3333333333333333333333333333333333333333333333333333333 +4698 3333333333333333333333333333333333333333333333333333333 +4699 3333333333333333333333333333333333333333333333333333333 +4700 3333333333333333333333333333333333333333333333333333333 +4701 3333333333333333333333333333333333333333333333333333333 +4702 3333333333333333333333333333333333333333333333333333333 +4703 3333333333333333333333333333333333333333333333333333333 +4704 3333333333333333333333333333333333333333333333333333333 +4705 3333333333333333333333333333333333333333333333333333333 +4706 3333333333333333333333333333333333333333333333333333333 +4707 3333333333333333333333333333333333333333333333333333333 +4708 3333333333333333333333333333333333333333333333333333333 +4709 3333333333333333333333333333333333333333333333333333333 +4710 3333333333333333333333333333333333333333333333333333333 +4711 3333333333333333333333333333333333333333333333333333333 +4712 3333333333333333333333333333333333333333333333333333333 +4713 3333333333333333333333333333333333333333333333333333333 +4714 3333333333333333333333333333333333333333333333333333333 +4715 3333333333333333333333333333333333333333333333333333333 +4716 3333333333333333333333333333333333333333333333333333333 +4717 3333333333333333333333333333333333333333333333333333333 +4718 3333333333333333333333333333333333333333333333333333333 +4719 3333333333333333333333333333333333333333333333333333333 +4720 3333333333333333333333333333333333333333333333333333333 +4721 3333333333333333333333333333333333333333333333333333333 +4722 3333333333333333333333333333333333333333333333333333333 +4723 3333333333333333333333333333333333333333333333333333333 +4724 3333333333333333333333333333333333333333333333333333333 +4725 3333333333333333333333333333333333333333333333333333333 +4726 3333333333333333333333333333333333333333333333333333333 +4727 3333333333333333333333333333333333333333333333333333333 +4728 3333333333333333333333333333333333333333333333333333333 +4729 3333333333333333333333333333333333333333333333333333333 +4730 3333333333333333333333333333333333333333333333333333333 +4731 3333333333333333333333333333333333333333333333333333333 +4732 3333333333333333333333333333333333333333333333333333333 +4733 3333333333333333333333333333333333333333333333333333333 +4734 3333333333333333333333333333333333333333333333333333333 +4735 3333333333333333333333333333333333333333333333333333333 +4736 3333333333333333333333333333333333333333333333333333333 +4737 3333333333333333333333333333333333333333333333333333333 +4738 3333333333333333333333333333333333333333333333333333333 +4739 3333333333333333333333333333333333333333333333333333333 +4740 3333333333333333333333333333333333333333333333333333333 +4741 3333333333333333333333333333333333333333333333333333333 +4742 3333333333333333333333333333333333333333333333333333333 +4743 3333333333333333333333333333333333333333333333333333333 +4744 3333333333333333333333333333333333333333333333333333333 +4745 3333333333333333333333333333333333333333333333333333333 +4746 3333333333333333333333333333333333333333333333333333333 +4747 3333333333333333333333333333333333333333333333333333333 +4748 3333333333333333333333333333333333333333333333333333333 +4749 3333333333333333333333333333333333333333333333333333333 +4750 3333333333333333333333333333333333333333333333333333333 +4751 3333333333333333333333333333333333333333333333333333333 +4752 3333333333333333333333333333333333333333333333333333333 +4753 3333333333333333333333333333333333333333333333333333333 +4754 3333333333333333333333333333333333333333333333333333333 +4755 3333333333333333333333333333333333333333333333333333333 +4756 3333333333333333333333333333333333333333333333333333333 +4757 3333333333333333333333333333333333333333333333333333333 +4758 3333333333333333333333333333333333333333333333333333333 +4759 3333333333333333333333333333333333333333333333333333333 +4760 3333333333333333333333333333333333333333333333333333333 +4761 3333333333333333333333333333333333333333333333333333333 +4762 3333333333333333333333333333333333333333333333333333333 +4763 3333333333333333333333333333333333333333333333333333333 +4764 3333333333333333333333333333333333333333333333333333333 +4765 3333333333333333333333333333333333333333333333333333333 +4766 3333333333333333333333333333333333333333333333333333333 +4767 3333333333333333333333333333333333333333333333333333333 +4768 3333333333333333333333333333333333333333333333333333333 +4769 3333333333333333333333333333333333333333333333333333333 +4770 3333333333333333333333333333333333333333333333333333333 +4771 3333333333333333333333333333333333333333333333333333333 +4772 3333333333333333333333333333333333333333333333333333333 +4773 3333333333333333333333333333333333333333333333333333333 +4774 3333333333333333333333333333333333333333333333333333333 +4775 3333333333333333333333333333333333333333333333333333333 +4776 3333333333333333333333333333333333333333333333333333333 +4777 3333333333333333333333333333333333333333333333333333333 +4778 3333333333333333333333333333333333333333333333333333333 +4779 3333333333333333333333333333333333333333333333333333333 +4780 3333333333333333333333333333333333333333333333333333333 +4781 3333333333333333333333333333333333333333333333333333333 +4782 3333333333333333333333333333333333333333333333333333333 +4783 3333333333333333333333333333333333333333333333333333333 +4784 3333333333333333333333333333333333333333333333333333333 +4785 3333333333333333333333333333333333333333333333333333333 +4786 3333333333333333333333333333333333333333333333333333333 +4787 3333333333333333333333333333333333333333333333333333333 +4788 3333333333333333333333333333333333333333333333333333333 +4789 3333333333333333333333333333333333333333333333333333333 +4790 3333333333333333333333333333333333333333333333333333333 +4791 3333333333333333333333333333333333333333333333333333333 +4792 3333333333333333333333333333333333333333333333333333333 +4793 3333333333333333333333333333333333333333333333333333333 +4794 3333333333333333333333333333333333333333333333333333333 +4795 3333333333333333333333333333333333333333333333333333333 +4796 3333333333333333333333333333333333333333333333333333333 +4797 3333333333333333333333333333333333333333333333333333333 +4798 3333333333333333333333333333333333333333333333333333333 +4799 3333333333333333333333333333333333333333333333333333333 +4800 3333333333333333333333333333333333333333333333333333333 +4801 3333333333333333333333333333333333333333333333333333333 +4802 3333333333333333333333333333333333333333333333333333333 +4803 3333333333333333333333333333333333333333333333333333333 +4804 3333333333333333333333333333333333333333333333333333333 +4805 3333333333333333333333333333333333333333333333333333333 +4806 3333333333333333333333333333333333333333333333333333333 +4807 3333333333333333333333333333333333333333333333333333333 +4808 3333333333333333333333333333333333333333333333333333333 +4809 3333333333333333333333333333333333333333333333333333333 +4810 3333333333333333333333333333333333333333333333333333333 +4811 3333333333333333333333333333333333333333333333333333333 +4812 3333333333333333333333333333333333333333333333333333333 +4813 3333333333333333333333333333333333333333333333333333333 +4814 3333333333333333333333333333333333333333333333333333333 +4815 3333333333333333333333333333333333333333333333333333333 +4816 3333333333333333333333333333333333333333333333333333333 +4817 3333333333333333333333333333333333333333333333333333333 +4818 3333333333333333333333333333333333333333333333333333333 +4819 3333333333333333333333333333333333333333333333333333333 +4820 3333333333333333333333333333333333333333333333333333333 +4821 3333333333333333333333333333333333333333333333333333333 +4822 3333333333333333333333333333333333333333333333333333333 +4823 3333333333333333333333333333333333333333333333333333333 +4824 3333333333333333333333333333333333333333333333333333333 +4825 3333333333333333333333333333333333333333333333333333333 +4826 3333333333333333333333333333333333333333333333333333333 +4827 3333333333333333333333333333333333333333333333333333333 +4828 3333333333333333333333333333333333333333333333333333333 +4829 3333333333333333333333333333333333333333333333333333333 +4830 3333333333333333333333333333333333333333333333333333333 +4831 3333333333333333333333333333333333333333333333333333333 +4832 3333333333333333333333333333333333333333333333333333333 +4833 3333333333333333333333333333333333333333333333333333333 +4834 3333333333333333333333333333333333333333333333333333333 +4835 3333333333333333333333333333333333333333333333333333333 +4836 3333333333333333333333333333333333333333333333333333333 +4837 3333333333333333333333333333333333333333333333333333333 +4838 3333333333333333333333333333333333333333333333333333333 +4839 3333333333333333333333333333333333333333333333333333333 +4840 3333333333333333333333333333333333333333333333333333333 +4841 3333333333333333333333333333333333333333333333333333333 +4842 3333333333333333333333333333333333333333333333333333333 +4843 3333333333333333333333333333333333333333333333333333333 +4844 3333333333333333333333333333333333333333333333333333333 +4845 3333333333333333333333333333333333333333333333333333333 +4846 3333333333333333333333333333333333333333333333333333333 +4847 3333333333333333333333333333333333333333333333333333333 +4848 3333333333333333333333333333333333333333333333333333333 +4849 3333333333333333333333333333333333333333333333333333333 +4850 3333333333333333333333333333333333333333333333333333333 +4851 3333333333333333333333333333333333333333333333333333333 +4852 3333333333333333333333333333333333333333333333333333333 +4853 3333333333333333333333333333333333333333333333333333333 +4854 3333333333333333333333333333333333333333333333333333333 +4855 3333333333333333333333333333333333333333333333333333333 +4856 3333333333333333333333333333333333333333333333333333333 +4857 3333333333333333333333333333333333333333333333333333333 +4858 3333333333333333333333333333333333333333333333333333333 +4859 3333333333333333333333333333333333333333333333333333333 +4860 3333333333333333333333333333333333333333333333333333333 +4861 3333333333333333333333333333333333333333333333333333333 +4862 3333333333333333333333333333333333333333333333333333333 +4863 3333333333333333333333333333333333333333333333333333333 +4864 3333333333333333333333333333333333333333333333333333333 +4865 3333333333333333333333333333333333333333333333333333333 +4866 3333333333333333333333333333333333333333333333333333333 +4867 3333333333333333333333333333333333333333333333333333333 +4868 3333333333333333333333333333333333333333333333333333333 +4869 3333333333333333333333333333333333333333333333333333333 +4870 3333333333333333333333333333333333333333333333333333333 +4871 3333333333333333333333333333333333333333333333333333333 +4872 3333333333333333333333333333333333333333333333333333333 +4873 3333333333333333333333333333333333333333333333333333333 +4874 3333333333333333333333333333333333333333333333333333333 +4875 3333333333333333333333333333333333333333333333333333333 +4876 3333333333333333333333333333333333333333333333333333333 +4877 3333333333333333333333333333333333333333333333333333333 +4878 3333333333333333333333333333333333333333333333333333333 +4879 3333333333333333333333333333333333333333333333333333333 +4880 3333333333333333333333333333333333333333333333333333333 +4881 3333333333333333333333333333333333333333333333333333333 +4882 3333333333333333333333333333333333333333333333333333333 +4883 3333333333333333333333333333333333333333333333333333333 +4884 3333333333333333333333333333333333333333333333333333333 +4885 3333333333333333333333333333333333333333333333333333333 +4886 3333333333333333333333333333333333333333333333333333333 +4887 3333333333333333333333333333333333333333333333333333333 +4888 3333333333333333333333333333333333333333333333333333333 +4889 3333333333333333333333333333333333333333333333333333333 +4890 3333333333333333333333333333333333333333333333333333333 +4891 3333333333333333333333333333333333333333333333333333333 +4892 3333333333333333333333333333333333333333333333333333333 +4893 3333333333333333333333333333333333333333333333333333333 +4894 3333333333333333333333333333333333333333333333333333333 +4895 3333333333333333333333333333333333333333333333333333333 +4896 3333333333333333333333333333333333333333333333333333333 +4897 3333333333333333333333333333333333333333333333333333333 +4898 3333333333333333333333333333333333333333333333333333333 +4899 3333333333333333333333333333333333333333333333333333333 +4900 3333333333333333333333333333333333333333333333333333333 +4901 3333333333333333333333333333333333333333333333333333333 +4902 3333333333333333333333333333333333333333333333333333333 +4903 3333333333333333333333333333333333333333333333333333333 +4904 3333333333333333333333333333333333333333333333333333333 +4905 3333333333333333333333333333333333333333333333333333333 +4906 3333333333333333333333333333333333333333333333333333333 +4907 3333333333333333333333333333333333333333333333333333333 +4908 3333333333333333333333333333333333333333333333333333333 +4909 3333333333333333333333333333333333333333333333333333333 +4910 3333333333333333333333333333333333333333333333333333333 +4911 3333333333333333333333333333333333333333333333333333333 +4912 3333333333333333333333333333333333333333333333333333333 +4913 3333333333333333333333333333333333333333333333333333333 +4914 3333333333333333333333333333333333333333333333333333333 +4915 3333333333333333333333333333333333333333333333333333333 +4916 3333333333333333333333333333333333333333333333333333333 +4917 3333333333333333333333333333333333333333333333333333333 +4918 3333333333333333333333333333333333333333333333333333333 +4919 3333333333333333333333333333333333333333333333333333333 +4920 3333333333333333333333333333333333333333333333333333333 +4921 3333333333333333333333333333333333333333333333333333333 +4922 3333333333333333333333333333333333333333333333333333333 +4923 3333333333333333333333333333333333333333333333333333333 +4924 3333333333333333333333333333333333333333333333333333333 +4925 3333333333333333333333333333333333333333333333333333333 +4926 3333333333333333333333333333333333333333333333333333333 +4927 3333333333333333333333333333333333333333333333333333333 +4928 3333333333333333333333333333333333333333333333333333333 +4929 3333333333333333333333333333333333333333333333333333333 +4930 3333333333333333333333333333333333333333333333333333333 +4931 3333333333333333333333333333333333333333333333333333333 +4932 3333333333333333333333333333333333333333333333333333333 +4933 3333333333333333333333333333333333333333333333333333333 +4934 3333333333333333333333333333333333333333333333333333333 +4935 3333333333333333333333333333333333333333333333333333333 +4936 3333333333333333333333333333333333333333333333333333333 +4937 3333333333333333333333333333333333333333333333333333333 +4938 3333333333333333333333333333333333333333333333333333333 +4939 3333333333333333333333333333333333333333333333333333333 +4940 3333333333333333333333333333333333333333333333333333333 +4941 3333333333333333333333333333333333333333333333333333333 +4942 3333333333333333333333333333333333333333333333333333333 +4943 3333333333333333333333333333333333333333333333333333333 +4944 3333333333333333333333333333333333333333333333333333333 +4945 3333333333333333333333333333333333333333333333333333333 +4946 3333333333333333333333333333333333333333333333333333333 +4947 3333333333333333333333333333333333333333333333333333333 +4948 3333333333333333333333333333333333333333333333333333333 +4949 3333333333333333333333333333333333333333333333333333333 +4950 3333333333333333333333333333333333333333333333333333333 +4951 3333333333333333333333333333333333333333333333333333333 +4952 3333333333333333333333333333333333333333333333333333333 +4953 3333333333333333333333333333333333333333333333333333333 +4954 3333333333333333333333333333333333333333333333333333333 +4955 3333333333333333333333333333333333333333333333333333333 +4956 3333333333333333333333333333333333333333333333333333333 +4957 3333333333333333333333333333333333333333333333333333333 +4958 3333333333333333333333333333333333333333333333333333333 +4959 3333333333333333333333333333333333333333333333333333333 +4960 3333333333333333333333333333333333333333333333333333333 +4961 3333333333333333333333333333333333333333333333333333333 +4962 3333333333333333333333333333333333333333333333333333333 +4963 3333333333333333333333333333333333333333333333333333333 +4964 3333333333333333333333333333333333333333333333333333333 +4965 3333333333333333333333333333333333333333333333333333333 +4966 3333333333333333333333333333333333333333333333333333333 +4967 3333333333333333333333333333333333333333333333333333333 +4968 3333333333333333333333333333333333333333333333333333333 +4969 3333333333333333333333333333333333333333333333333333333 +4970 3333333333333333333333333333333333333333333333333333333 +4971 3333333333333333333333333333333333333333333333333333333 +4972 3333333333333333333333333333333333333333333333333333333 +4973 3333333333333333333333333333333333333333333333333333333 +4974 3333333333333333333333333333333333333333333333333333333 +4975 3333333333333333333333333333333333333333333333333333333 +4976 3333333333333333333333333333333333333333333333333333333 +4977 3333333333333333333333333333333333333333333333333333333 +4978 3333333333333333333333333333333333333333333333333333333 +4979 3333333333333333333333333333333333333333333333333333333 +4980 3333333333333333333333333333333333333333333333333333333 +4981 3333333333333333333333333333333333333333333333333333333 +4982 3333333333333333333333333333333333333333333333333333333 +4983 3333333333333333333333333333333333333333333333333333333 +4984 3333333333333333333333333333333333333333333333333333333 +4985 3333333333333333333333333333333333333333333333333333333 +4986 3333333333333333333333333333333333333333333333333333333 +4987 3333333333333333333333333333333333333333333333333333333 +4988 3333333333333333333333333333333333333333333333333333333 +4989 3333333333333333333333333333333333333333333333333333333 +4990 3333333333333333333333333333333333333333333333333333333 +4991 3333333333333333333333333333333333333333333333333333333 +4992 3333333333333333333333333333333333333333333333333333333 +4993 3333333333333333333333333333333333333333333333333333333 +4994 3333333333333333333333333333333333333333333333333333333 +4995 3333333333333333333333333333333333333333333333333333333 +4996 3333333333333333333333333333333333333333333333333333333 +4997 3333333333333333333333333333333333333333333333333333333 +4998 3333333333333333333333333333333333333333333333333333333 +4999 3333333333333333333333333333333333333333333333333333333 +5000 3333333333333333333333333333333333333333333333333333333 +5001 3333333333333333333333333333333333333333333333333333333 +5002 3333333333333333333333333333333333333333333333333333333 +5003 3333333333333333333333333333333333333333333333333333333 +5004 3333333333333333333333333333333333333333333333333333333 +5005 3333333333333333333333333333333333333333333333333333333 +5006 3333333333333333333333333333333333333333333333333333333 +5007 3333333333333333333333333333333333333333333333333333333 +5008 3333333333333333333333333333333333333333333333333333333 +5009 3333333333333333333333333333333333333333333333333333333 +5010 3333333333333333333333333333333333333333333333333333333 +5011 3333333333333333333333333333333333333333333333333333333 +5012 3333333333333333333333333333333333333333333333333333333 +5013 3333333333333333333333333333333333333333333333333333333 +5014 3333333333333333333333333333333333333333333333333333333 +5015 3333333333333333333333333333333333333333333333333333333 +5016 3333333333333333333333333333333333333333333333333333333 +5017 3333333333333333333333333333333333333333333333333333333 +5018 3333333333333333333333333333333333333333333333333333333 +5019 3333333333333333333333333333333333333333333333333333333 +5020 3333333333333333333333333333333333333333333333333333333 +5021 3333333333333333333333333333333333333333333333333333333 +5022 3333333333333333333333333333333333333333333333333333333 +5023 3333333333333333333333333333333333333333333333333333333 +5024 3333333333333333333333333333333333333333333333333333333 +5025 3333333333333333333333333333333333333333333333333333333 +5026 3333333333333333333333333333333333333333333333333333333 +5027 3333333333333333333333333333333333333333333333333333333 +5028 3333333333333333333333333333333333333333333333333333333 +5029 3333333333333333333333333333333333333333333333333333333 +5030 3333333333333333333333333333333333333333333333333333333 +5031 3333333333333333333333333333333333333333333333333333333 +5032 3333333333333333333333333333333333333333333333333333333 +5033 3333333333333333333333333333333333333333333333333333333 +5034 3333333333333333333333333333333333333333333333333333333 +5035 3333333333333333333333333333333333333333333333333333333 +5036 3333333333333333333333333333333333333333333333333333333 +5037 3333333333333333333333333333333333333333333333333333333 +5038 3333333333333333333333333333333333333333333333333333333 +5039 3333333333333333333333333333333333333333333333333333333 +5040 3333333333333333333333333333333333333333333333333333333 +5041 3333333333333333333333333333333333333333333333333333333 +5042 3333333333333333333333333333333333333333333333333333333 +5043 3333333333333333333333333333333333333333333333333333333 +5044 3333333333333333333333333333333333333333333333333333333 +5045 3333333333333333333333333333333333333333333333333333333 +5046 3333333333333333333333333333333333333333333333333333333 +5047 3333333333333333333333333333333333333333333333333333333 +5048 3333333333333333333333333333333333333333333333333333333 +5049 3333333333333333333333333333333333333333333333333333333 +5050 3333333333333333333333333333333333333333333333333333333 +5051 3333333333333333333333333333333333333333333333333333333 +5052 3333333333333333333333333333333333333333333333333333333 +5053 3333333333333333333333333333333333333333333333333333333 +5054 3333333333333333333333333333333333333333333333333333333 +5055 3333333333333333333333333333333333333333333333333333333 +5056 3333333333333333333333333333333333333333333333333333333 +5057 3333333333333333333333333333333333333333333333333333333 +5058 3333333333333333333333333333333333333333333333333333333 +5059 3333333333333333333333333333333333333333333333333333333 +5060 3333333333333333333333333333333333333333333333333333333 +5061 3333333333333333333333333333333333333333333333333333333 +5062 3333333333333333333333333333333333333333333333333333333 +5063 3333333333333333333333333333333333333333333333333333333 +5064 3333333333333333333333333333333333333333333333333333333 +5065 3333333333333333333333333333333333333333333333333333333 +5066 3333333333333333333333333333333333333333333333333333333 +5067 3333333333333333333333333333333333333333333333333333333 +5068 3333333333333333333333333333333333333333333333333333333 +5069 3333333333333333333333333333333333333333333333333333333 +5070 3333333333333333333333333333333333333333333333333333333 +5071 3333333333333333333333333333333333333333333333333333333 +5072 3333333333333333333333333333333333333333333333333333333 +5073 3333333333333333333333333333333333333333333333333333333 +5074 3333333333333333333333333333333333333333333333333333333 +5075 3333333333333333333333333333333333333333333333333333333 +5076 3333333333333333333333333333333333333333333333333333333 +5077 3333333333333333333333333333333333333333333333333333333 +5078 3333333333333333333333333333333333333333333333333333333 +5079 3333333333333333333333333333333333333333333333333333333 +5080 3333333333333333333333333333333333333333333333333333333 +5081 3333333333333333333333333333333333333333333333333333333 +5082 3333333333333333333333333333333333333333333333333333333 +5083 3333333333333333333333333333333333333333333333333333333 +5084 3333333333333333333333333333333333333333333333333333333 +5085 3333333333333333333333333333333333333333333333333333333 +5086 3333333333333333333333333333333333333333333333333333333 +5087 3333333333333333333333333333333333333333333333333333333 +5088 3333333333333333333333333333333333333333333333333333333 +5089 3333333333333333333333333333333333333333333333333333333 +5090 3333333333333333333333333333333333333333333333333333333 +5091 3333333333333333333333333333333333333333333333333333333 +5092 3333333333333333333333333333333333333333333333333333333 +5093 3333333333333333333333333333333333333333333333333333333 +5094 3333333333333333333333333333333333333333333333333333333 +5095 3333333333333333333333333333333333333333333333333333333 +5096 3333333333333333333333333333333333333333333333333333333 +5097 3333333333333333333333333333333333333333333333333333333 +5098 3333333333333333333333333333333333333333333333333333333 +5099 3333333333333333333333333333333333333333333333333333333 +5100 3333333333333333333333333333333333333333333333333333333 +5101 3333333333333333333333333333333333333333333333333333333 +5102 3333333333333333333333333333333333333333333333333333333 +5103 3333333333333333333333333333333333333333333333333333333 +5104 3333333333333333333333333333333333333333333333333333333 +5105 3333333333333333333333333333333333333333333333333333333 +5106 3333333333333333333333333333333333333333333333333333333 +5107 3333333333333333333333333333333333333333333333333333333 +5108 3333333333333333333333333333333333333333333333333333333 +5109 3333333333333333333333333333333333333333333333333333333 +5110 3333333333333333333333333333333333333333333333333333333 +5111 3333333333333333333333333333333333333333333333333333333 +5112 3333333333333333333333333333333333333333333333333333333 +5113 3333333333333333333333333333333333333333333333333333333 +5114 3333333333333333333333333333333333333333333333333333333 +5115 3333333333333333333333333333333333333333333333333333333 +5116 3333333333333333333333333333333333333333333333333333333 +5117 3333333333333333333333333333333333333333333333333333333 +5118 3333333333333333333333333333333333333333333333333333333 +5119 3333333333333333333333333333333333333333333333333333333 +5120 3333333333333333333333333333333333333333333333333333333 +5121 3333333333333333333333333333333333333333333333333333333 +5122 3333333333333333333333333333333333333333333333333333333 +5123 3333333333333333333333333333333333333333333333333333333 +5124 3333333333333333333333333333333333333333333333333333333 +5125 3333333333333333333333333333333333333333333333333333333 +5126 3333333333333333333333333333333333333333333333333333333 +5127 3333333333333333333333333333333333333333333333333333333 +5128 3333333333333333333333333333333333333333333333333333333 +5129 3333333333333333333333333333333333333333333333333333333 +5130 3333333333333333333333333333333333333333333333333333333 +5131 3333333333333333333333333333333333333333333333333333333 +5132 3333333333333333333333333333333333333333333333333333333 +5133 3333333333333333333333333333333333333333333333333333333 +5134 3333333333333333333333333333333333333333333333333333333 +5135 3333333333333333333333333333333333333333333333333333333 +5136 3333333333333333333333333333333333333333333333333333333 +5137 3333333333333333333333333333333333333333333333333333333 +5138 3333333333333333333333333333333333333333333333333333333 +5139 3333333333333333333333333333333333333333333333333333333 +5140 3333333333333333333333333333333333333333333333333333333 +5141 3333333333333333333333333333333333333333333333333333333 +5142 3333333333333333333333333333333333333333333333333333333 +5143 3333333333333333333333333333333333333333333333333333333 +5144 3333333333333333333333333333333333333333333333333333333 +5145 3333333333333333333333333333333333333333333333333333333 +5146 3333333333333333333333333333333333333333333333333333333 +5147 3333333333333333333333333333333333333333333333333333333 +5148 3333333333333333333333333333333333333333333333333333333 +5149 3333333333333333333333333333333333333333333333333333333 +5150 3333333333333333333333333333333333333333333333333333333 +5151 3333333333333333333333333333333333333333333333333333333 +5152 3333333333333333333333333333333333333333333333333333333 +5153 3333333333333333333333333333333333333333333333333333333 +5154 3333333333333333333333333333333333333333333333333333333 +5155 3333333333333333333333333333333333333333333333333333333 +5156 3333333333333333333333333333333333333333333333333333333 +5157 3333333333333333333333333333333333333333333333333333333 +5158 3333333333333333333333333333333333333333333333333333333 +5159 3333333333333333333333333333333333333333333333333333333 +5160 3333333333333333333333333333333333333333333333333333333 +5161 3333333333333333333333333333333333333333333333333333333 +5162 3333333333333333333333333333333333333333333333333333333 +5163 3333333333333333333333333333333333333333333333333333333 +5164 3333333333333333333333333333333333333333333333333333333 +5165 3333333333333333333333333333333333333333333333333333333 +5166 3333333333333333333333333333333333333333333333333333333 +5167 3333333333333333333333333333333333333333333333333333333 +5168 3333333333333333333333333333333333333333333333333333333 +5169 3333333333333333333333333333333333333333333333333333333 +5170 3333333333333333333333333333333333333333333333333333333 +5171 3333333333333333333333333333333333333333333333333333333 +5172 3333333333333333333333333333333333333333333333333333333 +5173 3333333333333333333333333333333333333333333333333333333 +5174 3333333333333333333333333333333333333333333333333333333 +5175 3333333333333333333333333333333333333333333333333333333 +5176 3333333333333333333333333333333333333333333333333333333 +5177 3333333333333333333333333333333333333333333333333333333 +5178 3333333333333333333333333333333333333333333333333333333 +5179 3333333333333333333333333333333333333333333333333333333 +5180 3333333333333333333333333333333333333333333333333333333 +5181 3333333333333333333333333333333333333333333333333333333 +5182 3333333333333333333333333333333333333333333333333333333 +5183 3333333333333333333333333333333333333333333333333333333 +5184 3333333333333333333333333333333333333333333333333333333 +5185 3333333333333333333333333333333333333333333333333333333 +5186 3333333333333333333333333333333333333333333333333333333 +5187 3333333333333333333333333333333333333333333333333333333 +5188 3333333333333333333333333333333333333333333333333333333 +5189 3333333333333333333333333333333333333333333333333333333 +5190 3333333333333333333333333333333333333333333333333333333 +5191 3333333333333333333333333333333333333333333333333333333 +5192 3333333333333333333333333333333333333333333333333333333 +5193 3333333333333333333333333333333333333333333333333333333 +5194 3333333333333333333333333333333333333333333333333333333 +5195 3333333333333333333333333333333333333333333333333333333 +5196 3333333333333333333333333333333333333333333333333333333 +5197 3333333333333333333333333333333333333333333333333333333 +5198 3333333333333333333333333333333333333333333333333333333 +5199 3333333333333333333333333333333333333333333333333333333 +5200 3333333333333333333333333333333333333333333333333333333 +5201 3333333333333333333333333333333333333333333333333333333 +5202 3333333333333333333333333333333333333333333333333333333 +5203 3333333333333333333333333333333333333333333333333333333 +5204 3333333333333333333333333333333333333333333333333333333 +5205 3333333333333333333333333333333333333333333333333333333 +5206 3333333333333333333333333333333333333333333333333333333 +5207 3333333333333333333333333333333333333333333333333333333 +5208 3333333333333333333333333333333333333333333333333333333 +5209 3333333333333333333333333333333333333333333333333333333 +5210 3333333333333333333333333333333333333333333333333333333 +5211 3333333333333333333333333333333333333333333333333333333 +5212 3333333333333333333333333333333333333333333333333333333 +5213 3333333333333333333333333333333333333333333333333333333 +5214 3333333333333333333333333333333333333333333333333333333 +5215 3333333333333333333333333333333333333333333333333333333 +5216 3333333333333333333333333333333333333333333333333333333 +5217 3333333333333333333333333333333333333333333333333333333 +5218 3333333333333333333333333333333333333333333333333333333 +5219 3333333333333333333333333333333333333333333333333333333 +5220 3333333333333333333333333333333333333333333333333333333 +5221 3333333333333333333333333333333333333333333333333333333 +5222 3333333333333333333333333333333333333333333333333333333 +5223 3333333333333333333333333333333333333333333333333333333 +5224 3333333333333333333333333333333333333333333333333333333 +5225 3333333333333333333333333333333333333333333333333333333 +5226 3333333333333333333333333333333333333333333333333333333 +5227 3333333333333333333333333333333333333333333333333333333 +5228 3333333333333333333333333333333333333333333333333333333 +5229 3333333333333333333333333333333333333333333333333333333 +5230 3333333333333333333333333333333333333333333333333333333 +5231 3333333333333333333333333333333333333333333333333333333 +5232 3333333333333333333333333333333333333333333333333333333 +5233 3333333333333333333333333333333333333333333333333333333 +5234 3333333333333333333333333333333333333333333333333333333 +5235 3333333333333333333333333333333333333333333333333333333 +5236 3333333333333333333333333333333333333333333333333333333 +5237 3333333333333333333333333333333333333333333333333333333 +5238 3333333333333333333333333333333333333333333333333333333 +5239 3333333333333333333333333333333333333333333333333333333 +5240 3333333333333333333333333333333333333333333333333333333 +5241 3333333333333333333333333333333333333333333333333333333 +5242 3333333333333333333333333333333333333333333333333333333 +5243 3333333333333333333333333333333333333333333333333333333 +5244 3333333333333333333333333333333333333333333333333333333 +5245 3333333333333333333333333333333333333333333333333333333 +5246 3333333333333333333333333333333333333333333333333333333 +5247 3333333333333333333333333333333333333333333333333333333 +5248 3333333333333333333333333333333333333333333333333333333 +5249 3333333333333333333333333333333333333333333333333333333 +5250 3333333333333333333333333333333333333333333333333333333 +5251 3333333333333333333333333333333333333333333333333333333 +5252 3333333333333333333333333333333333333333333333333333333 +5253 3333333333333333333333333333333333333333333333333333333 +5254 3333333333333333333333333333333333333333333333333333333 +5255 3333333333333333333333333333333333333333333333333333333 +5256 3333333333333333333333333333333333333333333333333333333 +5257 3333333333333333333333333333333333333333333333333333333 +5258 3333333333333333333333333333333333333333333333333333333 +5259 3333333333333333333333333333333333333333333333333333333 +5260 3333333333333333333333333333333333333333333333333333333 +5261 3333333333333333333333333333333333333333333333333333333 +5262 3333333333333333333333333333333333333333333333333333333 +5263 3333333333333333333333333333333333333333333333333333333 +5264 3333333333333333333333333333333333333333333333333333333 +5265 3333333333333333333333333333333333333333333333333333333 +5266 3333333333333333333333333333333333333333333333333333333 +5267 3333333333333333333333333333333333333333333333333333333 +5268 3333333333333333333333333333333333333333333333333333333 +5269 3333333333333333333333333333333333333333333333333333333 +5270 3333333333333333333333333333333333333333333333333333333 +5271 3333333333333333333333333333333333333333333333333333333 +5272 3333333333333333333333333333333333333333333333333333333 +5273 3333333333333333333333333333333333333333333333333333333 +5274 3333333333333333333333333333333333333333333333333333333 +5275 3333333333333333333333333333333333333333333333333333333 +5276 3333333333333333333333333333333333333333333333333333333 +5277 3333333333333333333333333333333333333333333333333333333 +5278 3333333333333333333333333333333333333333333333333333333 +5279 3333333333333333333333333333333333333333333333333333333 +5280 3333333333333333333333333333333333333333333333333333333 +5281 3333333333333333333333333333333333333333333333333333333 +5282 3333333333333333333333333333333333333333333333333333333 +5283 3333333333333333333333333333333333333333333333333333333 +5284 3333333333333333333333333333333333333333333333333333333 +5285 3333333333333333333333333333333333333333333333333333333 +5286 3333333333333333333333333333333333333333333333333333333 +5287 3333333333333333333333333333333333333333333333333333333 +5288 3333333333333333333333333333333333333333333333333333333 +5289 3333333333333333333333333333333333333333333333333333333 +5290 3333333333333333333333333333333333333333333333333333333 +5291 3333333333333333333333333333333333333333333333333333333 +5292 3333333333333333333333333333333333333333333333333333333 +5293 3333333333333333333333333333333333333333333333333333333 +5294 3333333333333333333333333333333333333333333333333333333 +5295 3333333333333333333333333333333333333333333333333333333 +5296 3333333333333333333333333333333333333333333333333333333 +5297 3333333333333333333333333333333333333333333333333333333 +5298 3333333333333333333333333333333333333333333333333333333 +5299 3333333333333333333333333333333333333333333333333333333 +5300 3333333333333333333333333333333333333333333333333333333 +5301 3333333333333333333333333333333333333333333333333333333 +5302 3333333333333333333333333333333333333333333333333333333 +5303 3333333333333333333333333333333333333333333333333333333 +5304 3333333333333333333333333333333333333333333333333333333 +5305 3333333333333333333333333333333333333333333333333333333 +5306 3333333333333333333333333333333333333333333333333333333 +5307 3333333333333333333333333333333333333333333333333333333 +5308 3333333333333333333333333333333333333333333333333333333 +5309 3333333333333333333333333333333333333333333333333333333 +5310 3333333333333333333333333333333333333333333333333333333 +5311 3333333333333333333333333333333333333333333333333333333 +5312 3333333333333333333333333333333333333333333333333333333 +5313 3333333333333333333333333333333333333333333333333333333 +5314 3333333333333333333333333333333333333333333333333333333 +5315 3333333333333333333333333333333333333333333333333333333 +5316 3333333333333333333333333333333333333333333333333333333 +5317 3333333333333333333333333333333333333333333333333333333 +5318 3333333333333333333333333333333333333333333333333333333 +5319 3333333333333333333333333333333333333333333333333333333 +5320 3333333333333333333333333333333333333333333333333333333 +5321 3333333333333333333333333333333333333333333333333333333 +5322 3333333333333333333333333333333333333333333333333333333 +5323 3333333333333333333333333333333333333333333333333333333 +5324 3333333333333333333333333333333333333333333333333333333 +5325 3333333333333333333333333333333333333333333333333333333 +5326 3333333333333333333333333333333333333333333333333333333 +5327 3333333333333333333333333333333333333333333333333333333 +5328 3333333333333333333333333333333333333333333333333333333 +5329 3333333333333333333333333333333333333333333333333333333 +5330 3333333333333333333333333333333333333333333333333333333 +5331 3333333333333333333333333333333333333333333333333333333 +5332 3333333333333333333333333333333333333333333333333333333 +5333 3333333333333333333333333333333333333333333333333333333 +5334 3333333333333333333333333333333333333333333333333333333 +5335 3333333333333333333333333333333333333333333333333333333 +5336 3333333333333333333333333333333333333333333333333333333 +5337 3333333333333333333333333333333333333333333333333333333 +5338 3333333333333333333333333333333333333333333333333333333 +5339 3333333333333333333333333333333333333333333333333333333 +5340 3333333333333333333333333333333333333333333333333333333 +5341 3333333333333333333333333333333333333333333333333333333 +5342 3333333333333333333333333333333333333333333333333333333 +5343 3333333333333333333333333333333333333333333333333333333 +5344 3333333333333333333333333333333333333333333333333333333 +5345 3333333333333333333333333333333333333333333333333333333 +5346 3333333333333333333333333333333333333333333333333333333 +5347 3333333333333333333333333333333333333333333333333333333 +5348 3333333333333333333333333333333333333333333333333333333 +5349 3333333333333333333333333333333333333333333333333333333 +5350 3333333333333333333333333333333333333333333333333333333 +5351 3333333333333333333333333333333333333333333333333333333 +5352 3333333333333333333333333333333333333333333333333333333 +5353 3333333333333333333333333333333333333333333333333333333 +5354 3333333333333333333333333333333333333333333333333333333 +5355 3333333333333333333333333333333333333333333333333333333 +5356 3333333333333333333333333333333333333333333333333333333 +5357 3333333333333333333333333333333333333333333333333333333 +5358 3333333333333333333333333333333333333333333333333333333 +5359 3333333333333333333333333333333333333333333333333333333 +5360 3333333333333333333333333333333333333333333333333333333 +5361 3333333333333333333333333333333333333333333333333333333 +5362 3333333333333333333333333333333333333333333333333333333 +5363 3333333333333333333333333333333333333333333333333333333 +5364 3333333333333333333333333333333333333333333333333333333 +5365 3333333333333333333333333333333333333333333333333333333 +5366 3333333333333333333333333333333333333333333333333333333 +5367 3333333333333333333333333333333333333333333333333333333 +5368 3333333333333333333333333333333333333333333333333333333 +5369 3333333333333333333333333333333333333333333333333333333 +5370 3333333333333333333333333333333333333333333333333333333 +5371 3333333333333333333333333333333333333333333333333333333 +5372 3333333333333333333333333333333333333333333333333333333 +5373 3333333333333333333333333333333333333333333333333333333 +5374 3333333333333333333333333333333333333333333333333333333 +5375 3333333333333333333333333333333333333333333333333333333 +5376 3333333333333333333333333333333333333333333333333333333 +5377 3333333333333333333333333333333333333333333333333333333 +5378 3333333333333333333333333333333333333333333333333333333 +5379 3333333333333333333333333333333333333333333333333333333 +5380 3333333333333333333333333333333333333333333333333333333 +5381 3333333333333333333333333333333333333333333333333333333 +5382 3333333333333333333333333333333333333333333333333333333 +5383 3333333333333333333333333333333333333333333333333333333 +5384 3333333333333333333333333333333333333333333333333333333 +5385 3333333333333333333333333333333333333333333333333333333 +5386 3333333333333333333333333333333333333333333333333333333 +5387 3333333333333333333333333333333333333333333333333333333 +5388 3333333333333333333333333333333333333333333333333333333 +5389 3333333333333333333333333333333333333333333333333333333 +5390 3333333333333333333333333333333333333333333333333333333 +5391 3333333333333333333333333333333333333333333333333333333 +5392 3333333333333333333333333333333333333333333333333333333 +5393 3333333333333333333333333333333333333333333333333333333 +5394 3333333333333333333333333333333333333333333333333333333 +5395 3333333333333333333333333333333333333333333333333333333 +5396 3333333333333333333333333333333333333333333333333333333 +5397 3333333333333333333333333333333333333333333333333333333 +5398 3333333333333333333333333333333333333333333333333333333 +5399 3333333333333333333333333333333333333333333333333333333 +5400 3333333333333333333333333333333333333333333333333333333 +5401 3333333333333333333333333333333333333333333333333333333 +5402 3333333333333333333333333333333333333333333333333333333 +5403 3333333333333333333333333333333333333333333333333333333 +5404 3333333333333333333333333333333333333333333333333333333 +5405 3333333333333333333333333333333333333333333333333333333 +5406 3333333333333333333333333333333333333333333333333333333 +5407 3333333333333333333333333333333333333333333333333333333 +5408 3333333333333333333333333333333333333333333333333333333 +5409 3333333333333333333333333333333333333333333333333333333 +5410 3333333333333333333333333333333333333333333333333333333 +5411 3333333333333333333333333333333333333333333333333333333 +5412 3333333333333333333333333333333333333333333333333333333 +5413 3333333333333333333333333333333333333333333333333333333 +5414 3333333333333333333333333333333333333333333333333333333 +5415 3333333333333333333333333333333333333333333333333333333 +5416 3333333333333333333333333333333333333333333333333333333 +5417 3333333333333333333333333333333333333333333333333333333 +5418 3333333333333333333333333333333333333333333333333333333 +5419 3333333333333333333333333333333333333333333333333333333 +5420 3333333333333333333333333333333333333333333333333333333 +5421 3333333333333333333333333333333333333333333333333333333 +5422 3333333333333333333333333333333333333333333333333333333 +5423 3333333333333333333333333333333333333333333333333333333 +5424 3333333333333333333333333333333333333333333333333333333 +5425 3333333333333333333333333333333333333333333333333333333 +5426 3333333333333333333333333333333333333333333333333333333 +5427 3333333333333333333333333333333333333333333333333333333 +5428 3333333333333333333333333333333333333333333333333333333 +5429 3333333333333333333333333333333333333333333333333333333 +5430 3333333333333333333333333333333333333333333333333333333 +5431 3333333333333333333333333333333333333333333333333333333 +5432 3333333333333333333333333333333333333333333333333333333 +5433 3333333333333333333333333333333333333333333333333333333 +5434 3333333333333333333333333333333333333333333333333333333 +5435 3333333333333333333333333333333333333333333333333333333 +5436 3333333333333333333333333333333333333333333333333333333 +5437 3333333333333333333333333333333333333333333333333333333 +5438 3333333333333333333333333333333333333333333333333333333 +5439 3333333333333333333333333333333333333333333333333333333 +5440 3333333333333333333333333333333333333333333333333333333 +5441 3333333333333333333333333333333333333333333333333333333 +5442 3333333333333333333333333333333333333333333333333333333 +5443 3333333333333333333333333333333333333333333333333333333 +5444 3333333333333333333333333333333333333333333333333333333 +5445 3333333333333333333333333333333333333333333333333333333 +5446 3333333333333333333333333333333333333333333333333333333 +5447 3333333333333333333333333333333333333333333333333333333 +5448 3333333333333333333333333333333333333333333333333333333 +5449 3333333333333333333333333333333333333333333333333333333 +5450 3333333333333333333333333333333333333333333333333333333 +5451 3333333333333333333333333333333333333333333333333333333 +5452 3333333333333333333333333333333333333333333333333333333 +5453 3333333333333333333333333333333333333333333333333333333 +5454 3333333333333333333333333333333333333333333333333333333 +5455 3333333333333333333333333333333333333333333333333333333 +5456 3333333333333333333333333333333333333333333333333333333 +5457 3333333333333333333333333333333333333333333333333333333 +5458 3333333333333333333333333333333333333333333333333333333 +5459 3333333333333333333333333333333333333333333333333333333 +5460 3333333333333333333333333333333333333333333333333333333 +5461 3333333333333333333333333333333333333333333333333333333 +5462 3333333333333333333333333333333333333333333333333333333 +5463 3333333333333333333333333333333333333333333333333333333 +5464 3333333333333333333333333333333333333333333333333333333 +5465 3333333333333333333333333333333333333333333333333333333 +5466 3333333333333333333333333333333333333333333333333333333 +5467 3333333333333333333333333333333333333333333333333333333 +5468 3333333333333333333333333333333333333333333333333333333 +5469 3333333333333333333333333333333333333333333333333333333 +5470 3333333333333333333333333333333333333333333333333333333 +5471 3333333333333333333333333333333333333333333333333333333 +5472 3333333333333333333333333333333333333333333333333333333 +5473 3333333333333333333333333333333333333333333333333333333 +5474 3333333333333333333333333333333333333333333333333333333 +5475 3333333333333333333333333333333333333333333333333333333 +5476 3333333333333333333333333333333333333333333333333333333 +5477 3333333333333333333333333333333333333333333333333333333 +5478 3333333333333333333333333333333333333333333333333333333 +5479 3333333333333333333333333333333333333333333333333333333 +5480 3333333333333333333333333333333333333333333333333333333 +5481 3333333333333333333333333333333333333333333333333333333 +5482 3333333333333333333333333333333333333333333333333333333 +5483 3333333333333333333333333333333333333333333333333333333 +5484 3333333333333333333333333333333333333333333333333333333 +5485 3333333333333333333333333333333333333333333333333333333 +5486 3333333333333333333333333333333333333333333333333333333 +5487 3333333333333333333333333333333333333333333333333333333 +5488 3333333333333333333333333333333333333333333333333333333 +5489 3333333333333333333333333333333333333333333333333333333 +5490 3333333333333333333333333333333333333333333333333333333 +5491 3333333333333333333333333333333333333333333333333333333 +5492 3333333333333333333333333333333333333333333333333333333 +5493 3333333333333333333333333333333333333333333333333333333 +5494 3333333333333333333333333333333333333333333333333333333 +5495 3333333333333333333333333333333333333333333333333333333 +5496 3333333333333333333333333333333333333333333333333333333 +5497 3333333333333333333333333333333333333333333333333333333 +5498 3333333333333333333333333333333333333333333333333333333 +5499 3333333333333333333333333333333333333333333333333333333 +5500 3333333333333333333333333333333333333333333333333333333 +5501 3333333333333333333333333333333333333333333333333333333 +5502 3333333333333333333333333333333333333333333333333333333 +5503 3333333333333333333333333333333333333333333333333333333 +5504 3333333333333333333333333333333333333333333333333333333 +5505 3333333333333333333333333333333333333333333333333333333 +5506 3333333333333333333333333333333333333333333333333333333 +5507 3333333333333333333333333333333333333333333333333333333 +5508 3333333333333333333333333333333333333333333333333333333 +5509 3333333333333333333333333333333333333333333333333333333 +5510 3333333333333333333333333333333333333333333333333333333 +5511 3333333333333333333333333333333333333333333333333333333 +5512 3333333333333333333333333333333333333333333333333333333 +5513 3333333333333333333333333333333333333333333333333333333 +5514 3333333333333333333333333333333333333333333333333333333 +5515 3333333333333333333333333333333333333333333333333333333 +5516 3333333333333333333333333333333333333333333333333333333 +5517 3333333333333333333333333333333333333333333333333333333 +5518 3333333333333333333333333333333333333333333333333333333 +5519 3333333333333333333333333333333333333333333333333333333 +5520 3333333333333333333333333333333333333333333333333333333 +5521 3333333333333333333333333333333333333333333333333333333 +5522 3333333333333333333333333333333333333333333333333333333 +5523 3333333333333333333333333333333333333333333333333333333 +5524 3333333333333333333333333333333333333333333333333333333 +5525 3333333333333333333333333333333333333333333333333333333 +5526 3333333333333333333333333333333333333333333333333333333 +5527 3333333333333333333333333333333333333333333333333333333 +5528 3333333333333333333333333333333333333333333333333333333 +5529 3333333333333333333333333333333333333333333333333333333 +5530 3333333333333333333333333333333333333333333333333333333 +5531 3333333333333333333333333333333333333333333333333333333 +5532 3333333333333333333333333333333333333333333333333333333 +5533 3333333333333333333333333333333333333333333333333333333 +5534 3333333333333333333333333333333333333333333333333333333 +5535 3333333333333333333333333333333333333333333333333333333 +5536 3333333333333333333333333333333333333333333333333333333 +5537 3333333333333333333333333333333333333333333333333333333 +5538 3333333333333333333333333333333333333333333333333333333 +5539 3333333333333333333333333333333333333333333333333333333 +5540 3333333333333333333333333333333333333333333333333333333 +5541 3333333333333333333333333333333333333333333333333333333 +5542 3333333333333333333333333333333333333333333333333333333 +5543 3333333333333333333333333333333333333333333333333333333 +5544 3333333333333333333333333333333333333333333333333333333 +5545 3333333333333333333333333333333333333333333333333333333 +5546 3333333333333333333333333333333333333333333333333333333 +5547 3333333333333333333333333333333333333333333333333333333 +5548 3333333333333333333333333333333333333333333333333333333 +5549 3333333333333333333333333333333333333333333333333333333 +5550 3333333333333333333333333333333333333333333333333333333 +5551 3333333333333333333333333333333333333333333333333333333 +5552 3333333333333333333333333333333333333333333333333333333 +5553 3333333333333333333333333333333333333333333333333333333 +5554 3333333333333333333333333333333333333333333333333333333 +5555 3333333333333333333333333333333333333333333333333333333 +5556 3333333333333333333333333333333333333333333333333333333 +5557 3333333333333333333333333333333333333333333333333333333 +5558 3333333333333333333333333333333333333333333333333333333 +5559 3333333333333333333333333333333333333333333333333333333 +5560 3333333333333333333333333333333333333333333333333333333 +5561 3333333333333333333333333333333333333333333333333333333 +5562 3333333333333333333333333333333333333333333333333333333 +5563 3333333333333333333333333333333333333333333333333333333 +5564 3333333333333333333333333333333333333333333333333333333 +5565 3333333333333333333333333333333333333333333333333333333 +5566 3333333333333333333333333333333333333333333333333333333 +5567 3333333333333333333333333333333333333333333333333333333 +5568 3333333333333333333333333333333333333333333333333333333 +5569 3333333333333333333333333333333333333333333333333333333 +5570 3333333333333333333333333333333333333333333333333333333 +5571 3333333333333333333333333333333333333333333333333333333 +5572 3333333333333333333333333333333333333333333333333333333 +5573 3333333333333333333333333333333333333333333333333333333 +5574 3333333333333333333333333333333333333333333333333333333 +5575 3333333333333333333333333333333333333333333333333333333 +5576 3333333333333333333333333333333333333333333333333333333 +5577 3333333333333333333333333333333333333333333333333333333 +5578 3333333333333333333333333333333333333333333333333333333 +5579 3333333333333333333333333333333333333333333333333333333 +5580 3333333333333333333333333333333333333333333333333333333 +5581 3333333333333333333333333333333333333333333333333333333 +5582 3333333333333333333333333333333333333333333333333333333 +5583 3333333333333333333333333333333333333333333333333333333 +5584 3333333333333333333333333333333333333333333333333333333 +5585 3333333333333333333333333333333333333333333333333333333 +5586 3333333333333333333333333333333333333333333333333333333 +5587 3333333333333333333333333333333333333333333333333333333 +5588 3333333333333333333333333333333333333333333333333333333 +5589 3333333333333333333333333333333333333333333333333333333 +5590 3333333333333333333333333333333333333333333333333333333 +5591 3333333333333333333333333333333333333333333333333333333 +5592 3333333333333333333333333333333333333333333333333333333 +5593 3333333333333333333333333333333333333333333333333333333 +5594 3333333333333333333333333333333333333333333333333333333 +5595 3333333333333333333333333333333333333333333333333333333 +5596 3333333333333333333333333333333333333333333333333333333 +5597 3333333333333333333333333333333333333333333333333333333 +5598 3333333333333333333333333333333333333333333333333333333 +5599 3333333333333333333333333333333333333333333333333333333 +5600 3333333333333333333333333333333333333333333333333333333 +5601 3333333333333333333333333333333333333333333333333333333 +5602 3333333333333333333333333333333333333333333333333333333 +5603 3333333333333333333333333333333333333333333333333333333 +5604 3333333333333333333333333333333333333333333333333333333 +5605 3333333333333333333333333333333333333333333333333333333 +5606 3333333333333333333333333333333333333333333333333333333 +5607 3333333333333333333333333333333333333333333333333333333 +5608 3333333333333333333333333333333333333333333333333333333 +5609 3333333333333333333333333333333333333333333333333333333 +5610 3333333333333333333333333333333333333333333333333333333 +5611 3333333333333333333333333333333333333333333333333333333 +5612 3333333333333333333333333333333333333333333333333333333 +5613 3333333333333333333333333333333333333333333333333333333 +5614 3333333333333333333333333333333333333333333333333333333 +5615 3333333333333333333333333333333333333333333333333333333 +5616 3333333333333333333333333333333333333333333333333333333 +5617 3333333333333333333333333333333333333333333333333333333 +5618 3333333333333333333333333333333333333333333333333333333 +5619 3333333333333333333333333333333333333333333333333333333 +5620 3333333333333333333333333333333333333333333333333333333 +5621 3333333333333333333333333333333333333333333333333333333 +5622 3333333333333333333333333333333333333333333333333333333 +5623 3333333333333333333333333333333333333333333333333333333 +5624 3333333333333333333333333333333333333333333333333333333 +5625 3333333333333333333333333333333333333333333333333333333 +5626 3333333333333333333333333333333333333333333333333333333 +5627 3333333333333333333333333333333333333333333333333333333 +5628 3333333333333333333333333333333333333333333333333333333 +5629 3333333333333333333333333333333333333333333333333333333 +5630 3333333333333333333333333333333333333333333333333333333 +5631 3333333333333333333333333333333333333333333333333333333 +5632 3333333333333333333333333333333333333333333333333333333 +5633 3333333333333333333333333333333333333333333333333333333 +5634 3333333333333333333333333333333333333333333333333333333 +5635 3333333333333333333333333333333333333333333333333333333 +5636 3333333333333333333333333333333333333333333333333333333 +5637 3333333333333333333333333333333333333333333333333333333 +5638 3333333333333333333333333333333333333333333333333333333 +5639 3333333333333333333333333333333333333333333333333333333 +5640 3333333333333333333333333333333333333333333333333333333 +5641 3333333333333333333333333333333333333333333333333333333 +5642 3333333333333333333333333333333333333333333333333333333 +5643 3333333333333333333333333333333333333333333333333333333 +5644 3333333333333333333333333333333333333333333333333333333 +5645 3333333333333333333333333333333333333333333333333333333 +5646 3333333333333333333333333333333333333333333333333333333 +5647 3333333333333333333333333333333333333333333333333333333 +5648 3333333333333333333333333333333333333333333333333333333 +5649 3333333333333333333333333333333333333333333333333333333 +5650 3333333333333333333333333333333333333333333333333333333 +5651 3333333333333333333333333333333333333333333333333333333 +5652 3333333333333333333333333333333333333333333333333333333 +5653 3333333333333333333333333333333333333333333333333333333 +5654 3333333333333333333333333333333333333333333333333333333 +5655 3333333333333333333333333333333333333333333333333333333 +5656 3333333333333333333333333333333333333333333333333333333 +5657 3333333333333333333333333333333333333333333333333333333 +5658 3333333333333333333333333333333333333333333333333333333 +5659 3333333333333333333333333333333333333333333333333333333 +5660 3333333333333333333333333333333333333333333333333333333 +5661 3333333333333333333333333333333333333333333333333333333 +5662 3333333333333333333333333333333333333333333333333333333 +5663 3333333333333333333333333333333333333333333333333333333 +5664 3333333333333333333333333333333333333333333333333333333 +5665 3333333333333333333333333333333333333333333333333333333 +5666 3333333333333333333333333333333333333333333333333333333 +5667 3333333333333333333333333333333333333333333333333333333 +5668 3333333333333333333333333333333333333333333333333333333 +5669 3333333333333333333333333333333333333333333333333333333 +5670 3333333333333333333333333333333333333333333333333333333 +5671 3333333333333333333333333333333333333333333333333333333 +5672 3333333333333333333333333333333333333333333333333333333 +5673 3333333333333333333333333333333333333333333333333333333 +5674 3333333333333333333333333333333333333333333333333333333 +5675 3333333333333333333333333333333333333333333333333333333 +5676 3333333333333333333333333333333333333333333333333333333 +5677 3333333333333333333333333333333333333333333333333333333 +5678 3333333333333333333333333333333333333333333333333333333 +5679 3333333333333333333333333333333333333333333333333333333 +5680 3333333333333333333333333333333333333333333333333333333 +5681 3333333333333333333333333333333333333333333333333333333 +5682 3333333333333333333333333333333333333333333333333333333 +5683 3333333333333333333333333333333333333333333333333333333 +5684 3333333333333333333333333333333333333333333333333333333 +5685 3333333333333333333333333333333333333333333333333333333 +5686 3333333333333333333333333333333333333333333333333333333 +5687 3333333333333333333333333333333333333333333333333333333 +5688 3333333333333333333333333333333333333333333333333333333 +5689 3333333333333333333333333333333333333333333333333333333 +5690 3333333333333333333333333333333333333333333333333333333 +5691 3333333333333333333333333333333333333333333333333333333 +5692 3333333333333333333333333333333333333333333333333333333 +5693 3333333333333333333333333333333333333333333333333333333 +5694 3333333333333333333333333333333333333333333333333333333 +5695 3333333333333333333333333333333333333333333333333333333 +5696 3333333333333333333333333333333333333333333333333333333 +5697 3333333333333333333333333333333333333333333333333333333 +5698 3333333333333333333333333333333333333333333333333333333 +5699 3333333333333333333333333333333333333333333333333333333 +5700 3333333333333333333333333333333333333333333333333333333 +5701 3333333333333333333333333333333333333333333333333333333 +5702 3333333333333333333333333333333333333333333333333333333 +5703 3333333333333333333333333333333333333333333333333333333 +5704 3333333333333333333333333333333333333333333333333333333 +5705 3333333333333333333333333333333333333333333333333333333 +5706 3333333333333333333333333333333333333333333333333333333 +5707 3333333333333333333333333333333333333333333333333333333 +5708 3333333333333333333333333333333333333333333333333333333 +5709 3333333333333333333333333333333333333333333333333333333 +5710 3333333333333333333333333333333333333333333333333333333 +5711 3333333333333333333333333333333333333333333333333333333 +5712 3333333333333333333333333333333333333333333333333333333 +5713 3333333333333333333333333333333333333333333333333333333 +5714 3333333333333333333333333333333333333333333333333333333 +5715 3333333333333333333333333333333333333333333333333333333 +5716 3333333333333333333333333333333333333333333333333333333 +5717 3333333333333333333333333333333333333333333333333333333 +5718 3333333333333333333333333333333333333333333333333333333 +5719 3333333333333333333333333333333333333333333333333333333 +5720 3333333333333333333333333333333333333333333333333333333 +5721 3333333333333333333333333333333333333333333333333333333 +5722 3333333333333333333333333333333333333333333333333333333 +5723 3333333333333333333333333333333333333333333333333333333 +5724 3333333333333333333333333333333333333333333333333333333 +5725 3333333333333333333333333333333333333333333333333333333 +5726 3333333333333333333333333333333333333333333333333333333 +5727 3333333333333333333333333333333333333333333333333333333 +5728 3333333333333333333333333333333333333333333333333333333 +5729 3333333333333333333333333333333333333333333333333333333 +5730 3333333333333333333333333333333333333333333333333333333 +5731 3333333333333333333333333333333333333333333333333333333 +5732 3333333333333333333333333333333333333333333333333333333 +5733 3333333333333333333333333333333333333333333333333333333 +5734 3333333333333333333333333333333333333333333333333333333 +5735 3333333333333333333333333333333333333333333333333333333 +5736 3333333333333333333333333333333333333333333333333333333 +5737 3333333333333333333333333333333333333333333333333333333 +5738 3333333333333333333333333333333333333333333333333333333 +5739 3333333333333333333333333333333333333333333333333333333 +5740 3333333333333333333333333333333333333333333333333333333 +5741 3333333333333333333333333333333333333333333333333333333 +5742 3333333333333333333333333333333333333333333333333333333 +5743 3333333333333333333333333333333333333333333333333333333 +5744 3333333333333333333333333333333333333333333333333333333 +5745 3333333333333333333333333333333333333333333333333333333 +5746 3333333333333333333333333333333333333333333333333333333 +5747 3333333333333333333333333333333333333333333333333333333 +5748 3333333333333333333333333333333333333333333333333333333 +5749 3333333333333333333333333333333333333333333333333333333 +5750 3333333333333333333333333333333333333333333333333333333 +5751 3333333333333333333333333333333333333333333333333333333 +5752 3333333333333333333333333333333333333333333333333333333 +5753 3333333333333333333333333333333333333333333333333333333 +5754 3333333333333333333333333333333333333333333333333333333 +5755 3333333333333333333333333333333333333333333333333333333 +5756 3333333333333333333333333333333333333333333333333333333 +5757 3333333333333333333333333333333333333333333333333333333 +5758 3333333333333333333333333333333333333333333333333333333 +5759 3333333333333333333333333333333333333333333333333333333 +5760 3333333333333333333333333333333333333333333333333333333 +5761 3333333333333333333333333333333333333333333333333333333 +5762 3333333333333333333333333333333333333333333333333333333 +5763 3333333333333333333333333333333333333333333333333333333 +5764 3333333333333333333333333333333333333333333333333333333 +5765 3333333333333333333333333333333333333333333333333333333 +5766 3333333333333333333333333333333333333333333333333333333 +5767 3333333333333333333333333333333333333333333333333333333 +5768 3333333333333333333333333333333333333333333333333333333 +5769 3333333333333333333333333333333333333333333333333333333 +5770 3333333333333333333333333333333333333333333333333333333 +5771 3333333333333333333333333333333333333333333333333333333 +5772 3333333333333333333333333333333333333333333333333333333 +5773 3333333333333333333333333333333333333333333333333333333 +5774 3333333333333333333333333333333333333333333333333333333 +5775 3333333333333333333333333333333333333333333333333333333 +5776 3333333333333333333333333333333333333333333333333333333 +5777 3333333333333333333333333333333333333333333333333333333 +5778 3333333333333333333333333333333333333333333333333333333 +5779 3333333333333333333333333333333333333333333333333333333 +5780 3333333333333333333333333333333333333333333333333333333 +5781 3333333333333333333333333333333333333333333333333333333 +5782 3333333333333333333333333333333333333333333333333333333 +5783 3333333333333333333333333333333333333333333333333333333 +5784 3333333333333333333333333333333333333333333333333333333 +5785 3333333333333333333333333333333333333333333333333333333 +5786 3333333333333333333333333333333333333333333333333333333 +5787 3333333333333333333333333333333333333333333333333333333 +5788 3333333333333333333333333333333333333333333333333333333 +5789 3333333333333333333333333333333333333333333333333333333 +5790 3333333333333333333333333333333333333333333333333333333 +5791 3333333333333333333333333333333333333333333333333333333 +5792 3333333333333333333333333333333333333333333333333333333 +5793 3333333333333333333333333333333333333333333333333333333 +5794 3333333333333333333333333333333333333333333333333333333 +5795 3333333333333333333333333333333333333333333333333333333 +5796 3333333333333333333333333333333333333333333333333333333 +5797 3333333333333333333333333333333333333333333333333333333 +5798 3333333333333333333333333333333333333333333333333333333 +5799 3333333333333333333333333333333333333333333333333333333 +5800 3333333333333333333333333333333333333333333333333333333 +5801 3333333333333333333333333333333333333333333333333333333 +5802 3333333333333333333333333333333333333333333333333333333 +5803 3333333333333333333333333333333333333333333333333333333 +5804 3333333333333333333333333333333333333333333333333333333 +5805 3333333333333333333333333333333333333333333333333333333 +5806 3333333333333333333333333333333333333333333333333333333 +5807 3333333333333333333333333333333333333333333333333333333 +5808 3333333333333333333333333333333333333333333333333333333 +5809 3333333333333333333333333333333333333333333333333333333 +5810 3333333333333333333333333333333333333333333333333333333 +5811 3333333333333333333333333333333333333333333333333333333 +5812 3333333333333333333333333333333333333333333333333333333 +5813 3333333333333333333333333333333333333333333333333333333 +5814 3333333333333333333333333333333333333333333333333333333 +5815 3333333333333333333333333333333333333333333333333333333 +5816 3333333333333333333333333333333333333333333333333333333 +5817 3333333333333333333333333333333333333333333333333333333 +5818 3333333333333333333333333333333333333333333333333333333 +5819 3333333333333333333333333333333333333333333333333333333 +5820 3333333333333333333333333333333333333333333333333333333 +5821 3333333333333333333333333333333333333333333333333333333 +5822 3333333333333333333333333333333333333333333333333333333 +5823 3333333333333333333333333333333333333333333333333333333 +5824 3333333333333333333333333333333333333333333333333333333 +5825 3333333333333333333333333333333333333333333333333333333 +5826 3333333333333333333333333333333333333333333333333333333 +5827 3333333333333333333333333333333333333333333333333333333 +5828 3333333333333333333333333333333333333333333333333333333 +5829 3333333333333333333333333333333333333333333333333333333 +5830 3333333333333333333333333333333333333333333333333333333 +5831 3333333333333333333333333333333333333333333333333333333 +5832 3333333333333333333333333333333333333333333333333333333 +5833 3333333333333333333333333333333333333333333333333333333 +5834 3333333333333333333333333333333333333333333333333333333 +5835 3333333333333333333333333333333333333333333333333333333 +5836 3333333333333333333333333333333333333333333333333333333 +5837 3333333333333333333333333333333333333333333333333333333 +5838 3333333333333333333333333333333333333333333333333333333 +5839 3333333333333333333333333333333333333333333333333333333 +5840 3333333333333333333333333333333333333333333333333333333 +5841 3333333333333333333333333333333333333333333333333333333 +5842 3333333333333333333333333333333333333333333333333333333 +5843 3333333333333333333333333333333333333333333333333333333 +5844 3333333333333333333333333333333333333333333333333333333 +5845 3333333333333333333333333333333333333333333333333333333 +5846 3333333333333333333333333333333333333333333333333333333 +5847 3333333333333333333333333333333333333333333333333333333 +5848 3333333333333333333333333333333333333333333333333333333 +5849 3333333333333333333333333333333333333333333333333333333 +5850 3333333333333333333333333333333333333333333333333333333 +5851 3333333333333333333333333333333333333333333333333333333 +5852 3333333333333333333333333333333333333333333333333333333 +5853 3333333333333333333333333333333333333333333333333333333 +5854 3333333333333333333333333333333333333333333333333333333 +5855 3333333333333333333333333333333333333333333333333333333 +5856 3333333333333333333333333333333333333333333333333333333 +5857 3333333333333333333333333333333333333333333333333333333 +5858 3333333333333333333333333333333333333333333333333333333 +5859 3333333333333333333333333333333333333333333333333333333 +5860 3333333333333333333333333333333333333333333333333333333 +5861 3333333333333333333333333333333333333333333333333333333 +5862 3333333333333333333333333333333333333333333333333333333 +5863 3333333333333333333333333333333333333333333333333333333 +5864 3333333333333333333333333333333333333333333333333333333 +5865 3333333333333333333333333333333333333333333333333333333 +5866 3333333333333333333333333333333333333333333333333333333 +5867 3333333333333333333333333333333333333333333333333333333 +5868 3333333333333333333333333333333333333333333333333333333 +5869 3333333333333333333333333333333333333333333333333333333 +5870 3333333333333333333333333333333333333333333333333333333 +5871 3333333333333333333333333333333333333333333333333333333 +5872 3333333333333333333333333333333333333333333333333333333 +5873 3333333333333333333333333333333333333333333333333333333 +5874 3333333333333333333333333333333333333333333333333333333 +5875 3333333333333333333333333333333333333333333333333333333 +5876 3333333333333333333333333333333333333333333333333333333 +5877 3333333333333333333333333333333333333333333333333333333 +5878 3333333333333333333333333333333333333333333333333333333 +5879 3333333333333333333333333333333333333333333333333333333 +5880 3333333333333333333333333333333333333333333333333333333 +5881 3333333333333333333333333333333333333333333333333333333 +5882 3333333333333333333333333333333333333333333333333333333 +5883 3333333333333333333333333333333333333333333333333333333 +5884 3333333333333333333333333333333333333333333333333333333 +5885 3333333333333333333333333333333333333333333333333333333 +5886 3333333333333333333333333333333333333333333333333333333 +5887 3333333333333333333333333333333333333333333333333333333 +5888 3333333333333333333333333333333333333333333333333333333 +5889 3333333333333333333333333333333333333333333333333333333 +5890 3333333333333333333333333333333333333333333333333333333 +5891 3333333333333333333333333333333333333333333333333333333 +5892 3333333333333333333333333333333333333333333333333333333 +5893 3333333333333333333333333333333333333333333333333333333 +5894 3333333333333333333333333333333333333333333333333333333 +5895 3333333333333333333333333333333333333333333333333333333 +5896 3333333333333333333333333333333333333333333333333333333 +5897 3333333333333333333333333333333333333333333333333333333 +5898 3333333333333333333333333333333333333333333333333333333 +5899 3333333333333333333333333333333333333333333333333333333 +5900 3333333333333333333333333333333333333333333333333333333 +5901 3333333333333333333333333333333333333333333333333333333 +5902 3333333333333333333333333333333333333333333333333333333 +5903 3333333333333333333333333333333333333333333333333333333 +5904 3333333333333333333333333333333333333333333333333333333 +5905 3333333333333333333333333333333333333333333333333333333 +5906 3333333333333333333333333333333333333333333333333333333 +5907 3333333333333333333333333333333333333333333333333333333 +5908 3333333333333333333333333333333333333333333333333333333 +5909 3333333333333333333333333333333333333333333333333333333 +5910 3333333333333333333333333333333333333333333333333333333 +5911 3333333333333333333333333333333333333333333333333333333 +5912 3333333333333333333333333333333333333333333333333333333 +5913 3333333333333333333333333333333333333333333333333333333 +5914 3333333333333333333333333333333333333333333333333333333 +5915 3333333333333333333333333333333333333333333333333333333 +5916 3333333333333333333333333333333333333333333333333333333 +5917 3333333333333333333333333333333333333333333333333333333 +5918 3333333333333333333333333333333333333333333333333333333 +5919 3333333333333333333333333333333333333333333333333333333 +5920 3333333333333333333333333333333333333333333333333333333 +5921 3333333333333333333333333333333333333333333333333333333 +5922 3333333333333333333333333333333333333333333333333333333 +5923 3333333333333333333333333333333333333333333333333333333 +5924 3333333333333333333333333333333333333333333333333333333 +5925 3333333333333333333333333333333333333333333333333333333 +5926 3333333333333333333333333333333333333333333333333333333 +5927 3333333333333333333333333333333333333333333333333333333 +5928 3333333333333333333333333333333333333333333333333333333 +5929 3333333333333333333333333333333333333333333333333333333 +5930 3333333333333333333333333333333333333333333333333333333 +5931 3333333333333333333333333333333333333333333333333333333 +5932 3333333333333333333333333333333333333333333333333333333 +5933 3333333333333333333333333333333333333333333333333333333 +5934 3333333333333333333333333333333333333333333333333333333 +5935 3333333333333333333333333333333333333333333333333333333 +5936 3333333333333333333333333333333333333333333333333333333 +5937 3333333333333333333333333333333333333333333333333333333 +5938 3333333333333333333333333333333333333333333333333333333 +5939 3333333333333333333333333333333333333333333333333333333 +5940 3333333333333333333333333333333333333333333333333333333 +5941 3333333333333333333333333333333333333333333333333333333 +5942 3333333333333333333333333333333333333333333333333333333 +5943 3333333333333333333333333333333333333333333333333333333 +5944 3333333333333333333333333333333333333333333333333333333 +5945 3333333333333333333333333333333333333333333333333333333 +5946 3333333333333333333333333333333333333333333333333333333 +5947 3333333333333333333333333333333333333333333333333333333 +5948 3333333333333333333333333333333333333333333333333333333 +5949 3333333333333333333333333333333333333333333333333333333 +5950 3333333333333333333333333333333333333333333333333333333 +5951 3333333333333333333333333333333333333333333333333333333 +5952 3333333333333333333333333333333333333333333333333333333 +5953 3333333333333333333333333333333333333333333333333333333 +5954 3333333333333333333333333333333333333333333333333333333 +5955 3333333333333333333333333333333333333333333333333333333 +5956 3333333333333333333333333333333333333333333333333333333 +5957 3333333333333333333333333333333333333333333333333333333 +5958 3333333333333333333333333333333333333333333333333333333 +5959 3333333333333333333333333333333333333333333333333333333 +5960 3333333333333333333333333333333333333333333333333333333 +5961 3333333333333333333333333333333333333333333333333333333 +5962 3333333333333333333333333333333333333333333333333333333 +5963 3333333333333333333333333333333333333333333333333333333 +5964 3333333333333333333333333333333333333333333333333333333 +5965 3333333333333333333333333333333333333333333333333333333 +5966 3333333333333333333333333333333333333333333333333333333 +5967 3333333333333333333333333333333333333333333333333333333 +5968 3333333333333333333333333333333333333333333333333333333 +5969 3333333333333333333333333333333333333333333333333333333 +5970 3333333333333333333333333333333333333333333333333333333 +5971 3333333333333333333333333333333333333333333333333333333 +5972 3333333333333333333333333333333333333333333333333333333 +5973 3333333333333333333333333333333333333333333333333333333 +5974 3333333333333333333333333333333333333333333333333333333 +5975 3333333333333333333333333333333333333333333333333333333 +5976 3333333333333333333333333333333333333333333333333333333 +5977 3333333333333333333333333333333333333333333333333333333 +5978 3333333333333333333333333333333333333333333333333333333 +5979 3333333333333333333333333333333333333333333333333333333 +5980 3333333333333333333333333333333333333333333333333333333 +5981 3333333333333333333333333333333333333333333333333333333 +5982 3333333333333333333333333333333333333333333333333333333 +5983 3333333333333333333333333333333333333333333333333333333 +5984 3333333333333333333333333333333333333333333333333333333 +5985 3333333333333333333333333333333333333333333333333333333 +5986 3333333333333333333333333333333333333333333333333333333 +5987 3333333333333333333333333333333333333333333333333333333 +5988 3333333333333333333333333333333333333333333333333333333 +5989 3333333333333333333333333333333333333333333333333333333 +5990 3333333333333333333333333333333333333333333333333333333 +5991 3333333333333333333333333333333333333333333333333333333 +5992 3333333333333333333333333333333333333333333333333333333 +5993 3333333333333333333333333333333333333333333333333333333 +5994 3333333333333333333333333333333333333333333333333333333 +5995 3333333333333333333333333333333333333333333333333333333 +5996 3333333333333333333333333333333333333333333333333333333 +5997 3333333333333333333333333333333333333333333333333333333 +5998 3333333333333333333333333333333333333333333333333333333 +5999 3333333333333333333333333333333333333333333333333333333 +6000 3333333333333333333333333333333333333333333333333333333 +6001 3333333333333333333333333333333333333333333333333333333 +6002 3333333333333333333333333333333333333333333333333333333 +6003 3333333333333333333333333333333333333333333333333333333 +6004 3333333333333333333333333333333333333333333333333333333 +6005 3333333333333333333333333333333333333333333333333333333 +6006 3333333333333333333333333333333333333333333333333333333 +6007 3333333333333333333333333333333333333333333333333333333 +6008 3333333333333333333333333333333333333333333333333333333 +6009 3333333333333333333333333333333333333333333333333333333 +6010 3333333333333333333333333333333333333333333333333333333 +6011 3333333333333333333333333333333333333333333333333333333 +6012 3333333333333333333333333333333333333333333333333333333 +6013 3333333333333333333333333333333333333333333333333333333 +6014 3333333333333333333333333333333333333333333333333333333 +6015 3333333333333333333333333333333333333333333333333333333 +6016 3333333333333333333333333333333333333333333333333333333 +6017 3333333333333333333333333333333333333333333333333333333 +6018 3333333333333333333333333333333333333333333333333333333 +6019 3333333333333333333333333333333333333333333333333333333 +6020 3333333333333333333333333333333333333333333333333333333 +6021 3333333333333333333333333333333333333333333333333333333 +6022 3333333333333333333333333333333333333333333333333333333 +6023 3333333333333333333333333333333333333333333333333333333 +6024 3333333333333333333333333333333333333333333333333333333 +6025 3333333333333333333333333333333333333333333333333333333 +6026 3333333333333333333333333333333333333333333333333333333 +6027 3333333333333333333333333333333333333333333333333333333 +6028 3333333333333333333333333333333333333333333333333333333 +6029 3333333333333333333333333333333333333333333333333333333 +6030 3333333333333333333333333333333333333333333333333333333 +6031 3333333333333333333333333333333333333333333333333333333 +6032 3333333333333333333333333333333333333333333333333333333 +6033 3333333333333333333333333333333333333333333333333333333 +6034 3333333333333333333333333333333333333333333333333333333 +6035 3333333333333333333333333333333333333333333333333333333 +6036 3333333333333333333333333333333333333333333333333333333 +6037 3333333333333333333333333333333333333333333333333333333 +6038 3333333333333333333333333333333333333333333333333333333 +6039 3333333333333333333333333333333333333333333333333333333 +6040 3333333333333333333333333333333333333333333333333333333 +6041 3333333333333333333333333333333333333333333333333333333 +6042 3333333333333333333333333333333333333333333333333333333 +6043 3333333333333333333333333333333333333333333333333333333 +6044 3333333333333333333333333333333333333333333333333333333 +6045 3333333333333333333333333333333333333333333333333333333 +6046 3333333333333333333333333333333333333333333333333333333 +6047 3333333333333333333333333333333333333333333333333333333 +6048 3333333333333333333333333333333333333333333333333333333 +6049 3333333333333333333333333333333333333333333333333333333 +6050 3333333333333333333333333333333333333333333333333333333 +6051 3333333333333333333333333333333333333333333333333333333 +6052 3333333333333333333333333333333333333333333333333333333 +6053 3333333333333333333333333333333333333333333333333333333 +6054 3333333333333333333333333333333333333333333333333333333 +6055 3333333333333333333333333333333333333333333333333333333 +6056 3333333333333333333333333333333333333333333333333333333 +6057 3333333333333333333333333333333333333333333333333333333 +6058 3333333333333333333333333333333333333333333333333333333 +6059 3333333333333333333333333333333333333333333333333333333 +6060 3333333333333333333333333333333333333333333333333333333 +6061 3333333333333333333333333333333333333333333333333333333 +6062 3333333333333333333333333333333333333333333333333333333 +6063 3333333333333333333333333333333333333333333333333333333 +6064 3333333333333333333333333333333333333333333333333333333 +6065 3333333333333333333333333333333333333333333333333333333 +6066 3333333333333333333333333333333333333333333333333333333 +6067 3333333333333333333333333333333333333333333333333333333 +6068 3333333333333333333333333333333333333333333333333333333 +6069 3333333333333333333333333333333333333333333333333333333 +6070 3333333333333333333333333333333333333333333333333333333 +6071 3333333333333333333333333333333333333333333333333333333 +6072 3333333333333333333333333333333333333333333333333333333 +6073 3333333333333333333333333333333333333333333333333333333 +6074 3333333333333333333333333333333333333333333333333333333 +6075 3333333333333333333333333333333333333333333333333333333 +6076 3333333333333333333333333333333333333333333333333333333 +6077 3333333333333333333333333333333333333333333333333333333 +6078 3333333333333333333333333333333333333333333333333333333 +6079 3333333333333333333333333333333333333333333333333333333 +6080 3333333333333333333333333333333333333333333333333333333 +6081 3333333333333333333333333333333333333333333333333333333 +6082 3333333333333333333333333333333333333333333333333333333 +6083 3333333333333333333333333333333333333333333333333333333 +6084 3333333333333333333333333333333333333333333333333333333 +6085 3333333333333333333333333333333333333333333333333333333 +6086 3333333333333333333333333333333333333333333333333333333 +6087 3333333333333333333333333333333333333333333333333333333 +6088 3333333333333333333333333333333333333333333333333333333 +6089 3333333333333333333333333333333333333333333333333333333 +6090 3333333333333333333333333333333333333333333333333333333 +6091 3333333333333333333333333333333333333333333333333333333 +6092 3333333333333333333333333333333333333333333333333333333 +6093 3333333333333333333333333333333333333333333333333333333 +6094 3333333333333333333333333333333333333333333333333333333 +6095 3333333333333333333333333333333333333333333333333333333 +6096 3333333333333333333333333333333333333333333333333333333 +6097 3333333333333333333333333333333333333333333333333333333 +6098 3333333333333333333333333333333333333333333333333333333 +6099 3333333333333333333333333333333333333333333333333333333 +6100 3333333333333333333333333333333333333333333333333333333 +6101 3333333333333333333333333333333333333333333333333333333 +6102 3333333333333333333333333333333333333333333333333333333 +6103 3333333333333333333333333333333333333333333333333333333 +6104 3333333333333333333333333333333333333333333333333333333 +6105 3333333333333333333333333333333333333333333333333333333 +6106 3333333333333333333333333333333333333333333333333333333 +6107 3333333333333333333333333333333333333333333333333333333 +6108 3333333333333333333333333333333333333333333333333333333 +6109 3333333333333333333333333333333333333333333333333333333 +6110 3333333333333333333333333333333333333333333333333333333 +6111 3333333333333333333333333333333333333333333333333333333 +6112 3333333333333333333333333333333333333333333333333333333 +6113 3333333333333333333333333333333333333333333333333333333 +6114 3333333333333333333333333333333333333333333333333333333 +6115 3333333333333333333333333333333333333333333333333333333 +6116 3333333333333333333333333333333333333333333333333333333 +6117 3333333333333333333333333333333333333333333333333333333 +6118 3333333333333333333333333333333333333333333333333333333 +6119 3333333333333333333333333333333333333333333333333333333 +6120 3333333333333333333333333333333333333333333333333333333 +6121 3333333333333333333333333333333333333333333333333333333 +6122 3333333333333333333333333333333333333333333333333333333 +6123 3333333333333333333333333333333333333333333333333333333 +6124 3333333333333333333333333333333333333333333333333333333 +6125 3333333333333333333333333333333333333333333333333333333 +6126 3333333333333333333333333333333333333333333333333333333 +6127 3333333333333333333333333333333333333333333333333333333 +6128 3333333333333333333333333333333333333333333333333333333 +6129 3333333333333333333333333333333333333333333333333333333 +6130 3333333333333333333333333333333333333333333333333333333 +6131 3333333333333333333333333333333333333333333333333333333 +6132 3333333333333333333333333333333333333333333333333333333 +6133 3333333333333333333333333333333333333333333333333333333 +6134 3333333333333333333333333333333333333333333333333333333 +6135 3333333333333333333333333333333333333333333333333333333 +6136 3333333333333333333333333333333333333333333333333333333 +6137 3333333333333333333333333333333333333333333333333333333 +6138 3333333333333333333333333333333333333333333333333333333 +6139 3333333333333333333333333333333333333333333333333333333 +6140 3333333333333333333333333333333333333333333333333333333 +6141 3333333333333333333333333333333333333333333333333333333 +6142 3333333333333333333333333333333333333333333333333333333 +6143 3333333333333333333333333333333333333333333333333333333 +6144 3333333333333333333333333333333333333333333333333333333 +6145 3333333333333333333333333333333333333333333333333333333 +6146 3333333333333333333333333333333333333333333333333333333 +6147 3333333333333333333333333333333333333333333333333333333 +6148 3333333333333333333333333333333333333333333333333333333 +6149 3333333333333333333333333333333333333333333333333333333 +6150 3333333333333333333333333333333333333333333333333333333 +6151 3333333333333333333333333333333333333333333333333333333 +6152 3333333333333333333333333333333333333333333333333333333 +6153 3333333333333333333333333333333333333333333333333333333 +6154 3333333333333333333333333333333333333333333333333333333 +6155 3333333333333333333333333333333333333333333333333333333 +6156 3333333333333333333333333333333333333333333333333333333 +6157 3333333333333333333333333333333333333333333333333333333 +6158 3333333333333333333333333333333333333333333333333333333 +6159 3333333333333333333333333333333333333333333333333333333 +6160 3333333333333333333333333333333333333333333333333333333 +6161 3333333333333333333333333333333333333333333333333333333 +6162 3333333333333333333333333333333333333333333333333333333 +6163 3333333333333333333333333333333333333333333333333333333 +6164 3333333333333333333333333333333333333333333333333333333 +6165 3333333333333333333333333333333333333333333333333333333 +6166 3333333333333333333333333333333333333333333333333333333 +6167 3333333333333333333333333333333333333333333333333333333 +6168 3333333333333333333333333333333333333333333333333333333 +6169 3333333333333333333333333333333333333333333333333333333 +6170 3333333333333333333333333333333333333333333333333333333 +6171 3333333333333333333333333333333333333333333333333333333 +6172 3333333333333333333333333333333333333333333333333333333 +6173 3333333333333333333333333333333333333333333333333333333 +6174 3333333333333333333333333333333333333333333333333333333 +6175 3333333333333333333333333333333333333333333333333333333 +6176 3333333333333333333333333333333333333333333333333333333 +6177 3333333333333333333333333333333333333333333333333333333 +6178 3333333333333333333333333333333333333333333333333333333 +6179 3333333333333333333333333333333333333333333333333333333 +6180 3333333333333333333333333333333333333333333333333333333 +6181 3333333333333333333333333333333333333333333333333333333 +6182 3333333333333333333333333333333333333333333333333333333 +6183 3333333333333333333333333333333333333333333333333333333 +6184 3333333333333333333333333333333333333333333333333333333 +6185 3333333333333333333333333333333333333333333333333333333 +6186 3333333333333333333333333333333333333333333333333333333 +6187 3333333333333333333333333333333333333333333333333333333 +6188 3333333333333333333333333333333333333333333333333333333 +6189 3333333333333333333333333333333333333333333333333333333 +6190 3333333333333333333333333333333333333333333333333333333 +6191 3333333333333333333333333333333333333333333333333333333 +6192 3333333333333333333333333333333333333333333333333333333 +6193 3333333333333333333333333333333333333333333333333333333 +6194 3333333333333333333333333333333333333333333333333333333 +6195 3333333333333333333333333333333333333333333333333333333 +6196 3333333333333333333333333333333333333333333333333333333 +6197 3333333333333333333333333333333333333333333333333333333 +6198 3333333333333333333333333333333333333333333333333333333 +6199 3333333333333333333333333333333333333333333333333333333 +6200 3333333333333333333333333333333333333333333333333333333 +6201 3333333333333333333333333333333333333333333333333333333 +6202 3333333333333333333333333333333333333333333333333333333 +6203 3333333333333333333333333333333333333333333333333333333 +6204 3333333333333333333333333333333333333333333333333333333 +6205 3333333333333333333333333333333333333333333333333333333 +6206 3333333333333333333333333333333333333333333333333333333 +6207 3333333333333333333333333333333333333333333333333333333 +6208 3333333333333333333333333333333333333333333333333333333 +6209 3333333333333333333333333333333333333333333333333333333 +6210 3333333333333333333333333333333333333333333333333333333 +6211 3333333333333333333333333333333333333333333333333333333 +6212 3333333333333333333333333333333333333333333333333333333 +6213 3333333333333333333333333333333333333333333333333333333 +6214 3333333333333333333333333333333333333333333333333333333 +6215 3333333333333333333333333333333333333333333333333333333 +6216 3333333333333333333333333333333333333333333333333333333 +6217 3333333333333333333333333333333333333333333333333333333 +6218 3333333333333333333333333333333333333333333333333333333 +6219 3333333333333333333333333333333333333333333333333333333 +6220 3333333333333333333333333333333333333333333333333333333 +6221 3333333333333333333333333333333333333333333333333333333 +6222 3333333333333333333333333333333333333333333333333333333 +6223 3333333333333333333333333333333333333333333333333333333 +6224 3333333333333333333333333333333333333333333333333333333 +6225 3333333333333333333333333333333333333333333333333333333 +6226 3333333333333333333333333333333333333333333333333333333 +6227 3333333333333333333333333333333333333333333333333333333 +6228 3333333333333333333333333333333333333333333333333333333 +6229 3333333333333333333333333333333333333333333333333333333 +6230 3333333333333333333333333333333333333333333333333333333 +6231 3333333333333333333333333333333333333333333333333333333 +6232 3333333333333333333333333333333333333333333333333333333 +6233 3333333333333333333333333333333333333333333333333333333 +6234 3333333333333333333333333333333333333333333333333333333 +6235 3333333333333333333333333333333333333333333333333333333 +6236 3333333333333333333333333333333333333333333333333333333 +6237 3333333333333333333333333333333333333333333333333333333 +6238 3333333333333333333333333333333333333333333333333333333 +6239 3333333333333333333333333333333333333333333333333333333 +6240 3333333333333333333333333333333333333333333333333333333 +6241 3333333333333333333333333333333333333333333333333333333 +6242 3333333333333333333333333333333333333333333333333333333 +6243 3333333333333333333333333333333333333333333333333333333 +6244 3333333333333333333333333333333333333333333333333333333 +6245 3333333333333333333333333333333333333333333333333333333 +6246 3333333333333333333333333333333333333333333333333333333 +6247 3333333333333333333333333333333333333333333333333333333 +6248 3333333333333333333333333333333333333333333333333333333 +6249 3333333333333333333333333333333333333333333333333333333 +6250 3333333333333333333333333333333333333333333333333333333 +6251 3333333333333333333333333333333333333333333333333333333 +6252 3333333333333333333333333333333333333333333333333333333 +6253 3333333333333333333333333333333333333333333333333333333 +6254 3333333333333333333333333333333333333333333333333333333 +6255 3333333333333333333333333333333333333333333333333333333 +6256 3333333333333333333333333333333333333333333333333333333 +6257 3333333333333333333333333333333333333333333333333333333 +6258 3333333333333333333333333333333333333333333333333333333 +6259 3333333333333333333333333333333333333333333333333333333 +6260 3333333333333333333333333333333333333333333333333333333 +6261 3333333333333333333333333333333333333333333333333333333 +6262 3333333333333333333333333333333333333333333333333333333 +6263 3333333333333333333333333333333333333333333333333333333 +6264 3333333333333333333333333333333333333333333333333333333 +6265 3333333333333333333333333333333333333333333333333333333 +6266 3333333333333333333333333333333333333333333333333333333 +6267 3333333333333333333333333333333333333333333333333333333 +6268 3333333333333333333333333333333333333333333333333333333 +6269 3333333333333333333333333333333333333333333333333333333 +6270 3333333333333333333333333333333333333333333333333333333 +6271 3333333333333333333333333333333333333333333333333333333 +6272 3333333333333333333333333333333333333333333333333333333 +6273 3333333333333333333333333333333333333333333333333333333 +6274 3333333333333333333333333333333333333333333333333333333 +6275 3333333333333333333333333333333333333333333333333333333 +6276 3333333333333333333333333333333333333333333333333333333 +6277 3333333333333333333333333333333333333333333333333333333 +6278 3333333333333333333333333333333333333333333333333333333 +6279 3333333333333333333333333333333333333333333333333333333 +6280 3333333333333333333333333333333333333333333333333333333 +6281 3333333333333333333333333333333333333333333333333333333 +6282 3333333333333333333333333333333333333333333333333333333 +6283 3333333333333333333333333333333333333333333333333333333 +6284 3333333333333333333333333333333333333333333333333333333 +6285 3333333333333333333333333333333333333333333333333333333 +6286 3333333333333333333333333333333333333333333333333333333 +6287 3333333333333333333333333333333333333333333333333333333 +6288 3333333333333333333333333333333333333333333333333333333 +6289 3333333333333333333333333333333333333333333333333333333 +6290 3333333333333333333333333333333333333333333333333333333 +6291 3333333333333333333333333333333333333333333333333333333 +6292 3333333333333333333333333333333333333333333333333333333 +6293 3333333333333333333333333333333333333333333333333333333 +6294 3333333333333333333333333333333333333333333333333333333 +6295 3333333333333333333333333333333333333333333333333333333 +6296 3333333333333333333333333333333333333333333333333333333 +6297 3333333333333333333333333333333333333333333333333333333 +6298 3333333333333333333333333333333333333333333333333333333 +6299 3333333333333333333333333333333333333333333333333333333 +6300 3333333333333333333333333333333333333333333333333333333 +6301 3333333333333333333333333333333333333333333333333333333 +6302 3333333333333333333333333333333333333333333333333333333 +6303 3333333333333333333333333333333333333333333333333333333 +6304 3333333333333333333333333333333333333333333333333333333 +6305 3333333333333333333333333333333333333333333333333333333 +6306 3333333333333333333333333333333333333333333333333333333 +6307 3333333333333333333333333333333333333333333333333333333 +6308 3333333333333333333333333333333333333333333333333333333 +6309 3333333333333333333333333333333333333333333333333333333 +6310 3333333333333333333333333333333333333333333333333333333 +6311 3333333333333333333333333333333333333333333333333333333 +6312 3333333333333333333333333333333333333333333333333333333 +6313 3333333333333333333333333333333333333333333333333333333 +6314 3333333333333333333333333333333333333333333333333333333 +6315 3333333333333333333333333333333333333333333333333333333 +6316 3333333333333333333333333333333333333333333333333333333 +6317 3333333333333333333333333333333333333333333333333333333 +6318 3333333333333333333333333333333333333333333333333333333 +6319 3333333333333333333333333333333333333333333333333333333 +6320 3333333333333333333333333333333333333333333333333333333 +6321 3333333333333333333333333333333333333333333333333333333 +6322 3333333333333333333333333333333333333333333333333333333 +6323 3333333333333333333333333333333333333333333333333333333 +6324 3333333333333333333333333333333333333333333333333333333 +6325 3333333333333333333333333333333333333333333333333333333 +6326 3333333333333333333333333333333333333333333333333333333 +6327 3333333333333333333333333333333333333333333333333333333 +6328 3333333333333333333333333333333333333333333333333333333 +6329 3333333333333333333333333333333333333333333333333333333 +6330 3333333333333333333333333333333333333333333333333333333 +6331 3333333333333333333333333333333333333333333333333333333 +6332 3333333333333333333333333333333333333333333333333333333 +6333 3333333333333333333333333333333333333333333333333333333 +6334 3333333333333333333333333333333333333333333333333333333 +6335 3333333333333333333333333333333333333333333333333333333 +6336 3333333333333333333333333333333333333333333333333333333 +6337 3333333333333333333333333333333333333333333333333333333 +6338 3333333333333333333333333333333333333333333333333333333 +6339 3333333333333333333333333333333333333333333333333333333 +6340 3333333333333333333333333333333333333333333333333333333 +6341 3333333333333333333333333333333333333333333333333333333 +6342 3333333333333333333333333333333333333333333333333333333 +6343 3333333333333333333333333333333333333333333333333333333 +6344 3333333333333333333333333333333333333333333333333333333 +6345 3333333333333333333333333333333333333333333333333333333 +6346 3333333333333333333333333333333333333333333333333333333 +6347 3333333333333333333333333333333333333333333333333333333 +6348 3333333333333333333333333333333333333333333333333333333 +6349 3333333333333333333333333333333333333333333333333333333 +6350 3333333333333333333333333333333333333333333333333333333 +6351 3333333333333333333333333333333333333333333333333333333 +6352 3333333333333333333333333333333333333333333333333333333 +6353 3333333333333333333333333333333333333333333333333333333 +6354 3333333333333333333333333333333333333333333333333333333 +6355 3333333333333333333333333333333333333333333333333333333 +6356 3333333333333333333333333333333333333333333333333333333 +6357 3333333333333333333333333333333333333333333333333333333 +6358 3333333333333333333333333333333333333333333333333333333 +6359 3333333333333333333333333333333333333333333333333333333 +6360 3333333333333333333333333333333333333333333333333333333 +6361 3333333333333333333333333333333333333333333333333333333 +6362 3333333333333333333333333333333333333333333333333333333 +6363 3333333333333333333333333333333333333333333333333333333 +6364 3333333333333333333333333333333333333333333333333333333 +6365 3333333333333333333333333333333333333333333333333333333 +6366 3333333333333333333333333333333333333333333333333333333 +6367 3333333333333333333333333333333333333333333333333333333 +6368 3333333333333333333333333333333333333333333333333333333 +6369 3333333333333333333333333333333333333333333333333333333 +6370 3333333333333333333333333333333333333333333333333333333 +6371 3333333333333333333333333333333333333333333333333333333 +6372 3333333333333333333333333333333333333333333333333333333 +6373 3333333333333333333333333333333333333333333333333333333 +6374 3333333333333333333333333333333333333333333333333333333 +6375 3333333333333333333333333333333333333333333333333333333 +6376 3333333333333333333333333333333333333333333333333333333 +6377 3333333333333333333333333333333333333333333333333333333 +6378 3333333333333333333333333333333333333333333333333333333 +6379 3333333333333333333333333333333333333333333333333333333 +6380 3333333333333333333333333333333333333333333333333333333 +6381 3333333333333333333333333333333333333333333333333333333 +6382 3333333333333333333333333333333333333333333333333333333 +6383 3333333333333333333333333333333333333333333333333333333 +6384 3333333333333333333333333333333333333333333333333333333 +6385 3333333333333333333333333333333333333333333333333333333 +6386 3333333333333333333333333333333333333333333333333333333 +6387 3333333333333333333333333333333333333333333333333333333 +6388 3333333333333333333333333333333333333333333333333333333 +6389 3333333333333333333333333333333333333333333333333333333 +6390 3333333333333333333333333333333333333333333333333333333 +6391 3333333333333333333333333333333333333333333333333333333 +6392 3333333333333333333333333333333333333333333333333333333 +6393 3333333333333333333333333333333333333333333333333333333 +6394 3333333333333333333333333333333333333333333333333333333 +6395 3333333333333333333333333333333333333333333333333333333 +6396 3333333333333333333333333333333333333333333333333333333 +6397 3333333333333333333333333333333333333333333333333333333 +6398 3333333333333333333333333333333333333333333333333333333 +6399 3333333333333333333333333333333333333333333333333333333 +6400 3333333333333333333333333333333333333333333333333333333 +6401 3333333333333333333333333333333333333333333333333333333 +6402 3333333333333333333333333333333333333333333333333333333 +6403 3333333333333333333333333333333333333333333333333333333 +6404 3333333333333333333333333333333333333333333333333333333 +6405 3333333333333333333333333333333333333333333333333333333 +6406 3333333333333333333333333333333333333333333333333333333 +6407 3333333333333333333333333333333333333333333333333333333 +6408 3333333333333333333333333333333333333333333333333333333 +6409 3333333333333333333333333333333333333333333333333333333 +6410 3333333333333333333333333333333333333333333333333333333 +6411 3333333333333333333333333333333333333333333333333333333 +6412 3333333333333333333333333333333333333333333333333333333 +6413 3333333333333333333333333333333333333333333333333333333 +6414 3333333333333333333333333333333333333333333333333333333 +6415 3333333333333333333333333333333333333333333333333333333 +6416 3333333333333333333333333333333333333333333333333333333 +6417 3333333333333333333333333333333333333333333333333333333 +6418 3333333333333333333333333333333333333333333333333333333 +6419 3333333333333333333333333333333333333333333333333333333 +6420 3333333333333333333333333333333333333333333333333333333 +6421 3333333333333333333333333333333333333333333333333333333 +6422 3333333333333333333333333333333333333333333333333333333 +6423 3333333333333333333333333333333333333333333333333333333 +6424 3333333333333333333333333333333333333333333333333333333 +6425 3333333333333333333333333333333333333333333333333333333 +6426 3333333333333333333333333333333333333333333333333333333 +6427 3333333333333333333333333333333333333333333333333333333 +6428 3333333333333333333333333333333333333333333333333333333 +6429 3333333333333333333333333333333333333333333333333333333 +6430 3333333333333333333333333333333333333333333333333333333 +6431 3333333333333333333333333333333333333333333333333333333 +6432 3333333333333333333333333333333333333333333333333333333 +6433 3333333333333333333333333333333333333333333333333333333 +6434 3333333333333333333333333333333333333333333333333333333 +6435 3333333333333333333333333333333333333333333333333333333 +6436 3333333333333333333333333333333333333333333333333333333 +6437 3333333333333333333333333333333333333333333333333333333 +6438 3333333333333333333333333333333333333333333333333333333 +6439 3333333333333333333333333333333333333333333333333333333 +6440 3333333333333333333333333333333333333333333333333333333 +6441 3333333333333333333333333333333333333333333333333333333 +6442 3333333333333333333333333333333333333333333333333333333 +6443 3333333333333333333333333333333333333333333333333333333 +6444 3333333333333333333333333333333333333333333333333333333 +6445 3333333333333333333333333333333333333333333333333333333 +6446 3333333333333333333333333333333333333333333333333333333 +6447 3333333333333333333333333333333333333333333333333333333 +6448 3333333333333333333333333333333333333333333333333333333 +6449 3333333333333333333333333333333333333333333333333333333 +6450 3333333333333333333333333333333333333333333333333333333 +6451 3333333333333333333333333333333333333333333333333333333 +6452 3333333333333333333333333333333333333333333333333333333 +6453 3333333333333333333333333333333333333333333333333333333 +6454 3333333333333333333333333333333333333333333333333333333 +6455 3333333333333333333333333333333333333333333333333333333 +6456 3333333333333333333333333333333333333333333333333333333 +6457 3333333333333333333333333333333333333333333333333333333 +6458 3333333333333333333333333333333333333333333333333333333 +6459 3333333333333333333333333333333333333333333333333333333 +6460 3333333333333333333333333333333333333333333333333333333 +6461 3333333333333333333333333333333333333333333333333333333 +6462 3333333333333333333333333333333333333333333333333333333 +6463 3333333333333333333333333333333333333333333333333333333 +6464 3333333333333333333333333333333333333333333333333333333 +6465 3333333333333333333333333333333333333333333333333333333 +6466 3333333333333333333333333333333333333333333333333333333 +6467 3333333333333333333333333333333333333333333333333333333 +6468 3333333333333333333333333333333333333333333333333333333 +6469 3333333333333333333333333333333333333333333333333333333 +6470 3333333333333333333333333333333333333333333333333333333 +6471 3333333333333333333333333333333333333333333333333333333 +6472 3333333333333333333333333333333333333333333333333333333 +6473 3333333333333333333333333333333333333333333333333333333 +6474 3333333333333333333333333333333333333333333333333333333 +6475 3333333333333333333333333333333333333333333333333333333 +6476 3333333333333333333333333333333333333333333333333333333 +6477 3333333333333333333333333333333333333333333333333333333 +6478 3333333333333333333333333333333333333333333333333333333 +6479 3333333333333333333333333333333333333333333333333333333 +6480 3333333333333333333333333333333333333333333333333333333 +6481 3333333333333333333333333333333333333333333333333333333 +6482 3333333333333333333333333333333333333333333333333333333 +6483 3333333333333333333333333333333333333333333333333333333 +6484 3333333333333333333333333333333333333333333333333333333 +6485 3333333333333333333333333333333333333333333333333333333 +6486 3333333333333333333333333333333333333333333333333333333 +6487 3333333333333333333333333333333333333333333333333333333 +6488 3333333333333333333333333333333333333333333333333333333 +6489 3333333333333333333333333333333333333333333333333333333 +6490 3333333333333333333333333333333333333333333333333333333 +6491 3333333333333333333333333333333333333333333333333333333 +6492 3333333333333333333333333333333333333333333333333333333 +6493 3333333333333333333333333333333333333333333333333333333 +6494 3333333333333333333333333333333333333333333333333333333 +6495 3333333333333333333333333333333333333333333333333333333 +6496 3333333333333333333333333333333333333333333333333333333 +6497 3333333333333333333333333333333333333333333333333333333 +6498 3333333333333333333333333333333333333333333333333333333 +6499 3333333333333333333333333333333333333333333333333333333 +6500 3333333333333333333333333333333333333333333333333333333 +6501 3333333333333333333333333333333333333333333333333333333 +6502 3333333333333333333333333333333333333333333333333333333 +6503 3333333333333333333333333333333333333333333333333333333 +6504 3333333333333333333333333333333333333333333333333333333 +6505 3333333333333333333333333333333333333333333333333333333 +6506 3333333333333333333333333333333333333333333333333333333 +6507 3333333333333333333333333333333333333333333333333333333 +6508 3333333333333333333333333333333333333333333333333333333 +6509 3333333333333333333333333333333333333333333333333333333 +6510 3333333333333333333333333333333333333333333333333333333 +6511 3333333333333333333333333333333333333333333333333333333 +6512 3333333333333333333333333333333333333333333333333333333 +6513 3333333333333333333333333333333333333333333333333333333 +6514 3333333333333333333333333333333333333333333333333333333 +6515 3333333333333333333333333333333333333333333333333333333 +6516 3333333333333333333333333333333333333333333333333333333 +6517 3333333333333333333333333333333333333333333333333333333 +6518 3333333333333333333333333333333333333333333333333333333 +6519 3333333333333333333333333333333333333333333333333333333 +6520 3333333333333333333333333333333333333333333333333333333 +6521 3333333333333333333333333333333333333333333333333333333 +6522 3333333333333333333333333333333333333333333333333333333 +6523 3333333333333333333333333333333333333333333333333333333 +6524 3333333333333333333333333333333333333333333333333333333 +6525 3333333333333333333333333333333333333333333333333333333 +6526 3333333333333333333333333333333333333333333333333333333 +6527 3333333333333333333333333333333333333333333333333333333 +6528 3333333333333333333333333333333333333333333333333333333 +6529 3333333333333333333333333333333333333333333333333333333 +6530 3333333333333333333333333333333333333333333333333333333 +6531 3333333333333333333333333333333333333333333333333333333 +6532 3333333333333333333333333333333333333333333333333333333 +6533 3333333333333333333333333333333333333333333333333333333 +6534 3333333333333333333333333333333333333333333333333333333 +6535 3333333333333333333333333333333333333333333333333333333 +6536 3333333333333333333333333333333333333333333333333333333 +6537 3333333333333333333333333333333333333333333333333333333 +6538 3333333333333333333333333333333333333333333333333333333 +6539 3333333333333333333333333333333333333333333333333333333 +6540 3333333333333333333333333333333333333333333333333333333 +6541 3333333333333333333333333333333333333333333333333333333 +6542 3333333333333333333333333333333333333333333333333333333 +6543 3333333333333333333333333333333333333333333333333333333 +6544 3333333333333333333333333333333333333333333333333333333 +6545 3333333333333333333333333333333333333333333333333333333 +6546 3333333333333333333333333333333333333333333333333333333 +6547 3333333333333333333333333333333333333333333333333333333 +6548 3333333333333333333333333333333333333333333333333333333 +6549 3333333333333333333333333333333333333333333333333333333 +6550 3333333333333333333333333333333333333333333333333333333 +6551 3333333333333333333333333333333333333333333333333333333 +6552 3333333333333333333333333333333333333333333333333333333 +6553 3333333333333333333333333333333333333333333333333333333 +6554 3333333333333333333333333333333333333333333333333333333 +6555 3333333333333333333333333333333333333333333333333333333 +6556 3333333333333333333333333333333333333333333333333333333 +6557 3333333333333333333333333333333333333333333333333333333 +6558 3333333333333333333333333333333333333333333333333333333 +6559 3333333333333333333333333333333333333333333333333333333 +6560 3333333333333333333333333333333333333333333333333333333 +6561 3333333333333333333333333333333333333333333333333333333 +6562 3333333333333333333333333333333333333333333333333333333 +6563 3333333333333333333333333333333333333333333333333333333 +6564 3333333333333333333333333333333333333333333333333333333 +6565 3333333333333333333333333333333333333333333333333333333 +6566 3333333333333333333333333333333333333333333333333333333 +6567 3333333333333333333333333333333333333333333333333333333 +6568 3333333333333333333333333333333333333333333333333333333 +6569 3333333333333333333333333333333333333333333333333333333 +6570 3333333333333333333333333333333333333333333333333333333 +6571 3333333333333333333333333333333333333333333333333333333 +6572 3333333333333333333333333333333333333333333333333333333 +6573 3333333333333333333333333333333333333333333333333333333 +6574 3333333333333333333333333333333333333333333333333333333 +6575 3333333333333333333333333333333333333333333333333333333 +6576 3333333333333333333333333333333333333333333333333333333 +6577 3333333333333333333333333333333333333333333333333333333 +6578 3333333333333333333333333333333333333333333333333333333 +6579 3333333333333333333333333333333333333333333333333333333 +6580 3333333333333333333333333333333333333333333333333333333 +6581 3333333333333333333333333333333333333333333333333333333 +6582 3333333333333333333333333333333333333333333333333333333 +6583 3333333333333333333333333333333333333333333333333333333 +6584 3333333333333333333333333333333333333333333333333333333 +6585 3333333333333333333333333333333333333333333333333333333 +6586 3333333333333333333333333333333333333333333333333333333 +6587 3333333333333333333333333333333333333333333333333333333 +6588 3333333333333333333333333333333333333333333333333333333 +6589 3333333333333333333333333333333333333333333333333333333 +6590 3333333333333333333333333333333333333333333333333333333 +6591 3333333333333333333333333333333333333333333333333333333 +6592 3333333333333333333333333333333333333333333333333333333 +6593 3333333333333333333333333333333333333333333333333333333 +6594 3333333333333333333333333333333333333333333333333333333 +6595 3333333333333333333333333333333333333333333333333333333 +6596 3333333333333333333333333333333333333333333333333333333 +6597 3333333333333333333333333333333333333333333333333333333 +6598 3333333333333333333333333333333333333333333333333333333 +6599 3333333333333333333333333333333333333333333333333333333 +6600 3333333333333333333333333333333333333333333333333333333 +6601 3333333333333333333333333333333333333333333333333333333 +6602 3333333333333333333333333333333333333333333333333333333 +6603 3333333333333333333333333333333333333333333333333333333 +6604 3333333333333333333333333333333333333333333333333333333 +6605 3333333333333333333333333333333333333333333333333333333 +6606 3333333333333333333333333333333333333333333333333333333 +6607 3333333333333333333333333333333333333333333333333333333 +6608 3333333333333333333333333333333333333333333333333333333 +6609 3333333333333333333333333333333333333333333333333333333 +6610 3333333333333333333333333333333333333333333333333333333 +6611 3333333333333333333333333333333333333333333333333333333 +6612 3333333333333333333333333333333333333333333333333333333 +6613 3333333333333333333333333333333333333333333333333333333 +6614 3333333333333333333333333333333333333333333333333333333 +6615 3333333333333333333333333333333333333333333333333333333 +6616 3333333333333333333333333333333333333333333333333333333 +6617 3333333333333333333333333333333333333333333333333333333 +6618 3333333333333333333333333333333333333333333333333333333 +6619 3333333333333333333333333333333333333333333333333333333 +6620 3333333333333333333333333333333333333333333333333333333 +6621 3333333333333333333333333333333333333333333333333333333 +6622 3333333333333333333333333333333333333333333333333333333 +6623 3333333333333333333333333333333333333333333333333333333 +6624 3333333333333333333333333333333333333333333333333333333 +6625 3333333333333333333333333333333333333333333333333333333 +6626 3333333333333333333333333333333333333333333333333333333 +6627 3333333333333333333333333333333333333333333333333333333 +6628 3333333333333333333333333333333333333333333333333333333 +6629 3333333333333333333333333333333333333333333333333333333 +6630 3333333333333333333333333333333333333333333333333333333 +6631 3333333333333333333333333333333333333333333333333333333 +6632 3333333333333333333333333333333333333333333333333333333 +6633 3333333333333333333333333333333333333333333333333333333 +6634 3333333333333333333333333333333333333333333333333333333 +6635 3333333333333333333333333333333333333333333333333333333 +6636 3333333333333333333333333333333333333333333333333333333 +6637 3333333333333333333333333333333333333333333333333333333 +6638 3333333333333333333333333333333333333333333333333333333 +6639 3333333333333333333333333333333333333333333333333333333 +6640 3333333333333333333333333333333333333333333333333333333 +6641 3333333333333333333333333333333333333333333333333333333 +6642 3333333333333333333333333333333333333333333333333333333 +6643 3333333333333333333333333333333333333333333333333333333 +6644 3333333333333333333333333333333333333333333333333333333 +6645 3333333333333333333333333333333333333333333333333333333 +6646 3333333333333333333333333333333333333333333333333333333 +6647 3333333333333333333333333333333333333333333333333333333 +6648 3333333333333333333333333333333333333333333333333333333 +6649 3333333333333333333333333333333333333333333333333333333 +6650 3333333333333333333333333333333333333333333333333333333 +6651 3333333333333333333333333333333333333333333333333333333 +6652 3333333333333333333333333333333333333333333333333333333 +6653 3333333333333333333333333333333333333333333333333333333 +6654 3333333333333333333333333333333333333333333333333333333 +6655 3333333333333333333333333333333333333333333333333333333 +6656 3333333333333333333333333333333333333333333333333333333 +6657 3333333333333333333333333333333333333333333333333333333 +6658 3333333333333333333333333333333333333333333333333333333 +6659 3333333333333333333333333333333333333333333333333333333 +6660 3333333333333333333333333333333333333333333333333333333 +6661 3333333333333333333333333333333333333333333333333333333 +6662 3333333333333333333333333333333333333333333333333333333 +6663 3333333333333333333333333333333333333333333333333333333 +6664 3333333333333333333333333333333333333333333333333333333 +6665 3333333333333333333333333333333333333333333333333333333 +6666 3333333333333333333333333333333333333333333333333333333 +6667 3333333333333333333333333333333333333333333333333333333 +6668 3333333333333333333333333333333333333333333333333333333 +6669 3333333333333333333333333333333333333333333333333333333 +6670 3333333333333333333333333333333333333333333333333333333 +6671 3333333333333333333333333333333333333333333333333333333 +6672 3333333333333333333333333333333333333333333333333333333 +6673 3333333333333333333333333333333333333333333333333333333 +6674 3333333333333333333333333333333333333333333333333333333 +6675 3333333333333333333333333333333333333333333333333333333 +6676 3333333333333333333333333333333333333333333333333333333 +6677 3333333333333333333333333333333333333333333333333333333 +6678 3333333333333333333333333333333333333333333333333333333 +6679 3333333333333333333333333333333333333333333333333333333 +6680 3333333333333333333333333333333333333333333333333333333 +6681 3333333333333333333333333333333333333333333333333333333 +6682 3333333333333333333333333333333333333333333333333333333 +6683 3333333333333333333333333333333333333333333333333333333 +6684 3333333333333333333333333333333333333333333333333333333 +6685 3333333333333333333333333333333333333333333333333333333 +6686 3333333333333333333333333333333333333333333333333333333 +6687 3333333333333333333333333333333333333333333333333333333 +6688 3333333333333333333333333333333333333333333333333333333 +6689 3333333333333333333333333333333333333333333333333333333 +6690 3333333333333333333333333333333333333333333333333333333 +6691 3333333333333333333333333333333333333333333333333333333 +6692 3333333333333333333333333333333333333333333333333333333 +6693 3333333333333333333333333333333333333333333333333333333 +6694 3333333333333333333333333333333333333333333333333333333 +6695 3333333333333333333333333333333333333333333333333333333 +6696 3333333333333333333333333333333333333333333333333333333 +6697 3333333333333333333333333333333333333333333333333333333 +6698 3333333333333333333333333333333333333333333333333333333 +6699 3333333333333333333333333333333333333333333333333333333 +6700 3333333333333333333333333333333333333333333333333333333 +6701 3333333333333333333333333333333333333333333333333333333 +6702 3333333333333333333333333333333333333333333333333333333 +6703 3333333333333333333333333333333333333333333333333333333 +6704 3333333333333333333333333333333333333333333333333333333 +6705 3333333333333333333333333333333333333333333333333333333 +6706 3333333333333333333333333333333333333333333333333333333 +6707 3333333333333333333333333333333333333333333333333333333 +6708 3333333333333333333333333333333333333333333333333333333 +6709 3333333333333333333333333333333333333333333333333333333 +6710 3333333333333333333333333333333333333333333333333333333 +6711 3333333333333333333333333333333333333333333333333333333 +6712 3333333333333333333333333333333333333333333333333333333 +6713 3333333333333333333333333333333333333333333333333333333 +6714 3333333333333333333333333333333333333333333333333333333 +6715 3333333333333333333333333333333333333333333333333333333 +6716 3333333333333333333333333333333333333333333333333333333 +6717 3333333333333333333333333333333333333333333333333333333 +6718 3333333333333333333333333333333333333333333333333333333 +6719 3333333333333333333333333333333333333333333333333333333 +6720 3333333333333333333333333333333333333333333333333333333 +6721 3333333333333333333333333333333333333333333333333333333 +6722 3333333333333333333333333333333333333333333333333333333 +6723 3333333333333333333333333333333333333333333333333333333 +6724 3333333333333333333333333333333333333333333333333333333 +6725 3333333333333333333333333333333333333333333333333333333 +6726 3333333333333333333333333333333333333333333333333333333 +6727 3333333333333333333333333333333333333333333333333333333 +6728 3333333333333333333333333333333333333333333333333333333 +6729 3333333333333333333333333333333333333333333333333333333 +6730 3333333333333333333333333333333333333333333333333333333 +6731 3333333333333333333333333333333333333333333333333333333 +6732 3333333333333333333333333333333333333333333333333333333 +6733 3333333333333333333333333333333333333333333333333333333 +6734 3333333333333333333333333333333333333333333333333333333 +6735 3333333333333333333333333333333333333333333333333333333 +6736 3333333333333333333333333333333333333333333333333333333 +6737 3333333333333333333333333333333333333333333333333333333 +6738 3333333333333333333333333333333333333333333333333333333 +6739 3333333333333333333333333333333333333333333333333333333 +6740 3333333333333333333333333333333333333333333333333333333 +6741 3333333333333333333333333333333333333333333333333333333 +6742 3333333333333333333333333333333333333333333333333333333 +6743 3333333333333333333333333333333333333333333333333333333 +6744 3333333333333333333333333333333333333333333333333333333 +6745 3333333333333333333333333333333333333333333333333333333 +6746 3333333333333333333333333333333333333333333333333333333 +6747 3333333333333333333333333333333333333333333333333333333 +6748 3333333333333333333333333333333333333333333333333333333 +6749 3333333333333333333333333333333333333333333333333333333 +6750 3333333333333333333333333333333333333333333333333333333 +6751 3333333333333333333333333333333333333333333333333333333 +6752 3333333333333333333333333333333333333333333333333333333 +6753 3333333333333333333333333333333333333333333333333333333 +6754 3333333333333333333333333333333333333333333333333333333 +6755 3333333333333333333333333333333333333333333333333333333 +6756 3333333333333333333333333333333333333333333333333333333 +6757 3333333333333333333333333333333333333333333333333333333 +6758 3333333333333333333333333333333333333333333333333333333 +6759 3333333333333333333333333333333333333333333333333333333 +6760 3333333333333333333333333333333333333333333333333333333 +6761 3333333333333333333333333333333333333333333333333333333 +6762 3333333333333333333333333333333333333333333333333333333 +6763 3333333333333333333333333333333333333333333333333333333 +6764 3333333333333333333333333333333333333333333333333333333 +6765 3333333333333333333333333333333333333333333333333333333 +6766 3333333333333333333333333333333333333333333333333333333 +6767 3333333333333333333333333333333333333333333333333333333 +6768 3333333333333333333333333333333333333333333333333333333 +6769 3333333333333333333333333333333333333333333333333333333 +6770 3333333333333333333333333333333333333333333333333333333 +6771 3333333333333333333333333333333333333333333333333333333 +6772 3333333333333333333333333333333333333333333333333333333 +6773 3333333333333333333333333333333333333333333333333333333 +6774 3333333333333333333333333333333333333333333333333333333 +6775 3333333333333333333333333333333333333333333333333333333 +6776 3333333333333333333333333333333333333333333333333333333 +6777 3333333333333333333333333333333333333333333333333333333 +6778 3333333333333333333333333333333333333333333333333333333 +6779 3333333333333333333333333333333333333333333333333333333 +6780 3333333333333333333333333333333333333333333333333333333 +6781 3333333333333333333333333333333333333333333333333333333 +6782 3333333333333333333333333333333333333333333333333333333 +6783 3333333333333333333333333333333333333333333333333333333 +6784 3333333333333333333333333333333333333333333333333333333 +6785 3333333333333333333333333333333333333333333333333333333 +6786 3333333333333333333333333333333333333333333333333333333 +6787 3333333333333333333333333333333333333333333333333333333 +6788 3333333333333333333333333333333333333333333333333333333 +6789 3333333333333333333333333333333333333333333333333333333 +6790 3333333333333333333333333333333333333333333333333333333 +6791 3333333333333333333333333333333333333333333333333333333 +6792 3333333333333333333333333333333333333333333333333333333 +6793 3333333333333333333333333333333333333333333333333333333 +6794 3333333333333333333333333333333333333333333333333333333 +6795 3333333333333333333333333333333333333333333333333333333 +6796 3333333333333333333333333333333333333333333333333333333 +6797 3333333333333333333333333333333333333333333333333333333 +6798 3333333333333333333333333333333333333333333333333333333 +6799 3333333333333333333333333333333333333333333333333333333 +6800 3333333333333333333333333333333333333333333333333333333 +6801 3333333333333333333333333333333333333333333333333333333 +6802 3333333333333333333333333333333333333333333333333333333 +6803 3333333333333333333333333333333333333333333333333333333 +6804 3333333333333333333333333333333333333333333333333333333 +6805 3333333333333333333333333333333333333333333333333333333 +6806 3333333333333333333333333333333333333333333333333333333 +6807 3333333333333333333333333333333333333333333333333333333 +6808 3333333333333333333333333333333333333333333333333333333 +6809 3333333333333333333333333333333333333333333333333333333 +6810 3333333333333333333333333333333333333333333333333333333 +6811 3333333333333333333333333333333333333333333333333333333 +6812 3333333333333333333333333333333333333333333333333333333 +6813 3333333333333333333333333333333333333333333333333333333 +6814 3333333333333333333333333333333333333333333333333333333 +6815 3333333333333333333333333333333333333333333333333333333 +6816 3333333333333333333333333333333333333333333333333333333 +6817 3333333333333333333333333333333333333333333333333333333 +6818 3333333333333333333333333333333333333333333333333333333 +6819 3333333333333333333333333333333333333333333333333333333 +6820 3333333333333333333333333333333333333333333333333333333 +6821 3333333333333333333333333333333333333333333333333333333 +6822 3333333333333333333333333333333333333333333333333333333 +6823 3333333333333333333333333333333333333333333333333333333 +6824 3333333333333333333333333333333333333333333333333333333 +6825 3333333333333333333333333333333333333333333333333333333 +6826 3333333333333333333333333333333333333333333333333333333 +6827 3333333333333333333333333333333333333333333333333333333 +6828 3333333333333333333333333333333333333333333333333333333 +6829 3333333333333333333333333333333333333333333333333333333 +6830 3333333333333333333333333333333333333333333333333333333 +6831 3333333333333333333333333333333333333333333333333333333 +6832 3333333333333333333333333333333333333333333333333333333 +6833 3333333333333333333333333333333333333333333333333333333 +6834 3333333333333333333333333333333333333333333333333333333 +6835 3333333333333333333333333333333333333333333333333333333 +6836 3333333333333333333333333333333333333333333333333333333 +6837 3333333333333333333333333333333333333333333333333333333 +6838 3333333333333333333333333333333333333333333333333333333 +6839 3333333333333333333333333333333333333333333333333333333 +6840 3333333333333333333333333333333333333333333333333333333 +6841 3333333333333333333333333333333333333333333333333333333 +6842 3333333333333333333333333333333333333333333333333333333 +6843 3333333333333333333333333333333333333333333333333333333 +6844 3333333333333333333333333333333333333333333333333333333 +6845 3333333333333333333333333333333333333333333333333333333 +6846 3333333333333333333333333333333333333333333333333333333 +6847 3333333333333333333333333333333333333333333333333333333 +6848 3333333333333333333333333333333333333333333333333333333 +6849 3333333333333333333333333333333333333333333333333333333 +6850 3333333333333333333333333333333333333333333333333333333 +6851 3333333333333333333333333333333333333333333333333333333 +6852 3333333333333333333333333333333333333333333333333333333 +6853 3333333333333333333333333333333333333333333333333333333 +6854 3333333333333333333333333333333333333333333333333333333 +6855 3333333333333333333333333333333333333333333333333333333 +6856 3333333333333333333333333333333333333333333333333333333 +6857 3333333333333333333333333333333333333333333333333333333 +6858 3333333333333333333333333333333333333333333333333333333 +6859 3333333333333333333333333333333333333333333333333333333 +6860 3333333333333333333333333333333333333333333333333333333 +6861 3333333333333333333333333333333333333333333333333333333 +6862 3333333333333333333333333333333333333333333333333333333 +6863 3333333333333333333333333333333333333333333333333333333 +6864 3333333333333333333333333333333333333333333333333333333 +6865 3333333333333333333333333333333333333333333333333333333 +6866 3333333333333333333333333333333333333333333333333333333 +6867 3333333333333333333333333333333333333333333333333333333 +6868 3333333333333333333333333333333333333333333333333333333 +6869 3333333333333333333333333333333333333333333333333333333 +6870 3333333333333333333333333333333333333333333333333333333 +6871 3333333333333333333333333333333333333333333333333333333 +6872 3333333333333333333333333333333333333333333333333333333 +6873 3333333333333333333333333333333333333333333333333333333 +6874 3333333333333333333333333333333333333333333333333333333 +6875 3333333333333333333333333333333333333333333333333333333 +6876 3333333333333333333333333333333333333333333333333333333 +6877 3333333333333333333333333333333333333333333333333333333 +6878 3333333333333333333333333333333333333333333333333333333 +6879 3333333333333333333333333333333333333333333333333333333 +6880 3333333333333333333333333333333333333333333333333333333 +6881 3333333333333333333333333333333333333333333333333333333 +6882 3333333333333333333333333333333333333333333333333333333 +6883 3333333333333333333333333333333333333333333333333333333 +6884 3333333333333333333333333333333333333333333333333333333 +6885 3333333333333333333333333333333333333333333333333333333 +6886 3333333333333333333333333333333333333333333333333333333 +6887 3333333333333333333333333333333333333333333333333333333 +6888 3333333333333333333333333333333333333333333333333333333 +6889 3333333333333333333333333333333333333333333333333333333 +6890 3333333333333333333333333333333333333333333333333333333 +6891 3333333333333333333333333333333333333333333333333333333 +6892 3333333333333333333333333333333333333333333333333333333 +6893 3333333333333333333333333333333333333333333333333333333 +6894 3333333333333333333333333333333333333333333333333333333 +6895 3333333333333333333333333333333333333333333333333333333 +6896 3333333333333333333333333333333333333333333333333333333 +6897 3333333333333333333333333333333333333333333333333333333 +6898 3333333333333333333333333333333333333333333333333333333 +6899 3333333333333333333333333333333333333333333333333333333 +6900 3333333333333333333333333333333333333333333333333333333 +6901 3333333333333333333333333333333333333333333333333333333 +6902 3333333333333333333333333333333333333333333333333333333 +6903 3333333333333333333333333333333333333333333333333333333 +6904 3333333333333333333333333333333333333333333333333333333 +6905 3333333333333333333333333333333333333333333333333333333 +6906 3333333333333333333333333333333333333333333333333333333 +6907 3333333333333333333333333333333333333333333333333333333 +6908 3333333333333333333333333333333333333333333333333333333 +6909 3333333333333333333333333333333333333333333333333333333 +6910 3333333333333333333333333333333333333333333333333333333 +6911 3333333333333333333333333333333333333333333333333333333 +6912 3333333333333333333333333333333333333333333333333333333 +6913 3333333333333333333333333333333333333333333333333333333 +6914 3333333333333333333333333333333333333333333333333333333 +6915 3333333333333333333333333333333333333333333333333333333 +6916 3333333333333333333333333333333333333333333333333333333 +6917 3333333333333333333333333333333333333333333333333333333 +6918 3333333333333333333333333333333333333333333333333333333 +6919 3333333333333333333333333333333333333333333333333333333 +6920 3333333333333333333333333333333333333333333333333333333 +6921 3333333333333333333333333333333333333333333333333333333 +6922 3333333333333333333333333333333333333333333333333333333 +6923 3333333333333333333333333333333333333333333333333333333 +6924 3333333333333333333333333333333333333333333333333333333 +6925 3333333333333333333333333333333333333333333333333333333 +6926 3333333333333333333333333333333333333333333333333333333 +6927 3333333333333333333333333333333333333333333333333333333 +6928 3333333333333333333333333333333333333333333333333333333 +6929 3333333333333333333333333333333333333333333333333333333 +6930 3333333333333333333333333333333333333333333333333333333 +6931 3333333333333333333333333333333333333333333333333333333 +6932 3333333333333333333333333333333333333333333333333333333 +6933 3333333333333333333333333333333333333333333333333333333 +6934 3333333333333333333333333333333333333333333333333333333 +6935 3333333333333333333333333333333333333333333333333333333 +6936 3333333333333333333333333333333333333333333333333333333 +6937 3333333333333333333333333333333333333333333333333333333 +6938 3333333333333333333333333333333333333333333333333333333 +6939 3333333333333333333333333333333333333333333333333333333 +6940 3333333333333333333333333333333333333333333333333333333 +6941 3333333333333333333333333333333333333333333333333333333 +6942 3333333333333333333333333333333333333333333333333333333 +6943 3333333333333333333333333333333333333333333333333333333 +6944 3333333333333333333333333333333333333333333333333333333 +6945 3333333333333333333333333333333333333333333333333333333 +6946 3333333333333333333333333333333333333333333333333333333 +6947 3333333333333333333333333333333333333333333333333333333 +6948 3333333333333333333333333333333333333333333333333333333 +6949 3333333333333333333333333333333333333333333333333333333 +6950 3333333333333333333333333333333333333333333333333333333 +6951 3333333333333333333333333333333333333333333333333333333 +6952 3333333333333333333333333333333333333333333333333333333 +6953 3333333333333333333333333333333333333333333333333333333 +6954 3333333333333333333333333333333333333333333333333333333 +6955 3333333333333333333333333333333333333333333333333333333 +6956 3333333333333333333333333333333333333333333333333333333 +6957 3333333333333333333333333333333333333333333333333333333 +6958 3333333333333333333333333333333333333333333333333333333 +6959 3333333333333333333333333333333333333333333333333333333 +6960 3333333333333333333333333333333333333333333333333333333 +6961 3333333333333333333333333333333333333333333333333333333 +6962 3333333333333333333333333333333333333333333333333333333 +6963 3333333333333333333333333333333333333333333333333333333 +6964 3333333333333333333333333333333333333333333333333333333 +6965 3333333333333333333333333333333333333333333333333333333 +6966 3333333333333333333333333333333333333333333333333333333 +6967 3333333333333333333333333333333333333333333333333333333 +6968 3333333333333333333333333333333333333333333333333333333 +6969 3333333333333333333333333333333333333333333333333333333 +6970 3333333333333333333333333333333333333333333333333333333 +6971 3333333333333333333333333333333333333333333333333333333 +6972 3333333333333333333333333333333333333333333333333333333 +6973 3333333333333333333333333333333333333333333333333333333 +6974 3333333333333333333333333333333333333333333333333333333 +6975 3333333333333333333333333333333333333333333333333333333 +6976 3333333333333333333333333333333333333333333333333333333 +6977 3333333333333333333333333333333333333333333333333333333 +6978 3333333333333333333333333333333333333333333333333333333 +6979 3333333333333333333333333333333333333333333333333333333 +6980 3333333333333333333333333333333333333333333333333333333 +6981 3333333333333333333333333333333333333333333333333333333 +6982 3333333333333333333333333333333333333333333333333333333 +6983 3333333333333333333333333333333333333333333333333333333 +6984 3333333333333333333333333333333333333333333333333333333 +6985 3333333333333333333333333333333333333333333333333333333 +6986 3333333333333333333333333333333333333333333333333333333 +6987 3333333333333333333333333333333333333333333333333333333 +6988 3333333333333333333333333333333333333333333333333333333 +6989 3333333333333333333333333333333333333333333333333333333 +6990 3333333333333333333333333333333333333333333333333333333 +6991 3333333333333333333333333333333333333333333333333333333 +6992 3333333333333333333333333333333333333333333333333333333 +6993 3333333333333333333333333333333333333333333333333333333 +6994 3333333333333333333333333333333333333333333333333333333 +6995 3333333333333333333333333333333333333333333333333333333 +6996 3333333333333333333333333333333333333333333333333333333 +6997 3333333333333333333333333333333333333333333333333333333 +6998 3333333333333333333333333333333333333333333333333333333 +6999 3333333333333333333333333333333333333333333333333333333 +7000 3333333333333333333333333333333333333333333333333333333 +7001 3333333333333333333333333333333333333333333333333333333 +7002 3333333333333333333333333333333333333333333333333333333 +7003 3333333333333333333333333333333333333333333333333333333 +7004 3333333333333333333333333333333333333333333333333333333 +7005 3333333333333333333333333333333333333333333333333333333 +7006 3333333333333333333333333333333333333333333333333333333 +7007 3333333333333333333333333333333333333333333333333333333 +7008 3333333333333333333333333333333333333333333333333333333 +7009 3333333333333333333333333333333333333333333333333333333 +7010 3333333333333333333333333333333333333333333333333333333 +7011 3333333333333333333333333333333333333333333333333333333 +7012 3333333333333333333333333333333333333333333333333333333 +7013 3333333333333333333333333333333333333333333333333333333 +7014 3333333333333333333333333333333333333333333333333333333 +7015 3333333333333333333333333333333333333333333333333333333 +7016 3333333333333333333333333333333333333333333333333333333 +7017 3333333333333333333333333333333333333333333333333333333 +7018 3333333333333333333333333333333333333333333333333333333 +7019 3333333333333333333333333333333333333333333333333333333 +7020 3333333333333333333333333333333333333333333333333333333 +7021 3333333333333333333333333333333333333333333333333333333 +7022 3333333333333333333333333333333333333333333333333333333 +7023 3333333333333333333333333333333333333333333333333333333 +7024 3333333333333333333333333333333333333333333333333333333 +7025 3333333333333333333333333333333333333333333333333333333 +7026 3333333333333333333333333333333333333333333333333333333 +7027 3333333333333333333333333333333333333333333333333333333 +7028 3333333333333333333333333333333333333333333333333333333 +7029 3333333333333333333333333333333333333333333333333333333 +7030 3333333333333333333333333333333333333333333333333333333 +7031 3333333333333333333333333333333333333333333333333333333 +7032 3333333333333333333333333333333333333333333333333333333 +7033 3333333333333333333333333333333333333333333333333333333 +7034 3333333333333333333333333333333333333333333333333333333 +7035 3333333333333333333333333333333333333333333333333333333 +7036 3333333333333333333333333333333333333333333333333333333 +7037 3333333333333333333333333333333333333333333333333333333 +7038 3333333333333333333333333333333333333333333333333333333 +7039 3333333333333333333333333333333333333333333333333333333 +7040 3333333333333333333333333333333333333333333333333333333 +7041 3333333333333333333333333333333333333333333333333333333 +7042 3333333333333333333333333333333333333333333333333333333 +7043 3333333333333333333333333333333333333333333333333333333 +7044 3333333333333333333333333333333333333333333333333333333 +7045 3333333333333333333333333333333333333333333333333333333 +7046 3333333333333333333333333333333333333333333333333333333 +7047 3333333333333333333333333333333333333333333333333333333 +7048 3333333333333333333333333333333333333333333333333333333 +7049 3333333333333333333333333333333333333333333333333333333 +7050 3333333333333333333333333333333333333333333333333333333 +7051 3333333333333333333333333333333333333333333333333333333 +7052 3333333333333333333333333333333333333333333333333333333 +7053 3333333333333333333333333333333333333333333333333333333 +7054 3333333333333333333333333333333333333333333333333333333 +7055 3333333333333333333333333333333333333333333333333333333 +7056 3333333333333333333333333333333333333333333333333333333 +7057 3333333333333333333333333333333333333333333333333333333 +7058 3333333333333333333333333333333333333333333333333333333 +7059 3333333333333333333333333333333333333333333333333333333 +7060 3333333333333333333333333333333333333333333333333333333 +7061 3333333333333333333333333333333333333333333333333333333 +7062 3333333333333333333333333333333333333333333333333333333 +7063 3333333333333333333333333333333333333333333333333333333 +7064 3333333333333333333333333333333333333333333333333333333 +7065 3333333333333333333333333333333333333333333333333333333 +7066 3333333333333333333333333333333333333333333333333333333 +7067 3333333333333333333333333333333333333333333333333333333 +7068 3333333333333333333333333333333333333333333333333333333 +7069 3333333333333333333333333333333333333333333333333333333 +7070 3333333333333333333333333333333333333333333333333333333 +7071 3333333333333333333333333333333333333333333333333333333 +7072 3333333333333333333333333333333333333333333333333333333 +7073 3333333333333333333333333333333333333333333333333333333 +7074 3333333333333333333333333333333333333333333333333333333 +7075 3333333333333333333333333333333333333333333333333333333 +7076 3333333333333333333333333333333333333333333333333333333 +7077 3333333333333333333333333333333333333333333333333333333 +7078 3333333333333333333333333333333333333333333333333333333 +7079 3333333333333333333333333333333333333333333333333333333 +7080 3333333333333333333333333333333333333333333333333333333 +7081 3333333333333333333333333333333333333333333333333333333 +7082 3333333333333333333333333333333333333333333333333333333 +7083 3333333333333333333333333333333333333333333333333333333 +7084 3333333333333333333333333333333333333333333333333333333 +7085 3333333333333333333333333333333333333333333333333333333 +7086 3333333333333333333333333333333333333333333333333333333 +7087 3333333333333333333333333333333333333333333333333333333 +7088 3333333333333333333333333333333333333333333333333333333 +7089 3333333333333333333333333333333333333333333333333333333 +7090 3333333333333333333333333333333333333333333333333333333 +7091 3333333333333333333333333333333333333333333333333333333 +7092 3333333333333333333333333333333333333333333333333333333 +7093 3333333333333333333333333333333333333333333333333333333 +7094 3333333333333333333333333333333333333333333333333333333 +7095 3333333333333333333333333333333333333333333333333333333 +7096 3333333333333333333333333333333333333333333333333333333 +7097 3333333333333333333333333333333333333333333333333333333 +7098 3333333333333333333333333333333333333333333333333333333 +7099 3333333333333333333333333333333333333333333333333333333 +7100 3333333333333333333333333333333333333333333333333333333 +7101 3333333333333333333333333333333333333333333333333333333 +7102 3333333333333333333333333333333333333333333333333333333 +7103 3333333333333333333333333333333333333333333333333333333 +7104 3333333333333333333333333333333333333333333333333333333 +7105 3333333333333333333333333333333333333333333333333333333 +7106 3333333333333333333333333333333333333333333333333333333 +7107 3333333333333333333333333333333333333333333333333333333 +7108 3333333333333333333333333333333333333333333333333333333 +7109 3333333333333333333333333333333333333333333333333333333 +7110 3333333333333333333333333333333333333333333333333333333 +7111 3333333333333333333333333333333333333333333333333333333 +7112 3333333333333333333333333333333333333333333333333333333 +7113 3333333333333333333333333333333333333333333333333333333 +7114 3333333333333333333333333333333333333333333333333333333 +7115 3333333333333333333333333333333333333333333333333333333 +7116 3333333333333333333333333333333333333333333333333333333 +7117 3333333333333333333333333333333333333333333333333333333 +7118 3333333333333333333333333333333333333333333333333333333 +7119 3333333333333333333333333333333333333333333333333333333 +7120 3333333333333333333333333333333333333333333333333333333 +7121 3333333333333333333333333333333333333333333333333333333 +7122 3333333333333333333333333333333333333333333333333333333 +7123 3333333333333333333333333333333333333333333333333333333 +7124 3333333333333333333333333333333333333333333333333333333 +7125 3333333333333333333333333333333333333333333333333333333 +7126 3333333333333333333333333333333333333333333333333333333 +7127 3333333333333333333333333333333333333333333333333333333 +7128 3333333333333333333333333333333333333333333333333333333 +7129 3333333333333333333333333333333333333333333333333333333 +7130 3333333333333333333333333333333333333333333333333333333 +7131 3333333333333333333333333333333333333333333333333333333 +7132 3333333333333333333333333333333333333333333333333333333 +7133 3333333333333333333333333333333333333333333333333333333 +7134 3333333333333333333333333333333333333333333333333333333 +7135 3333333333333333333333333333333333333333333333333333333 +7136 3333333333333333333333333333333333333333333333333333333 +7137 3333333333333333333333333333333333333333333333333333333 +7138 3333333333333333333333333333333333333333333333333333333 +7139 3333333333333333333333333333333333333333333333333333333 +7140 3333333333333333333333333333333333333333333333333333333 +7141 3333333333333333333333333333333333333333333333333333333 +7142 3333333333333333333333333333333333333333333333333333333 +7143 3333333333333333333333333333333333333333333333333333333 +7144 3333333333333333333333333333333333333333333333333333333 +7145 3333333333333333333333333333333333333333333333333333333 +7146 3333333333333333333333333333333333333333333333333333333 +7147 3333333333333333333333333333333333333333333333333333333 +7148 3333333333333333333333333333333333333333333333333333333 +7149 3333333333333333333333333333333333333333333333333333333 +7150 3333333333333333333333333333333333333333333333333333333 +7151 3333333333333333333333333333333333333333333333333333333 +7152 3333333333333333333333333333333333333333333333333333333 +7153 3333333333333333333333333333333333333333333333333333333 +7154 3333333333333333333333333333333333333333333333333333333 +7155 3333333333333333333333333333333333333333333333333333333 +7156 3333333333333333333333333333333333333333333333333333333 +7157 3333333333333333333333333333333333333333333333333333333 +7158 3333333333333333333333333333333333333333333333333333333 +7159 3333333333333333333333333333333333333333333333333333333 +7160 3333333333333333333333333333333333333333333333333333333 +7161 3333333333333333333333333333333333333333333333333333333 +7162 3333333333333333333333333333333333333333333333333333333 +7163 3333333333333333333333333333333333333333333333333333333 +7164 3333333333333333333333333333333333333333333333333333333 +7165 3333333333333333333333333333333333333333333333333333333 +7166 3333333333333333333333333333333333333333333333333333333 +7167 3333333333333333333333333333333333333333333333333333333 +7168 3333333333333333333333333333333333333333333333333333333 +7169 3333333333333333333333333333333333333333333333333333333 +7170 3333333333333333333333333333333333333333333333333333333 +7171 3333333333333333333333333333333333333333333333333333333 +7172 3333333333333333333333333333333333333333333333333333333 +7173 3333333333333333333333333333333333333333333333333333333 +7174 3333333333333333333333333333333333333333333333333333333 +7175 3333333333333333333333333333333333333333333333333333333 +7176 3333333333333333333333333333333333333333333333333333333 +7177 3333333333333333333333333333333333333333333333333333333 +7178 3333333333333333333333333333333333333333333333333333333 +7179 3333333333333333333333333333333333333333333333333333333 +7180 3333333333333333333333333333333333333333333333333333333 +7181 3333333333333333333333333333333333333333333333333333333 +7182 3333333333333333333333333333333333333333333333333333333 +7183 3333333333333333333333333333333333333333333333333333333 +7184 3333333333333333333333333333333333333333333333333333333 +7185 3333333333333333333333333333333333333333333333333333333 +7186 3333333333333333333333333333333333333333333333333333333 +7187 3333333333333333333333333333333333333333333333333333333 +7188 3333333333333333333333333333333333333333333333333333333 +7189 3333333333333333333333333333333333333333333333333333333 +7190 3333333333333333333333333333333333333333333333333333333 +7191 3333333333333333333333333333333333333333333333333333333 +7192 3333333333333333333333333333333333333333333333333333333 +7193 3333333333333333333333333333333333333333333333333333333 +7194 3333333333333333333333333333333333333333333333333333333 +7195 3333333333333333333333333333333333333333333333333333333 +7196 3333333333333333333333333333333333333333333333333333333 +7197 3333333333333333333333333333333333333333333333333333333 +7198 3333333333333333333333333333333333333333333333333333333 +7199 3333333333333333333333333333333333333333333333333333333 +7200 3333333333333333333333333333333333333333333333333333333 +7201 3333333333333333333333333333333333333333333333333333333 +7202 3333333333333333333333333333333333333333333333333333333 +7203 3333333333333333333333333333333333333333333333333333333 +7204 3333333333333333333333333333333333333333333333333333333 +7205 3333333333333333333333333333333333333333333333333333333 +7206 3333333333333333333333333333333333333333333333333333333 +7207 3333333333333333333333333333333333333333333333333333333 +7208 3333333333333333333333333333333333333333333333333333333 +7209 3333333333333333333333333333333333333333333333333333333 +7210 3333333333333333333333333333333333333333333333333333333 +7211 3333333333333333333333333333333333333333333333333333333 +7212 3333333333333333333333333333333333333333333333333333333 +7213 3333333333333333333333333333333333333333333333333333333 +7214 3333333333333333333333333333333333333333333333333333333 +7215 3333333333333333333333333333333333333333333333333333333 +7216 3333333333333333333333333333333333333333333333333333333 +7217 3333333333333333333333333333333333333333333333333333333 +7218 3333333333333333333333333333333333333333333333333333333 +7219 3333333333333333333333333333333333333333333333333333333 +7220 3333333333333333333333333333333333333333333333333333333 +7221 3333333333333333333333333333333333333333333333333333333 +7222 3333333333333333333333333333333333333333333333333333333 +7223 3333333333333333333333333333333333333333333333333333333 +7224 3333333333333333333333333333333333333333333333333333333 +7225 3333333333333333333333333333333333333333333333333333333 +7226 3333333333333333333333333333333333333333333333333333333 +7227 3333333333333333333333333333333333333333333333333333333 +7228 3333333333333333333333333333333333333333333333333333333 +7229 3333333333333333333333333333333333333333333333333333333 +7230 3333333333333333333333333333333333333333333333333333333 +7231 3333333333333333333333333333333333333333333333333333333 +7232 3333333333333333333333333333333333333333333333333333333 +7233 3333333333333333333333333333333333333333333333333333333 +7234 3333333333333333333333333333333333333333333333333333333 +7235 3333333333333333333333333333333333333333333333333333333 +7236 3333333333333333333333333333333333333333333333333333333 +7237 3333333333333333333333333333333333333333333333333333333 +7238 3333333333333333333333333333333333333333333333333333333 +7239 3333333333333333333333333333333333333333333333333333333 +7240 3333333333333333333333333333333333333333333333333333333 +7241 3333333333333333333333333333333333333333333333333333333 +7242 3333333333333333333333333333333333333333333333333333333 +7243 3333333333333333333333333333333333333333333333333333333 +7244 3333333333333333333333333333333333333333333333333333333 +7245 3333333333333333333333333333333333333333333333333333333 +7246 3333333333333333333333333333333333333333333333333333333 +7247 3333333333333333333333333333333333333333333333333333333 +7248 3333333333333333333333333333333333333333333333333333333 +7249 3333333333333333333333333333333333333333333333333333333 +7250 3333333333333333333333333333333333333333333333333333333 +7251 3333333333333333333333333333333333333333333333333333333 +7252 3333333333333333333333333333333333333333333333333333333 +7253 3333333333333333333333333333333333333333333333333333333 +7254 3333333333333333333333333333333333333333333333333333333 +7255 3333333333333333333333333333333333333333333333333333333 +7256 3333333333333333333333333333333333333333333333333333333 +7257 3333333333333333333333333333333333333333333333333333333 +7258 3333333333333333333333333333333333333333333333333333333 +7259 3333333333333333333333333333333333333333333333333333333 +7260 3333333333333333333333333333333333333333333333333333333 +7261 3333333333333333333333333333333333333333333333333333333 +7262 3333333333333333333333333333333333333333333333333333333 +7263 3333333333333333333333333333333333333333333333333333333 +7264 3333333333333333333333333333333333333333333333333333333 +7265 3333333333333333333333333333333333333333333333333333333 +7266 3333333333333333333333333333333333333333333333333333333 +7267 3333333333333333333333333333333333333333333333333333333 +7268 3333333333333333333333333333333333333333333333333333333 +7269 3333333333333333333333333333333333333333333333333333333 +7270 3333333333333333333333333333333333333333333333333333333 +7271 3333333333333333333333333333333333333333333333333333333 +7272 3333333333333333333333333333333333333333333333333333333 +7273 3333333333333333333333333333333333333333333333333333333 +7274 3333333333333333333333333333333333333333333333333333333 +7275 3333333333333333333333333333333333333333333333333333333 +7276 3333333333333333333333333333333333333333333333333333333 +7277 3333333333333333333333333333333333333333333333333333333 +7278 3333333333333333333333333333333333333333333333333333333 +7279 3333333333333333333333333333333333333333333333333333333 +7280 3333333333333333333333333333333333333333333333333333333 +7281 3333333333333333333333333333333333333333333333333333333 +7282 3333333333333333333333333333333333333333333333333333333 +7283 3333333333333333333333333333333333333333333333333333333 +7284 3333333333333333333333333333333333333333333333333333333 +7285 3333333333333333333333333333333333333333333333333333333 +7286 3333333333333333333333333333333333333333333333333333333 +7287 3333333333333333333333333333333333333333333333333333333 +7288 3333333333333333333333333333333333333333333333333333333 +7289 3333333333333333333333333333333333333333333333333333333 +7290 3333333333333333333333333333333333333333333333333333333 +7291 3333333333333333333333333333333333333333333333333333333 +7292 3333333333333333333333333333333333333333333333333333333 +7293 3333333333333333333333333333333333333333333333333333333 +7294 3333333333333333333333333333333333333333333333333333333 +7295 3333333333333333333333333333333333333333333333333333333 +7296 3333333333333333333333333333333333333333333333333333333 +7297 3333333333333333333333333333333333333333333333333333333 +7298 3333333333333333333333333333333333333333333333333333333 +7299 3333333333333333333333333333333333333333333333333333333 +7300 3333333333333333333333333333333333333333333333333333333 +7301 3333333333333333333333333333333333333333333333333333333 +7302 3333333333333333333333333333333333333333333333333333333 +7303 3333333333333333333333333333333333333333333333333333333 +7304 3333333333333333333333333333333333333333333333333333333 +7305 3333333333333333333333333333333333333333333333333333333 +7306 3333333333333333333333333333333333333333333333333333333 +7307 3333333333333333333333333333333333333333333333333333333 +7308 3333333333333333333333333333333333333333333333333333333 +7309 3333333333333333333333333333333333333333333333333333333 +7310 3333333333333333333333333333333333333333333333333333333 +7311 3333333333333333333333333333333333333333333333333333333 +7312 3333333333333333333333333333333333333333333333333333333 +7313 3333333333333333333333333333333333333333333333333333333 +7314 3333333333333333333333333333333333333333333333333333333 +7315 3333333333333333333333333333333333333333333333333333333 +7316 3333333333333333333333333333333333333333333333333333333 +7317 3333333333333333333333333333333333333333333333333333333 +7318 3333333333333333333333333333333333333333333333333333333 +7319 3333333333333333333333333333333333333333333333333333333 +7320 3333333333333333333333333333333333333333333333333333333 +7321 3333333333333333333333333333333333333333333333333333333 +7322 3333333333333333333333333333333333333333333333333333333 +7323 3333333333333333333333333333333333333333333333333333333 +7324 3333333333333333333333333333333333333333333333333333333 +7325 3333333333333333333333333333333333333333333333333333333 +7326 3333333333333333333333333333333333333333333333333333333 +7327 3333333333333333333333333333333333333333333333333333333 +7328 3333333333333333333333333333333333333333333333333333333 +7329 3333333333333333333333333333333333333333333333333333333 +7330 3333333333333333333333333333333333333333333333333333333 +7331 3333333333333333333333333333333333333333333333333333333 +7332 3333333333333333333333333333333333333333333333333333333 +7333 3333333333333333333333333333333333333333333333333333333 +7334 3333333333333333333333333333333333333333333333333333333 +7335 3333333333333333333333333333333333333333333333333333333 +7336 3333333333333333333333333333333333333333333333333333333 +7337 3333333333333333333333333333333333333333333333333333333 +7338 3333333333333333333333333333333333333333333333333333333 +7339 3333333333333333333333333333333333333333333333333333333 +7340 3333333333333333333333333333333333333333333333333333333 +7341 3333333333333333333333333333333333333333333333333333333 +7342 3333333333333333333333333333333333333333333333333333333 +7343 3333333333333333333333333333333333333333333333333333333 +7344 3333333333333333333333333333333333333333333333333333333 +7345 3333333333333333333333333333333333333333333333333333333 +7346 3333333333333333333333333333333333333333333333333333333 +7347 3333333333333333333333333333333333333333333333333333333 +7348 3333333333333333333333333333333333333333333333333333333 +7349 3333333333333333333333333333333333333333333333333333333 +7350 3333333333333333333333333333333333333333333333333333333 +7351 3333333333333333333333333333333333333333333333333333333 +7352 3333333333333333333333333333333333333333333333333333333 +7353 3333333333333333333333333333333333333333333333333333333 +7354 3333333333333333333333333333333333333333333333333333333 +7355 3333333333333333333333333333333333333333333333333333333 +7356 3333333333333333333333333333333333333333333333333333333 +7357 3333333333333333333333333333333333333333333333333333333 +7358 3333333333333333333333333333333333333333333333333333333 +7359 3333333333333333333333333333333333333333333333333333333 +7360 3333333333333333333333333333333333333333333333333333333 +7361 3333333333333333333333333333333333333333333333333333333 +7362 3333333333333333333333333333333333333333333333333333333 +7363 3333333333333333333333333333333333333333333333333333333 +7364 3333333333333333333333333333333333333333333333333333333 +7365 3333333333333333333333333333333333333333333333333333333 +7366 3333333333333333333333333333333333333333333333333333333 +7367 3333333333333333333333333333333333333333333333333333333 +7368 3333333333333333333333333333333333333333333333333333333 +7369 3333333333333333333333333333333333333333333333333333333 +7370 3333333333333333333333333333333333333333333333333333333 +7371 3333333333333333333333333333333333333333333333333333333 +7372 3333333333333333333333333333333333333333333333333333333 +7373 3333333333333333333333333333333333333333333333333333333 +7374 3333333333333333333333333333333333333333333333333333333 +7375 3333333333333333333333333333333333333333333333333333333 +7376 3333333333333333333333333333333333333333333333333333333 +7377 3333333333333333333333333333333333333333333333333333333 +7378 3333333333333333333333333333333333333333333333333333333 +7379 3333333333333333333333333333333333333333333333333333333 +7380 3333333333333333333333333333333333333333333333333333333 +7381 3333333333333333333333333333333333333333333333333333333 +7382 3333333333333333333333333333333333333333333333333333333 +7383 3333333333333333333333333333333333333333333333333333333 +7384 3333333333333333333333333333333333333333333333333333333 +7385 3333333333333333333333333333333333333333333333333333333 +7386 3333333333333333333333333333333333333333333333333333333 +7387 3333333333333333333333333333333333333333333333333333333 +7388 3333333333333333333333333333333333333333333333333333333 +7389 3333333333333333333333333333333333333333333333333333333 +7390 3333333333333333333333333333333333333333333333333333333 +7391 3333333333333333333333333333333333333333333333333333333 +7392 3333333333333333333333333333333333333333333333333333333 +7393 3333333333333333333333333333333333333333333333333333333 +7394 3333333333333333333333333333333333333333333333333333333 +7395 3333333333333333333333333333333333333333333333333333333 +7396 3333333333333333333333333333333333333333333333333333333 +7397 3333333333333333333333333333333333333333333333333333333 +7398 3333333333333333333333333333333333333333333333333333333 +7399 3333333333333333333333333333333333333333333333333333333 +7400 3333333333333333333333333333333333333333333333333333333 +7401 3333333333333333333333333333333333333333333333333333333 +7402 3333333333333333333333333333333333333333333333333333333 +7403 3333333333333333333333333333333333333333333333333333333 +7404 3333333333333333333333333333333333333333333333333333333 +7405 3333333333333333333333333333333333333333333333333333333 +7406 3333333333333333333333333333333333333333333333333333333 +7407 3333333333333333333333333333333333333333333333333333333 +7408 3333333333333333333333333333333333333333333333333333333 +7409 3333333333333333333333333333333333333333333333333333333 +7410 3333333333333333333333333333333333333333333333333333333 +7411 3333333333333333333333333333333333333333333333333333333 +7412 3333333333333333333333333333333333333333333333333333333 +7413 3333333333333333333333333333333333333333333333333333333 +7414 3333333333333333333333333333333333333333333333333333333 +7415 3333333333333333333333333333333333333333333333333333333 +7416 3333333333333333333333333333333333333333333333333333333 +7417 3333333333333333333333333333333333333333333333333333333 +7418 3333333333333333333333333333333333333333333333333333333 +7419 3333333333333333333333333333333333333333333333333333333 +7420 3333333333333333333333333333333333333333333333333333333 +7421 3333333333333333333333333333333333333333333333333333333 +7422 3333333333333333333333333333333333333333333333333333333 +7423 3333333333333333333333333333333333333333333333333333333 +7424 3333333333333333333333333333333333333333333333333333333 +7425 3333333333333333333333333333333333333333333333333333333 +7426 3333333333333333333333333333333333333333333333333333333 +7427 3333333333333333333333333333333333333333333333333333333 +7428 3333333333333333333333333333333333333333333333333333333 +7429 3333333333333333333333333333333333333333333333333333333 +7430 3333333333333333333333333333333333333333333333333333333 +7431 3333333333333333333333333333333333333333333333333333333 +7432 3333333333333333333333333333333333333333333333333333333 +7433 3333333333333333333333333333333333333333333333333333333 +7434 3333333333333333333333333333333333333333333333333333333 +7435 3333333333333333333333333333333333333333333333333333333 +7436 3333333333333333333333333333333333333333333333333333333 +7437 3333333333333333333333333333333333333333333333333333333 +7438 3333333333333333333333333333333333333333333333333333333 +7439 3333333333333333333333333333333333333333333333333333333 +7440 3333333333333333333333333333333333333333333333333333333 +7441 3333333333333333333333333333333333333333333333333333333 +7442 3333333333333333333333333333333333333333333333333333333 +7443 3333333333333333333333333333333333333333333333333333333 +7444 3333333333333333333333333333333333333333333333333333333 +7445 3333333333333333333333333333333333333333333333333333333 +7446 3333333333333333333333333333333333333333333333333333333 +7447 3333333333333333333333333333333333333333333333333333333 +7448 3333333333333333333333333333333333333333333333333333333 +7449 3333333333333333333333333333333333333333333333333333333 +7450 3333333333333333333333333333333333333333333333333333333 +7451 3333333333333333333333333333333333333333333333333333333 +7452 3333333333333333333333333333333333333333333333333333333 +7453 3333333333333333333333333333333333333333333333333333333 +7454 3333333333333333333333333333333333333333333333333333333 +7455 3333333333333333333333333333333333333333333333333333333 +7456 3333333333333333333333333333333333333333333333333333333 +7457 3333333333333333333333333333333333333333333333333333333 +7458 3333333333333333333333333333333333333333333333333333333 +7459 3333333333333333333333333333333333333333333333333333333 +7460 3333333333333333333333333333333333333333333333333333333 +7461 3333333333333333333333333333333333333333333333333333333 +7462 3333333333333333333333333333333333333333333333333333333 +7463 3333333333333333333333333333333333333333333333333333333 +7464 3333333333333333333333333333333333333333333333333333333 +7465 3333333333333333333333333333333333333333333333333333333 +7466 3333333333333333333333333333333333333333333333333333333 +7467 3333333333333333333333333333333333333333333333333333333 +7468 3333333333333333333333333333333333333333333333333333333 +7469 3333333333333333333333333333333333333333333333333333333 +7470 3333333333333333333333333333333333333333333333333333333 +7471 3333333333333333333333333333333333333333333333333333333 +7472 3333333333333333333333333333333333333333333333333333333 +7473 3333333333333333333333333333333333333333333333333333333 +7474 3333333333333333333333333333333333333333333333333333333 +7475 3333333333333333333333333333333333333333333333333333333 +7476 3333333333333333333333333333333333333333333333333333333 +7477 3333333333333333333333333333333333333333333333333333333 +7478 3333333333333333333333333333333333333333333333333333333 +7479 3333333333333333333333333333333333333333333333333333333 +7480 3333333333333333333333333333333333333333333333333333333 +7481 3333333333333333333333333333333333333333333333333333333 +7482 3333333333333333333333333333333333333333333333333333333 +7483 3333333333333333333333333333333333333333333333333333333 +7484 3333333333333333333333333333333333333333333333333333333 +7485 3333333333333333333333333333333333333333333333333333333 +7486 3333333333333333333333333333333333333333333333333333333 +7487 3333333333333333333333333333333333333333333333333333333 +7488 3333333333333333333333333333333333333333333333333333333 +7489 3333333333333333333333333333333333333333333333333333333 +7490 3333333333333333333333333333333333333333333333333333333 +7491 3333333333333333333333333333333333333333333333333333333 +7492 3333333333333333333333333333333333333333333333333333333 +7493 3333333333333333333333333333333333333333333333333333333 +7494 3333333333333333333333333333333333333333333333333333333 +7495 3333333333333333333333333333333333333333333333333333333 +7496 3333333333333333333333333333333333333333333333333333333 +7497 3333333333333333333333333333333333333333333333333333333 +7498 3333333333333333333333333333333333333333333333333333333 +7499 3333333333333333333333333333333333333333333333333333333 +7500 3333333333333333333333333333333333333333333333333333333 +7501 3333333333333333333333333333333333333333333333333333333 +7502 3333333333333333333333333333333333333333333333333333333 +7503 3333333333333333333333333333333333333333333333333333333 +7504 3333333333333333333333333333333333333333333333333333333 +7505 3333333333333333333333333333333333333333333333333333333 +7506 3333333333333333333333333333333333333333333333333333333 +7507 3333333333333333333333333333333333333333333333333333333 +7508 3333333333333333333333333333333333333333333333333333333 +7509 3333333333333333333333333333333333333333333333333333333 +7510 3333333333333333333333333333333333333333333333333333333 +7511 3333333333333333333333333333333333333333333333333333333 +7512 3333333333333333333333333333333333333333333333333333333 +7513 3333333333333333333333333333333333333333333333333333333 +7514 3333333333333333333333333333333333333333333333333333333 +7515 3333333333333333333333333333333333333333333333333333333 +7516 3333333333333333333333333333333333333333333333333333333 +7517 3333333333333333333333333333333333333333333333333333333 +7518 3333333333333333333333333333333333333333333333333333333 +7519 3333333333333333333333333333333333333333333333333333333 +7520 3333333333333333333333333333333333333333333333333333333 +7521 3333333333333333333333333333333333333333333333333333333 +7522 3333333333333333333333333333333333333333333333333333333 +7523 3333333333333333333333333333333333333333333333333333333 +7524 3333333333333333333333333333333333333333333333333333333 +7525 3333333333333333333333333333333333333333333333333333333 +7526 3333333333333333333333333333333333333333333333333333333 +7527 3333333333333333333333333333333333333333333333333333333 +7528 3333333333333333333333333333333333333333333333333333333 +7529 3333333333333333333333333333333333333333333333333333333 +7530 3333333333333333333333333333333333333333333333333333333 +7531 3333333333333333333333333333333333333333333333333333333 +7532 3333333333333333333333333333333333333333333333333333333 +7533 3333333333333333333333333333333333333333333333333333333 +7534 3333333333333333333333333333333333333333333333333333333 +7535 3333333333333333333333333333333333333333333333333333333 +7536 3333333333333333333333333333333333333333333333333333333 +7537 3333333333333333333333333333333333333333333333333333333 +7538 3333333333333333333333333333333333333333333333333333333 +7539 3333333333333333333333333333333333333333333333333333333 +7540 3333333333333333333333333333333333333333333333333333333 +7541 3333333333333333333333333333333333333333333333333333333 +7542 3333333333333333333333333333333333333333333333333333333 +7543 3333333333333333333333333333333333333333333333333333333 +7544 3333333333333333333333333333333333333333333333333333333 +7545 3333333333333333333333333333333333333333333333333333333 +7546 3333333333333333333333333333333333333333333333333333333 +7547 3333333333333333333333333333333333333333333333333333333 +7548 3333333333333333333333333333333333333333333333333333333 +7549 3333333333333333333333333333333333333333333333333333333 +7550 3333333333333333333333333333333333333333333333333333333 +7551 3333333333333333333333333333333333333333333333333333333 +7552 3333333333333333333333333333333333333333333333333333333 +7553 3333333333333333333333333333333333333333333333333333333 +7554 3333333333333333333333333333333333333333333333333333333 +7555 3333333333333333333333333333333333333333333333333333333 +7556 3333333333333333333333333333333333333333333333333333333 +7557 3333333333333333333333333333333333333333333333333333333 +7558 3333333333333333333333333333333333333333333333333333333 +7559 3333333333333333333333333333333333333333333333333333333 +7560 3333333333333333333333333333333333333333333333333333333 +7561 3333333333333333333333333333333333333333333333333333333 +7562 3333333333333333333333333333333333333333333333333333333 +7563 3333333333333333333333333333333333333333333333333333333 +7564 3333333333333333333333333333333333333333333333333333333 +7565 3333333333333333333333333333333333333333333333333333333 +7566 3333333333333333333333333333333333333333333333333333333 +7567 3333333333333333333333333333333333333333333333333333333 +7568 3333333333333333333333333333333333333333333333333333333 +7569 3333333333333333333333333333333333333333333333333333333 +7570 3333333333333333333333333333333333333333333333333333333 +7571 3333333333333333333333333333333333333333333333333333333 +7572 3333333333333333333333333333333333333333333333333333333 +7573 3333333333333333333333333333333333333333333333333333333 +7574 3333333333333333333333333333333333333333333333333333333 +7575 3333333333333333333333333333333333333333333333333333333 +7576 3333333333333333333333333333333333333333333333333333333 +7577 3333333333333333333333333333333333333333333333333333333 +7578 3333333333333333333333333333333333333333333333333333333 +7579 3333333333333333333333333333333333333333333333333333333 +7580 3333333333333333333333333333333333333333333333333333333 +7581 3333333333333333333333333333333333333333333333333333333 +7582 3333333333333333333333333333333333333333333333333333333 +7583 3333333333333333333333333333333333333333333333333333333 +7584 3333333333333333333333333333333333333333333333333333333 +7585 3333333333333333333333333333333333333333333333333333333 +7586 3333333333333333333333333333333333333333333333333333333 +7587 3333333333333333333333333333333333333333333333333333333 +7588 3333333333333333333333333333333333333333333333333333333 +7589 3333333333333333333333333333333333333333333333333333333 +7590 3333333333333333333333333333333333333333333333333333333 +7591 3333333333333333333333333333333333333333333333333333333 +7592 3333333333333333333333333333333333333333333333333333333 +7593 3333333333333333333333333333333333333333333333333333333 +7594 3333333333333333333333333333333333333333333333333333333 +7595 3333333333333333333333333333333333333333333333333333333 +7596 3333333333333333333333333333333333333333333333333333333 +7597 3333333333333333333333333333333333333333333333333333333 +7598 3333333333333333333333333333333333333333333333333333333 +7599 3333333333333333333333333333333333333333333333333333333 +7600 3333333333333333333333333333333333333333333333333333333 +7601 3333333333333333333333333333333333333333333333333333333 +7602 3333333333333333333333333333333333333333333333333333333 +7603 3333333333333333333333333333333333333333333333333333333 +7604 3333333333333333333333333333333333333333333333333333333 +7605 3333333333333333333333333333333333333333333333333333333 +7606 3333333333333333333333333333333333333333333333333333333 +7607 3333333333333333333333333333333333333333333333333333333 +7608 3333333333333333333333333333333333333333333333333333333 +7609 3333333333333333333333333333333333333333333333333333333 +7610 3333333333333333333333333333333333333333333333333333333 +7611 3333333333333333333333333333333333333333333333333333333 +7612 3333333333333333333333333333333333333333333333333333333 +7613 3333333333333333333333333333333333333333333333333333333 +7614 3333333333333333333333333333333333333333333333333333333 +7615 3333333333333333333333333333333333333333333333333333333 +7616 3333333333333333333333333333333333333333333333333333333 +7617 3333333333333333333333333333333333333333333333333333333 +7618 3333333333333333333333333333333333333333333333333333333 +7619 3333333333333333333333333333333333333333333333333333333 +7620 3333333333333333333333333333333333333333333333333333333 +7621 3333333333333333333333333333333333333333333333333333333 +7622 3333333333333333333333333333333333333333333333333333333 +7623 3333333333333333333333333333333333333333333333333333333 +7624 3333333333333333333333333333333333333333333333333333333 +7625 3333333333333333333333333333333333333333333333333333333 +7626 3333333333333333333333333333333333333333333333333333333 +7627 3333333333333333333333333333333333333333333333333333333 +7628 3333333333333333333333333333333333333333333333333333333 +7629 3333333333333333333333333333333333333333333333333333333 +7630 3333333333333333333333333333333333333333333333333333333 +7631 3333333333333333333333333333333333333333333333333333333 +7632 3333333333333333333333333333333333333333333333333333333 +7633 3333333333333333333333333333333333333333333333333333333 +7634 3333333333333333333333333333333333333333333333333333333 +7635 3333333333333333333333333333333333333333333333333333333 +7636 3333333333333333333333333333333333333333333333333333333 +7637 3333333333333333333333333333333333333333333333333333333 +7638 3333333333333333333333333333333333333333333333333333333 +7639 3333333333333333333333333333333333333333333333333333333 +7640 3333333333333333333333333333333333333333333333333333333 +7641 3333333333333333333333333333333333333333333333333333333 +7642 3333333333333333333333333333333333333333333333333333333 +7643 3333333333333333333333333333333333333333333333333333333 +7644 3333333333333333333333333333333333333333333333333333333 +7645 3333333333333333333333333333333333333333333333333333333 +7646 3333333333333333333333333333333333333333333333333333333 +7647 3333333333333333333333333333333333333333333333333333333 +7648 3333333333333333333333333333333333333333333333333333333 +7649 3333333333333333333333333333333333333333333333333333333 +7650 3333333333333333333333333333333333333333333333333333333 +7651 3333333333333333333333333333333333333333333333333333333 +7652 3333333333333333333333333333333333333333333333333333333 +7653 3333333333333333333333333333333333333333333333333333333 +7654 3333333333333333333333333333333333333333333333333333333 +7655 3333333333333333333333333333333333333333333333333333333 +7656 3333333333333333333333333333333333333333333333333333333 +7657 3333333333333333333333333333333333333333333333333333333 +7658 3333333333333333333333333333333333333333333333333333333 +7659 3333333333333333333333333333333333333333333333333333333 +7660 3333333333333333333333333333333333333333333333333333333 +7661 3333333333333333333333333333333333333333333333333333333 +7662 3333333333333333333333333333333333333333333333333333333 +7663 3333333333333333333333333333333333333333333333333333333 +7664 3333333333333333333333333333333333333333333333333333333 +7665 3333333333333333333333333333333333333333333333333333333 +7666 3333333333333333333333333333333333333333333333333333333 +7667 3333333333333333333333333333333333333333333333333333333 +7668 3333333333333333333333333333333333333333333333333333333 +7669 3333333333333333333333333333333333333333333333333333333 +7670 3333333333333333333333333333333333333333333333333333333 +7671 3333333333333333333333333333333333333333333333333333333 +7672 3333333333333333333333333333333333333333333333333333333 +7673 3333333333333333333333333333333333333333333333333333333 +7674 3333333333333333333333333333333333333333333333333333333 +7675 3333333333333333333333333333333333333333333333333333333 +7676 3333333333333333333333333333333333333333333333333333333 +7677 3333333333333333333333333333333333333333333333333333333 +7678 3333333333333333333333333333333333333333333333333333333 +7679 3333333333333333333333333333333333333333333333333333333 +7680 3333333333333333333333333333333333333333333333333333333 +7681 3333333333333333333333333333333333333333333333333333333 +7682 3333333333333333333333333333333333333333333333333333333 +7683 3333333333333333333333333333333333333333333333333333333 +7684 3333333333333333333333333333333333333333333333333333333 +7685 3333333333333333333333333333333333333333333333333333333 +7686 3333333333333333333333333333333333333333333333333333333 +7687 3333333333333333333333333333333333333333333333333333333 +7688 3333333333333333333333333333333333333333333333333333333 +7689 3333333333333333333333333333333333333333333333333333333 +7690 3333333333333333333333333333333333333333333333333333333 +7691 3333333333333333333333333333333333333333333333333333333 +7692 3333333333333333333333333333333333333333333333333333333 +7693 3333333333333333333333333333333333333333333333333333333 +7694 3333333333333333333333333333333333333333333333333333333 +7695 3333333333333333333333333333333333333333333333333333333 +7696 3333333333333333333333333333333333333333333333333333333 +7697 3333333333333333333333333333333333333333333333333333333 +7698 3333333333333333333333333333333333333333333333333333333 +7699 3333333333333333333333333333333333333333333333333333333 +7700 3333333333333333333333333333333333333333333333333333333 +7701 3333333333333333333333333333333333333333333333333333333 +7702 3333333333333333333333333333333333333333333333333333333 +7703 3333333333333333333333333333333333333333333333333333333 +7704 3333333333333333333333333333333333333333333333333333333 +7705 3333333333333333333333333333333333333333333333333333333 +7706 3333333333333333333333333333333333333333333333333333333 +7707 3333333333333333333333333333333333333333333333333333333 +7708 3333333333333333333333333333333333333333333333333333333 +7709 3333333333333333333333333333333333333333333333333333333 +7710 3333333333333333333333333333333333333333333333333333333 +7711 3333333333333333333333333333333333333333333333333333333 +7712 3333333333333333333333333333333333333333333333333333333 +7713 3333333333333333333333333333333333333333333333333333333 +7714 3333333333333333333333333333333333333333333333333333333 +7715 3333333333333333333333333333333333333333333333333333333 +7716 3333333333333333333333333333333333333333333333333333333 +7717 3333333333333333333333333333333333333333333333333333333 +7718 3333333333333333333333333333333333333333333333333333333 +7719 3333333333333333333333333333333333333333333333333333333 +7720 3333333333333333333333333333333333333333333333333333333 +7721 3333333333333333333333333333333333333333333333333333333 +7722 3333333333333333333333333333333333333333333333333333333 +7723 3333333333333333333333333333333333333333333333333333333 +7724 3333333333333333333333333333333333333333333333333333333 +7725 3333333333333333333333333333333333333333333333333333333 +7726 3333333333333333333333333333333333333333333333333333333 +7727 3333333333333333333333333333333333333333333333333333333 +7728 3333333333333333333333333333333333333333333333333333333 +7729 3333333333333333333333333333333333333333333333333333333 +7730 3333333333333333333333333333333333333333333333333333333 +7731 3333333333333333333333333333333333333333333333333333333 +7732 3333333333333333333333333333333333333333333333333333333 +7733 3333333333333333333333333333333333333333333333333333333 +7734 3333333333333333333333333333333333333333333333333333333 +7735 3333333333333333333333333333333333333333333333333333333 +7736 3333333333333333333333333333333333333333333333333333333 +7737 3333333333333333333333333333333333333333333333333333333 +7738 3333333333333333333333333333333333333333333333333333333 +7739 3333333333333333333333333333333333333333333333333333333 +7740 3333333333333333333333333333333333333333333333333333333 +7741 3333333333333333333333333333333333333333333333333333333 +7742 3333333333333333333333333333333333333333333333333333333 +7743 3333333333333333333333333333333333333333333333333333333 +7744 3333333333333333333333333333333333333333333333333333333 +7745 3333333333333333333333333333333333333333333333333333333 +7746 3333333333333333333333333333333333333333333333333333333 +7747 3333333333333333333333333333333333333333333333333333333 +7748 3333333333333333333333333333333333333333333333333333333 +7749 3333333333333333333333333333333333333333333333333333333 +7750 3333333333333333333333333333333333333333333333333333333 +7751 3333333333333333333333333333333333333333333333333333333 +7752 3333333333333333333333333333333333333333333333333333333 +7753 3333333333333333333333333333333333333333333333333333333 +7754 3333333333333333333333333333333333333333333333333333333 +7755 3333333333333333333333333333333333333333333333333333333 +7756 3333333333333333333333333333333333333333333333333333333 +7757 3333333333333333333333333333333333333333333333333333333 +7758 3333333333333333333333333333333333333333333333333333333 +7759 3333333333333333333333333333333333333333333333333333333 +7760 3333333333333333333333333333333333333333333333333333333 +7761 3333333333333333333333333333333333333333333333333333333 +7762 3333333333333333333333333333333333333333333333333333333 +7763 3333333333333333333333333333333333333333333333333333333 +7764 3333333333333333333333333333333333333333333333333333333 +7765 3333333333333333333333333333333333333333333333333333333 +7766 3333333333333333333333333333333333333333333333333333333 +7767 3333333333333333333333333333333333333333333333333333333 +7768 3333333333333333333333333333333333333333333333333333333 +7769 3333333333333333333333333333333333333333333333333333333 +7770 3333333333333333333333333333333333333333333333333333333 +7771 3333333333333333333333333333333333333333333333333333333 +7772 3333333333333333333333333333333333333333333333333333333 +7773 3333333333333333333333333333333333333333333333333333333 +7774 3333333333333333333333333333333333333333333333333333333 +7775 3333333333333333333333333333333333333333333333333333333 +7776 3333333333333333333333333333333333333333333333333333333 +7777 3333333333333333333333333333333333333333333333333333333 +7778 3333333333333333333333333333333333333333333333333333333 +7779 3333333333333333333333333333333333333333333333333333333 +7780 3333333333333333333333333333333333333333333333333333333 +7781 3333333333333333333333333333333333333333333333333333333 +7782 3333333333333333333333333333333333333333333333333333333 +7783 3333333333333333333333333333333333333333333333333333333 +7784 3333333333333333333333333333333333333333333333333333333 +7785 3333333333333333333333333333333333333333333333333333333 +7786 3333333333333333333333333333333333333333333333333333333 +7787 3333333333333333333333333333333333333333333333333333333 +7788 3333333333333333333333333333333333333333333333333333333 +7789 3333333333333333333333333333333333333333333333333333333 +7790 3333333333333333333333333333333333333333333333333333333 +7791 3333333333333333333333333333333333333333333333333333333 +7792 3333333333333333333333333333333333333333333333333333333 +7793 3333333333333333333333333333333333333333333333333333333 +7794 3333333333333333333333333333333333333333333333333333333 +7795 3333333333333333333333333333333333333333333333333333333 +7796 3333333333333333333333333333333333333333333333333333333 +7797 3333333333333333333333333333333333333333333333333333333 +7798 3333333333333333333333333333333333333333333333333333333 +7799 3333333333333333333333333333333333333333333333333333333 +7800 3333333333333333333333333333333333333333333333333333333 +7801 3333333333333333333333333333333333333333333333333333333 +7802 3333333333333333333333333333333333333333333333333333333 +7803 3333333333333333333333333333333333333333333333333333333 +7804 3333333333333333333333333333333333333333333333333333333 +7805 3333333333333333333333333333333333333333333333333333333 +7806 3333333333333333333333333333333333333333333333333333333 +7807 3333333333333333333333333333333333333333333333333333333 +7808 3333333333333333333333333333333333333333333333333333333 +7809 3333333333333333333333333333333333333333333333333333333 +7810 3333333333333333333333333333333333333333333333333333333 +7811 3333333333333333333333333333333333333333333333333333333 +7812 3333333333333333333333333333333333333333333333333333333 +7813 3333333333333333333333333333333333333333333333333333333 +7814 3333333333333333333333333333333333333333333333333333333 +7815 3333333333333333333333333333333333333333333333333333333 +7816 3333333333333333333333333333333333333333333333333333333 +7817 3333333333333333333333333333333333333333333333333333333 +7818 3333333333333333333333333333333333333333333333333333333 +7819 3333333333333333333333333333333333333333333333333333333 +7820 3333333333333333333333333333333333333333333333333333333 +7821 3333333333333333333333333333333333333333333333333333333 +7822 3333333333333333333333333333333333333333333333333333333 +7823 3333333333333333333333333333333333333333333333333333333 +7824 3333333333333333333333333333333333333333333333333333333 +7825 3333333333333333333333333333333333333333333333333333333 +7826 3333333333333333333333333333333333333333333333333333333 +7827 3333333333333333333333333333333333333333333333333333333 +7828 3333333333333333333333333333333333333333333333333333333 +7829 3333333333333333333333333333333333333333333333333333333 +7830 3333333333333333333333333333333333333333333333333333333 +7831 3333333333333333333333333333333333333333333333333333333 +7832 3333333333333333333333333333333333333333333333333333333 +7833 3333333333333333333333333333333333333333333333333333333 +7834 3333333333333333333333333333333333333333333333333333333 +7835 3333333333333333333333333333333333333333333333333333333 +7836 3333333333333333333333333333333333333333333333333333333 +7837 3333333333333333333333333333333333333333333333333333333 +7838 3333333333333333333333333333333333333333333333333333333 +7839 3333333333333333333333333333333333333333333333333333333 +7840 3333333333333333333333333333333333333333333333333333333 +7841 3333333333333333333333333333333333333333333333333333333 +7842 3333333333333333333333333333333333333333333333333333333 +7843 3333333333333333333333333333333333333333333333333333333 +7844 3333333333333333333333333333333333333333333333333333333 +7845 3333333333333333333333333333333333333333333333333333333 +7846 3333333333333333333333333333333333333333333333333333333 +7847 3333333333333333333333333333333333333333333333333333333 +7848 3333333333333333333333333333333333333333333333333333333 +7849 3333333333333333333333333333333333333333333333333333333 +7850 3333333333333333333333333333333333333333333333333333333 +7851 3333333333333333333333333333333333333333333333333333333 +7852 3333333333333333333333333333333333333333333333333333333 +7853 3333333333333333333333333333333333333333333333333333333 +7854 3333333333333333333333333333333333333333333333333333333 +7855 3333333333333333333333333333333333333333333333333333333 +7856 3333333333333333333333333333333333333333333333333333333 +7857 3333333333333333333333333333333333333333333333333333333 +7858 3333333333333333333333333333333333333333333333333333333 +7859 3333333333333333333333333333333333333333333333333333333 +7860 3333333333333333333333333333333333333333333333333333333 +7861 3333333333333333333333333333333333333333333333333333333 +7862 3333333333333333333333333333333333333333333333333333333 +7863 3333333333333333333333333333333333333333333333333333333 +7864 3333333333333333333333333333333333333333333333333333333 +7865 3333333333333333333333333333333333333333333333333333333 +7866 3333333333333333333333333333333333333333333333333333333 +7867 3333333333333333333333333333333333333333333333333333333 +7868 3333333333333333333333333333333333333333333333333333333 +7869 3333333333333333333333333333333333333333333333333333333 +7870 3333333333333333333333333333333333333333333333333333333 +7871 3333333333333333333333333333333333333333333333333333333 +7872 3333333333333333333333333333333333333333333333333333333 +7873 3333333333333333333333333333333333333333333333333333333 +7874 3333333333333333333333333333333333333333333333333333333 +7875 3333333333333333333333333333333333333333333333333333333 +7876 3333333333333333333333333333333333333333333333333333333 +7877 3333333333333333333333333333333333333333333333333333333 +7878 3333333333333333333333333333333333333333333333333333333 +7879 3333333333333333333333333333333333333333333333333333333 +7880 3333333333333333333333333333333333333333333333333333333 +7881 3333333333333333333333333333333333333333333333333333333 +7882 3333333333333333333333333333333333333333333333333333333 +7883 3333333333333333333333333333333333333333333333333333333 +7884 3333333333333333333333333333333333333333333333333333333 +7885 3333333333333333333333333333333333333333333333333333333 +7886 3333333333333333333333333333333333333333333333333333333 +7887 3333333333333333333333333333333333333333333333333333333 +7888 3333333333333333333333333333333333333333333333333333333 +7889 3333333333333333333333333333333333333333333333333333333 +7890 3333333333333333333333333333333333333333333333333333333 +7891 3333333333333333333333333333333333333333333333333333333 +7892 3333333333333333333333333333333333333333333333333333333 +7893 3333333333333333333333333333333333333333333333333333333 +7894 3333333333333333333333333333333333333333333333333333333 +7895 3333333333333333333333333333333333333333333333333333333 +7896 3333333333333333333333333333333333333333333333333333333 +7897 3333333333333333333333333333333333333333333333333333333 +7898 3333333333333333333333333333333333333333333333333333333 +7899 3333333333333333333333333333333333333333333333333333333 +7900 3333333333333333333333333333333333333333333333333333333 +7901 3333333333333333333333333333333333333333333333333333333 +7902 3333333333333333333333333333333333333333333333333333333 +7903 3333333333333333333333333333333333333333333333333333333 +7904 3333333333333333333333333333333333333333333333333333333 +7905 3333333333333333333333333333333333333333333333333333333 +7906 3333333333333333333333333333333333333333333333333333333 +7907 3333333333333333333333333333333333333333333333333333333 +7908 3333333333333333333333333333333333333333333333333333333 +7909 3333333333333333333333333333333333333333333333333333333 +7910 3333333333333333333333333333333333333333333333333333333 +7911 3333333333333333333333333333333333333333333333333333333 +7912 3333333333333333333333333333333333333333333333333333333 +7913 3333333333333333333333333333333333333333333333333333333 +7914 3333333333333333333333333333333333333333333333333333333 +7915 3333333333333333333333333333333333333333333333333333333 +7916 3333333333333333333333333333333333333333333333333333333 +7917 3333333333333333333333333333333333333333333333333333333 +7918 3333333333333333333333333333333333333333333333333333333 +7919 3333333333333333333333333333333333333333333333333333333 +7920 3333333333333333333333333333333333333333333333333333333 +7921 3333333333333333333333333333333333333333333333333333333 +7922 3333333333333333333333333333333333333333333333333333333 +7923 3333333333333333333333333333333333333333333333333333333 +7924 3333333333333333333333333333333333333333333333333333333 +7925 3333333333333333333333333333333333333333333333333333333 +7926 3333333333333333333333333333333333333333333333333333333 +7927 3333333333333333333333333333333333333333333333333333333 +7928 3333333333333333333333333333333333333333333333333333333 +7929 3333333333333333333333333333333333333333333333333333333 +7930 3333333333333333333333333333333333333333333333333333333 +7931 3333333333333333333333333333333333333333333333333333333 +7932 3333333333333333333333333333333333333333333333333333333 +7933 3333333333333333333333333333333333333333333333333333333 +7934 3333333333333333333333333333333333333333333333333333333 +7935 3333333333333333333333333333333333333333333333333333333 +7936 3333333333333333333333333333333333333333333333333333333 +7937 3333333333333333333333333333333333333333333333333333333 +7938 3333333333333333333333333333333333333333333333333333333 +7939 3333333333333333333333333333333333333333333333333333333 +7940 3333333333333333333333333333333333333333333333333333333 +7941 3333333333333333333333333333333333333333333333333333333 +7942 3333333333333333333333333333333333333333333333333333333 +7943 3333333333333333333333333333333333333333333333333333333 +7944 3333333333333333333333333333333333333333333333333333333 +7945 3333333333333333333333333333333333333333333333333333333 +7946 3333333333333333333333333333333333333333333333333333333 +7947 3333333333333333333333333333333333333333333333333333333 +7948 3333333333333333333333333333333333333333333333333333333 +7949 3333333333333333333333333333333333333333333333333333333 +7950 3333333333333333333333333333333333333333333333333333333 +7951 3333333333333333333333333333333333333333333333333333333 +7952 3333333333333333333333333333333333333333333333333333333 +7953 3333333333333333333333333333333333333333333333333333333 +7954 3333333333333333333333333333333333333333333333333333333 +7955 3333333333333333333333333333333333333333333333333333333 +7956 3333333333333333333333333333333333333333333333333333333 +7957 3333333333333333333333333333333333333333333333333333333 +7958 3333333333333333333333333333333333333333333333333333333 +7959 3333333333333333333333333333333333333333333333333333333 +7960 3333333333333333333333333333333333333333333333333333333 +7961 3333333333333333333333333333333333333333333333333333333 +7962 3333333333333333333333333333333333333333333333333333333 +7963 3333333333333333333333333333333333333333333333333333333 +7964 3333333333333333333333333333333333333333333333333333333 +7965 3333333333333333333333333333333333333333333333333333333 +7966 3333333333333333333333333333333333333333333333333333333 +7967 3333333333333333333333333333333333333333333333333333333 +7968 3333333333333333333333333333333333333333333333333333333 +7969 3333333333333333333333333333333333333333333333333333333 +7970 3333333333333333333333333333333333333333333333333333333 +7971 3333333333333333333333333333333333333333333333333333333 +7972 3333333333333333333333333333333333333333333333333333333 +7973 3333333333333333333333333333333333333333333333333333333 +7974 3333333333333333333333333333333333333333333333333333333 +7975 3333333333333333333333333333333333333333333333333333333 +7976 3333333333333333333333333333333333333333333333333333333 +7977 3333333333333333333333333333333333333333333333333333333 +7978 3333333333333333333333333333333333333333333333333333333 +7979 3333333333333333333333333333333333333333333333333333333 +7980 3333333333333333333333333333333333333333333333333333333 +7981 3333333333333333333333333333333333333333333333333333333 +7982 3333333333333333333333333333333333333333333333333333333 +7983 3333333333333333333333333333333333333333333333333333333 +7984 3333333333333333333333333333333333333333333333333333333 +7985 3333333333333333333333333333333333333333333333333333333 +7986 3333333333333333333333333333333333333333333333333333333 +7987 3333333333333333333333333333333333333333333333333333333 +7988 3333333333333333333333333333333333333333333333333333333 +7989 3333333333333333333333333333333333333333333333333333333 +7990 3333333333333333333333333333333333333333333333333333333 +7991 3333333333333333333333333333333333333333333333333333333 +7992 3333333333333333333333333333333333333333333333333333333 +7993 3333333333333333333333333333333333333333333333333333333 +7994 3333333333333333333333333333333333333333333333333333333 +7995 3333333333333333333333333333333333333333333333333333333 +7996 3333333333333333333333333333333333333333333333333333333 +7997 3333333333333333333333333333333333333333333333333333333 +7998 3333333333333333333333333333333333333333333333333333333 +7999 3333333333333333333333333333333333333333333333333333333 +8000 3333333333333333333333333333333333333333333333333333333 +8001 3333333333333333333333333333333333333333333333333333333 +8002 3333333333333333333333333333333333333333333333333333333 +8003 3333333333333333333333333333333333333333333333333333333 +8004 3333333333333333333333333333333333333333333333333333333 +8005 3333333333333333333333333333333333333333333333333333333 +8006 3333333333333333333333333333333333333333333333333333333 +8007 3333333333333333333333333333333333333333333333333333333 +8008 3333333333333333333333333333333333333333333333333333333 +8009 3333333333333333333333333333333333333333333333333333333 +8010 3333333333333333333333333333333333333333333333333333333 +8011 3333333333333333333333333333333333333333333333333333333 +8012 3333333333333333333333333333333333333333333333333333333 +8013 3333333333333333333333333333333333333333333333333333333 +8014 3333333333333333333333333333333333333333333333333333333 +8015 3333333333333333333333333333333333333333333333333333333 +8016 3333333333333333333333333333333333333333333333333333333 +8017 3333333333333333333333333333333333333333333333333333333 +8018 3333333333333333333333333333333333333333333333333333333 +8019 3333333333333333333333333333333333333333333333333333333 +8020 3333333333333333333333333333333333333333333333333333333 +8021 3333333333333333333333333333333333333333333333333333333 +8022 3333333333333333333333333333333333333333333333333333333 +8023 3333333333333333333333333333333333333333333333333333333 +8024 3333333333333333333333333333333333333333333333333333333 +8025 3333333333333333333333333333333333333333333333333333333 +8026 3333333333333333333333333333333333333333333333333333333 +8027 3333333333333333333333333333333333333333333333333333333 +8028 3333333333333333333333333333333333333333333333333333333 +8029 3333333333333333333333333333333333333333333333333333333 +8030 3333333333333333333333333333333333333333333333333333333 +8031 3333333333333333333333333333333333333333333333333333333 +8032 3333333333333333333333333333333333333333333333333333333 +8033 3333333333333333333333333333333333333333333333333333333 +8034 3333333333333333333333333333333333333333333333333333333 +8035 3333333333333333333333333333333333333333333333333333333 +8036 3333333333333333333333333333333333333333333333333333333 +8037 3333333333333333333333333333333333333333333333333333333 +8038 3333333333333333333333333333333333333333333333333333333 +8039 3333333333333333333333333333333333333333333333333333333 +8040 3333333333333333333333333333333333333333333333333333333 +8041 3333333333333333333333333333333333333333333333333333333 +8042 3333333333333333333333333333333333333333333333333333333 +8043 3333333333333333333333333333333333333333333333333333333 +8044 3333333333333333333333333333333333333333333333333333333 +8045 3333333333333333333333333333333333333333333333333333333 +8046 3333333333333333333333333333333333333333333333333333333 +8047 3333333333333333333333333333333333333333333333333333333 +8048 3333333333333333333333333333333333333333333333333333333 +8049 3333333333333333333333333333333333333333333333333333333 +8050 3333333333333333333333333333333333333333333333333333333 +8051 3333333333333333333333333333333333333333333333333333333 +8052 3333333333333333333333333333333333333333333333333333333 +8053 3333333333333333333333333333333333333333333333333333333 +8054 3333333333333333333333333333333333333333333333333333333 +8055 3333333333333333333333333333333333333333333333333333333 +8056 3333333333333333333333333333333333333333333333333333333 +8057 3333333333333333333333333333333333333333333333333333333 +8058 3333333333333333333333333333333333333333333333333333333 +8059 3333333333333333333333333333333333333333333333333333333 +8060 3333333333333333333333333333333333333333333333333333333 +8061 3333333333333333333333333333333333333333333333333333333 +8062 3333333333333333333333333333333333333333333333333333333 +8063 3333333333333333333333333333333333333333333333333333333 +8064 3333333333333333333333333333333333333333333333333333333 +8065 3333333333333333333333333333333333333333333333333333333 +8066 3333333333333333333333333333333333333333333333333333333 +8067 3333333333333333333333333333333333333333333333333333333 +8068 3333333333333333333333333333333333333333333333333333333 +8069 3333333333333333333333333333333333333333333333333333333 +8070 3333333333333333333333333333333333333333333333333333333 +8071 3333333333333333333333333333333333333333333333333333333 +8072 3333333333333333333333333333333333333333333333333333333 +8073 3333333333333333333333333333333333333333333333333333333 +8074 3333333333333333333333333333333333333333333333333333333 +8075 3333333333333333333333333333333333333333333333333333333 +8076 3333333333333333333333333333333333333333333333333333333 +8077 3333333333333333333333333333333333333333333333333333333 +8078 3333333333333333333333333333333333333333333333333333333 +8079 3333333333333333333333333333333333333333333333333333333 +8080 3333333333333333333333333333333333333333333333333333333 +8081 3333333333333333333333333333333333333333333333333333333 +8082 3333333333333333333333333333333333333333333333333333333 +8083 3333333333333333333333333333333333333333333333333333333 +8084 3333333333333333333333333333333333333333333333333333333 +8085 3333333333333333333333333333333333333333333333333333333 +8086 3333333333333333333333333333333333333333333333333333333 +8087 3333333333333333333333333333333333333333333333333333333 +8088 3333333333333333333333333333333333333333333333333333333 +8089 3333333333333333333333333333333333333333333333333333333 +8090 3333333333333333333333333333333333333333333333333333333 +8091 3333333333333333333333333333333333333333333333333333333 +8092 3333333333333333333333333333333333333333333333333333333 +8093 3333333333333333333333333333333333333333333333333333333 +8094 3333333333333333333333333333333333333333333333333333333 +8095 3333333333333333333333333333333333333333333333333333333 +8096 3333333333333333333333333333333333333333333333333333333 +8097 3333333333333333333333333333333333333333333333333333333 +8098 3333333333333333333333333333333333333333333333333333333 +8099 3333333333333333333333333333333333333333333333333333333 +8100 3333333333333333333333333333333333333333333333333333333 +8101 3333333333333333333333333333333333333333333333333333333 +8102 3333333333333333333333333333333333333333333333333333333 +8103 3333333333333333333333333333333333333333333333333333333 +8104 3333333333333333333333333333333333333333333333333333333 +8105 3333333333333333333333333333333333333333333333333333333 +8106 3333333333333333333333333333333333333333333333333333333 +8107 3333333333333333333333333333333333333333333333333333333 +8108 3333333333333333333333333333333333333333333333333333333 +8109 3333333333333333333333333333333333333333333333333333333 +8110 3333333333333333333333333333333333333333333333333333333 +8111 3333333333333333333333333333333333333333333333333333333 +8112 3333333333333333333333333333333333333333333333333333333 +8113 3333333333333333333333333333333333333333333333333333333 +8114 3333333333333333333333333333333333333333333333333333333 +8115 3333333333333333333333333333333333333333333333333333333 +8116 3333333333333333333333333333333333333333333333333333333 +8117 3333333333333333333333333333333333333333333333333333333 +8118 3333333333333333333333333333333333333333333333333333333 +8119 3333333333333333333333333333333333333333333333333333333 +8120 3333333333333333333333333333333333333333333333333333333 +8121 3333333333333333333333333333333333333333333333333333333 +8122 3333333333333333333333333333333333333333333333333333333 +8123 3333333333333333333333333333333333333333333333333333333 +8124 3333333333333333333333333333333333333333333333333333333 +8125 3333333333333333333333333333333333333333333333333333333 +8126 3333333333333333333333333333333333333333333333333333333 +8127 3333333333333333333333333333333333333333333333333333333 +8128 3333333333333333333333333333333333333333333333333333333 +8129 3333333333333333333333333333333333333333333333333333333 +8130 3333333333333333333333333333333333333333333333333333333 +8131 3333333333333333333333333333333333333333333333333333333 +8132 3333333333333333333333333333333333333333333333333333333 +8133 3333333333333333333333333333333333333333333333333333333 +8134 3333333333333333333333333333333333333333333333333333333 +8135 3333333333333333333333333333333333333333333333333333333 +8136 3333333333333333333333333333333333333333333333333333333 +8137 3333333333333333333333333333333333333333333333333333333 +8138 3333333333333333333333333333333333333333333333333333333 +8139 3333333333333333333333333333333333333333333333333333333 +8140 3333333333333333333333333333333333333333333333333333333 +8141 3333333333333333333333333333333333333333333333333333333 +8142 3333333333333333333333333333333333333333333333333333333 +8143 3333333333333333333333333333333333333333333333333333333 +8144 3333333333333333333333333333333333333333333333333333333 +8145 3333333333333333333333333333333333333333333333333333333 +8146 3333333333333333333333333333333333333333333333333333333 +8147 3333333333333333333333333333333333333333333333333333333 +8148 3333333333333333333333333333333333333333333333333333333 +8149 3333333333333333333333333333333333333333333333333333333 +8150 3333333333333333333333333333333333333333333333333333333 +8151 3333333333333333333333333333333333333333333333333333333 +8152 3333333333333333333333333333333333333333333333333333333 +8153 3333333333333333333333333333333333333333333333333333333 +8154 3333333333333333333333333333333333333333333333333333333 +8155 3333333333333333333333333333333333333333333333333333333 +8156 3333333333333333333333333333333333333333333333333333333 +8157 3333333333333333333333333333333333333333333333333333333 +8158 3333333333333333333333333333333333333333333333333333333 +8159 3333333333333333333333333333333333333333333333333333333 +8160 3333333333333333333333333333333333333333333333333333333 +8161 3333333333333333333333333333333333333333333333333333333 +8162 3333333333333333333333333333333333333333333333333333333 +8163 3333333333333333333333333333333333333333333333333333333 +8164 3333333333333333333333333333333333333333333333333333333 +8165 3333333333333333333333333333333333333333333333333333333 +8166 3333333333333333333333333333333333333333333333333333333 +8167 3333333333333333333333333333333333333333333333333333333 +8168 3333333333333333333333333333333333333333333333333333333 +8169 3333333333333333333333333333333333333333333333333333333 +8170 3333333333333333333333333333333333333333333333333333333 +8171 3333333333333333333333333333333333333333333333333333333 +8172 3333333333333333333333333333333333333333333333333333333 +8173 3333333333333333333333333333333333333333333333333333333 +8174 3333333333333333333333333333333333333333333333333333333 +8175 3333333333333333333333333333333333333333333333333333333 +8176 3333333333333333333333333333333333333333333333333333333 +8177 3333333333333333333333333333333333333333333333333333333 +8178 3333333333333333333333333333333333333333333333333333333 +8179 3333333333333333333333333333333333333333333333333333333 +8180 3333333333333333333333333333333333333333333333333333333 +8181 3333333333333333333333333333333333333333333333333333333 +8182 3333333333333333333333333333333333333333333333333333333 +8183 3333333333333333333333333333333333333333333333333333333 +8184 3333333333333333333333333333333333333333333333333333333 +8185 3333333333333333333333333333333333333333333333333333333 +8186 3333333333333333333333333333333333333333333333333333333 +8187 3333333333333333333333333333333333333333333333333333333 +8188 3333333333333333333333333333333333333333333333333333333 +8189 3333333333333333333333333333333333333333333333333333333 +8190 3333333333333333333333333333333333333333333333333333333 +8191 3333333333333333333333333333333333333333333333333333333 +8192 3333333333333333333333333333333333333333333333333333333 +8193 3333333333333333333333333333333333333333333333333333333 +8194 3333333333333333333333333333333333333333333333333333333 +8195 3333333333333333333333333333333333333333333333333333333 +8196 3333333333333333333333333333333333333333333333333333333 +8197 3333333333333333333333333333333333333333333333333333333 +8198 3333333333333333333333333333333333333333333333333333333 +8199 3333333333333333333333333333333333333333333333333333333 +8200 3333333333333333333333333333333333333333333333333333333 +8201 3333333333333333333333333333333333333333333333333333333 +8202 3333333333333333333333333333333333333333333333333333333 +8203 3333333333333333333333333333333333333333333333333333333 +8204 3333333333333333333333333333333333333333333333333333333 +8205 3333333333333333333333333333333333333333333333333333333 +8206 3333333333333333333333333333333333333333333333333333333 +8207 3333333333333333333333333333333333333333333333333333333 +8208 3333333333333333333333333333333333333333333333333333333 +8209 3333333333333333333333333333333333333333333333333333333 +8210 3333333333333333333333333333333333333333333333333333333 +8211 3333333333333333333333333333333333333333333333333333333 +8212 3333333333333333333333333333333333333333333333333333333 +8213 3333333333333333333333333333333333333333333333333333333 +8214 3333333333333333333333333333333333333333333333333333333 +8215 3333333333333333333333333333333333333333333333333333333 +8216 3333333333333333333333333333333333333333333333333333333 +8217 3333333333333333333333333333333333333333333333333333333 +8218 3333333333333333333333333333333333333333333333333333333 +8219 3333333333333333333333333333333333333333333333333333333 +8220 3333333333333333333333333333333333333333333333333333333 +8221 3333333333333333333333333333333333333333333333333333333 +8222 3333333333333333333333333333333333333333333333333333333 +8223 3333333333333333333333333333333333333333333333333333333 +8224 3333333333333333333333333333333333333333333333333333333 +8225 3333333333333333333333333333333333333333333333333333333 +8226 3333333333333333333333333333333333333333333333333333333 +8227 3333333333333333333333333333333333333333333333333333333 +8228 3333333333333333333333333333333333333333333333333333333 +8229 3333333333333333333333333333333333333333333333333333333 +8230 3333333333333333333333333333333333333333333333333333333 +8231 3333333333333333333333333333333333333333333333333333333 +8232 3333333333333333333333333333333333333333333333333333333 +8233 3333333333333333333333333333333333333333333333333333333 +8234 3333333333333333333333333333333333333333333333333333333 +8235 3333333333333333333333333333333333333333333333333333333 +8236 3333333333333333333333333333333333333333333333333333333 +8237 3333333333333333333333333333333333333333333333333333333 +8238 3333333333333333333333333333333333333333333333333333333 +8239 3333333333333333333333333333333333333333333333333333333 +8240 3333333333333333333333333333333333333333333333333333333 +8241 3333333333333333333333333333333333333333333333333333333 +8242 3333333333333333333333333333333333333333333333333333333 +8243 3333333333333333333333333333333333333333333333333333333 +8244 3333333333333333333333333333333333333333333333333333333 +8245 3333333333333333333333333333333333333333333333333333333 +8246 3333333333333333333333333333333333333333333333333333333 +8247 3333333333333333333333333333333333333333333333333333333 +8248 3333333333333333333333333333333333333333333333333333333 +8249 3333333333333333333333333333333333333333333333333333333 +8250 3333333333333333333333333333333333333333333333333333333 +8251 3333333333333333333333333333333333333333333333333333333 +8252 3333333333333333333333333333333333333333333333333333333 +8253 3333333333333333333333333333333333333333333333333333333 +8254 3333333333333333333333333333333333333333333333333333333 +8255 3333333333333333333333333333333333333333333333333333333 +8256 3333333333333333333333333333333333333333333333333333333 +8257 3333333333333333333333333333333333333333333333333333333 +8258 3333333333333333333333333333333333333333333333333333333 +8259 3333333333333333333333333333333333333333333333333333333 +8260 3333333333333333333333333333333333333333333333333333333 +8261 3333333333333333333333333333333333333333333333333333333 +8262 3333333333333333333333333333333333333333333333333333333 +8263 3333333333333333333333333333333333333333333333333333333 +8264 3333333333333333333333333333333333333333333333333333333 +8265 3333333333333333333333333333333333333333333333333333333 +8266 3333333333333333333333333333333333333333333333333333333 +8267 3333333333333333333333333333333333333333333333333333333 +8268 3333333333333333333333333333333333333333333333333333333 +8269 3333333333333333333333333333333333333333333333333333333 +8270 3333333333333333333333333333333333333333333333333333333 +8271 3333333333333333333333333333333333333333333333333333333 +8272 3333333333333333333333333333333333333333333333333333333 +8273 3333333333333333333333333333333333333333333333333333333 +8274 3333333333333333333333333333333333333333333333333333333 +8275 3333333333333333333333333333333333333333333333333333333 +8276 3333333333333333333333333333333333333333333333333333333 +8277 3333333333333333333333333333333333333333333333333333333 +8278 3333333333333333333333333333333333333333333333333333333 +8279 3333333333333333333333333333333333333333333333333333333 +8280 3333333333333333333333333333333333333333333333333333333 +8281 3333333333333333333333333333333333333333333333333333333 +8282 3333333333333333333333333333333333333333333333333333333 +8283 3333333333333333333333333333333333333333333333333333333 +8284 3333333333333333333333333333333333333333333333333333333 +8285 3333333333333333333333333333333333333333333333333333333 +8286 3333333333333333333333333333333333333333333333333333333 +8287 3333333333333333333333333333333333333333333333333333333 +8288 3333333333333333333333333333333333333333333333333333333 +8289 3333333333333333333333333333333333333333333333333333333 +8290 3333333333333333333333333333333333333333333333333333333 +8291 3333333333333333333333333333333333333333333333333333333 +8292 3333333333333333333333333333333333333333333333333333333 +8293 3333333333333333333333333333333333333333333333333333333 +8294 3333333333333333333333333333333333333333333333333333333 +8295 3333333333333333333333333333333333333333333333333333333 +8296 3333333333333333333333333333333333333333333333333333333 +8297 3333333333333333333333333333333333333333333333333333333 +8298 3333333333333333333333333333333333333333333333333333333 +8299 3333333333333333333333333333333333333333333333333333333 +8300 3333333333333333333333333333333333333333333333333333333 +8301 3333333333333333333333333333333333333333333333333333333 +8302 3333333333333333333333333333333333333333333333333333333 +8303 3333333333333333333333333333333333333333333333333333333 +8304 3333333333333333333333333333333333333333333333333333333 +8305 3333333333333333333333333333333333333333333333333333333 +8306 3333333333333333333333333333333333333333333333333333333 +8307 3333333333333333333333333333333333333333333333333333333 +8308 3333333333333333333333333333333333333333333333333333333 +8309 3333333333333333333333333333333333333333333333333333333 +8310 3333333333333333333333333333333333333333333333333333333 +8311 3333333333333333333333333333333333333333333333333333333 +8312 3333333333333333333333333333333333333333333333333333333 +8313 3333333333333333333333333333333333333333333333333333333 +8314 3333333333333333333333333333333333333333333333333333333 +8315 3333333333333333333333333333333333333333333333333333333 +8316 3333333333333333333333333333333333333333333333333333333 +8317 3333333333333333333333333333333333333333333333333333333 +8318 3333333333333333333333333333333333333333333333333333333 +8319 3333333333333333333333333333333333333333333333333333333 +8320 3333333333333333333333333333333333333333333333333333333 +8321 3333333333333333333333333333333333333333333333333333333 +8322 3333333333333333333333333333333333333333333333333333333 +8323 3333333333333333333333333333333333333333333333333333333 +8324 3333333333333333333333333333333333333333333333333333333 +8325 3333333333333333333333333333333333333333333333333333333 +8326 3333333333333333333333333333333333333333333333333333333 +8327 3333333333333333333333333333333333333333333333333333333 +8328 3333333333333333333333333333333333333333333333333333333 +8329 3333333333333333333333333333333333333333333333333333333 +8330 3333333333333333333333333333333333333333333333333333333 +8331 3333333333333333333333333333333333333333333333333333333 +8332 3333333333333333333333333333333333333333333333333333333 +8333 3333333333333333333333333333333333333333333333333333333 +8334 3333333333333333333333333333333333333333333333333333333 +8335 3333333333333333333333333333333333333333333333333333333 +8336 3333333333333333333333333333333333333333333333333333333 +8337 3333333333333333333333333333333333333333333333333333333 +8338 3333333333333333333333333333333333333333333333333333333 +8339 3333333333333333333333333333333333333333333333333333333 +8340 3333333333333333333333333333333333333333333333333333333 +8341 3333333333333333333333333333333333333333333333333333333 +8342 3333333333333333333333333333333333333333333333333333333 +8343 3333333333333333333333333333333333333333333333333333333 +8344 3333333333333333333333333333333333333333333333333333333 +8345 3333333333333333333333333333333333333333333333333333333 +8346 3333333333333333333333333333333333333333333333333333333 +8347 3333333333333333333333333333333333333333333333333333333 +8348 3333333333333333333333333333333333333333333333333333333 +8349 3333333333333333333333333333333333333333333333333333333 +8350 3333333333333333333333333333333333333333333333333333333 +8351 3333333333333333333333333333333333333333333333333333333 +8352 3333333333333333333333333333333333333333333333333333333 +8353 3333333333333333333333333333333333333333333333333333333 +8354 3333333333333333333333333333333333333333333333333333333 +8355 3333333333333333333333333333333333333333333333333333333 +8356 3333333333333333333333333333333333333333333333333333333 +8357 3333333333333333333333333333333333333333333333333333333 +8358 3333333333333333333333333333333333333333333333333333333 +8359 3333333333333333333333333333333333333333333333333333333 +8360 3333333333333333333333333333333333333333333333333333333 +8361 3333333333333333333333333333333333333333333333333333333 +8362 3333333333333333333333333333333333333333333333333333333 +8363 3333333333333333333333333333333333333333333333333333333 +8364 3333333333333333333333333333333333333333333333333333333 +8365 3333333333333333333333333333333333333333333333333333333 +8366 3333333333333333333333333333333333333333333333333333333 +8367 3333333333333333333333333333333333333333333333333333333 +8368 3333333333333333333333333333333333333333333333333333333 +8369 3333333333333333333333333333333333333333333333333333333 +8370 3333333333333333333333333333333333333333333333333333333 +8371 3333333333333333333333333333333333333333333333333333333 +8372 3333333333333333333333333333333333333333333333333333333 +8373 3333333333333333333333333333333333333333333333333333333 +8374 3333333333333333333333333333333333333333333333333333333 +8375 3333333333333333333333333333333333333333333333333333333 +8376 3333333333333333333333333333333333333333333333333333333 +8377 3333333333333333333333333333333333333333333333333333333 +8378 3333333333333333333333333333333333333333333333333333333 +8379 3333333333333333333333333333333333333333333333333333333 +8380 3333333333333333333333333333333333333333333333333333333 +8381 3333333333333333333333333333333333333333333333333333333 +8382 3333333333333333333333333333333333333333333333333333333 +8383 3333333333333333333333333333333333333333333333333333333 +8384 3333333333333333333333333333333333333333333333333333333 +8385 3333333333333333333333333333333333333333333333333333333 +8386 3333333333333333333333333333333333333333333333333333333 +8387 3333333333333333333333333333333333333333333333333333333 +8388 3333333333333333333333333333333333333333333333333333333 +8389 3333333333333333333333333333333333333333333333333333333 +8390 3333333333333333333333333333333333333333333333333333333 +8391 3333333333333333333333333333333333333333333333333333333 +8392 3333333333333333333333333333333333333333333333333333333 +8393 3333333333333333333333333333333333333333333333333333333 +8394 3333333333333333333333333333333333333333333333333333333 +8395 3333333333333333333333333333333333333333333333333333333 +8396 3333333333333333333333333333333333333333333333333333333 +8397 3333333333333333333333333333333333333333333333333333333 +8398 3333333333333333333333333333333333333333333333333333333 +8399 3333333333333333333333333333333333333333333333333333333 +8400 3333333333333333333333333333333333333333333333333333333 +8401 3333333333333333333333333333333333333333333333333333333 +8402 3333333333333333333333333333333333333333333333333333333 +8403 3333333333333333333333333333333333333333333333333333333 +8404 3333333333333333333333333333333333333333333333333333333 +8405 3333333333333333333333333333333333333333333333333333333 +8406 3333333333333333333333333333333333333333333333333333333 +8407 3333333333333333333333333333333333333333333333333333333 +8408 3333333333333333333333333333333333333333333333333333333 +8409 3333333333333333333333333333333333333333333333333333333 +8410 3333333333333333333333333333333333333333333333333333333 +8411 3333333333333333333333333333333333333333333333333333333 +8412 3333333333333333333333333333333333333333333333333333333 +8413 3333333333333333333333333333333333333333333333333333333 +8414 3333333333333333333333333333333333333333333333333333333 +8415 3333333333333333333333333333333333333333333333333333333 +8416 3333333333333333333333333333333333333333333333333333333 +8417 3333333333333333333333333333333333333333333333333333333 +8418 3333333333333333333333333333333333333333333333333333333 +8419 3333333333333333333333333333333333333333333333333333333 +8420 3333333333333333333333333333333333333333333333333333333 +8421 3333333333333333333333333333333333333333333333333333333 +8422 3333333333333333333333333333333333333333333333333333333 +8423 3333333333333333333333333333333333333333333333333333333 +8424 3333333333333333333333333333333333333333333333333333333 +8425 3333333333333333333333333333333333333333333333333333333 +8426 3333333333333333333333333333333333333333333333333333333 +8427 3333333333333333333333333333333333333333333333333333333 +8428 3333333333333333333333333333333333333333333333333333333 +8429 3333333333333333333333333333333333333333333333333333333 +8430 3333333333333333333333333333333333333333333333333333333 +8431 3333333333333333333333333333333333333333333333333333333 +8432 3333333333333333333333333333333333333333333333333333333 +8433 3333333333333333333333333333333333333333333333333333333 +8434 3333333333333333333333333333333333333333333333333333333 +8435 3333333333333333333333333333333333333333333333333333333 +8436 3333333333333333333333333333333333333333333333333333333 +8437 3333333333333333333333333333333333333333333333333333333 +8438 3333333333333333333333333333333333333333333333333333333 +8439 3333333333333333333333333333333333333333333333333333333 +8440 3333333333333333333333333333333333333333333333333333333 +8441 3333333333333333333333333333333333333333333333333333333 +8442 3333333333333333333333333333333333333333333333333333333 +8443 3333333333333333333333333333333333333333333333333333333 +8444 3333333333333333333333333333333333333333333333333333333 +8445 3333333333333333333333333333333333333333333333333333333 +8446 3333333333333333333333333333333333333333333333333333333 +8447 3333333333333333333333333333333333333333333333333333333 +8448 3333333333333333333333333333333333333333333333333333333 +8449 3333333333333333333333333333333333333333333333333333333 +8450 3333333333333333333333333333333333333333333333333333333 +8451 3333333333333333333333333333333333333333333333333333333 +8452 3333333333333333333333333333333333333333333333333333333 +8453 3333333333333333333333333333333333333333333333333333333 +8454 3333333333333333333333333333333333333333333333333333333 +8455 3333333333333333333333333333333333333333333333333333333 +8456 3333333333333333333333333333333333333333333333333333333 +8457 3333333333333333333333333333333333333333333333333333333 +8458 3333333333333333333333333333333333333333333333333333333 +8459 3333333333333333333333333333333333333333333333333333333 +8460 3333333333333333333333333333333333333333333333333333333 +8461 3333333333333333333333333333333333333333333333333333333 +8462 3333333333333333333333333333333333333333333333333333333 +8463 3333333333333333333333333333333333333333333333333333333 +8464 3333333333333333333333333333333333333333333333333333333 +8465 3333333333333333333333333333333333333333333333333333333 +8466 3333333333333333333333333333333333333333333333333333333 +8467 3333333333333333333333333333333333333333333333333333333 +8468 3333333333333333333333333333333333333333333333333333333 +8469 3333333333333333333333333333333333333333333333333333333 +8470 3333333333333333333333333333333333333333333333333333333 +8471 3333333333333333333333333333333333333333333333333333333 +8472 3333333333333333333333333333333333333333333333333333333 +8473 3333333333333333333333333333333333333333333333333333333 +8474 3333333333333333333333333333333333333333333333333333333 +8475 3333333333333333333333333333333333333333333333333333333 +8476 3333333333333333333333333333333333333333333333333333333 +8477 3333333333333333333333333333333333333333333333333333333 +8478 3333333333333333333333333333333333333333333333333333333 +8479 3333333333333333333333333333333333333333333333333333333 +8480 3333333333333333333333333333333333333333333333333333333 +8481 3333333333333333333333333333333333333333333333333333333 +8482 3333333333333333333333333333333333333333333333333333333 +8483 3333333333333333333333333333333333333333333333333333333 +8484 3333333333333333333333333333333333333333333333333333333 +8485 3333333333333333333333333333333333333333333333333333333 +8486 3333333333333333333333333333333333333333333333333333333 +8487 3333333333333333333333333333333333333333333333333333333 +8488 3333333333333333333333333333333333333333333333333333333 +8489 3333333333333333333333333333333333333333333333333333333 +8490 3333333333333333333333333333333333333333333333333333333 +8491 3333333333333333333333333333333333333333333333333333333 +8492 3333333333333333333333333333333333333333333333333333333 +8493 3333333333333333333333333333333333333333333333333333333 +8494 3333333333333333333333333333333333333333333333333333333 +8495 3333333333333333333333333333333333333333333333333333333 +8496 3333333333333333333333333333333333333333333333333333333 +8497 3333333333333333333333333333333333333333333333333333333 +8498 3333333333333333333333333333333333333333333333333333333 +8499 3333333333333333333333333333333333333333333333333333333 +8500 3333333333333333333333333333333333333333333333333333333 +8501 3333333333333333333333333333333333333333333333333333333 +8502 3333333333333333333333333333333333333333333333333333333 +8503 3333333333333333333333333333333333333333333333333333333 +8504 3333333333333333333333333333333333333333333333333333333 +8505 3333333333333333333333333333333333333333333333333333333 +8506 3333333333333333333333333333333333333333333333333333333 +8507 3333333333333333333333333333333333333333333333333333333 +8508 3333333333333333333333333333333333333333333333333333333 +8509 3333333333333333333333333333333333333333333333333333333 +8510 3333333333333333333333333333333333333333333333333333333 +8511 3333333333333333333333333333333333333333333333333333333 +8512 3333333333333333333333333333333333333333333333333333333 +8513 3333333333333333333333333333333333333333333333333333333 +8514 3333333333333333333333333333333333333333333333333333333 +8515 3333333333333333333333333333333333333333333333333333333 +8516 3333333333333333333333333333333333333333333333333333333 +8517 3333333333333333333333333333333333333333333333333333333 +8518 3333333333333333333333333333333333333333333333333333333 +8519 3333333333333333333333333333333333333333333333333333333 +8520 3333333333333333333333333333333333333333333333333333333 +8521 3333333333333333333333333333333333333333333333333333333 +8522 3333333333333333333333333333333333333333333333333333333 +8523 3333333333333333333333333333333333333333333333333333333 +8524 3333333333333333333333333333333333333333333333333333333 +8525 3333333333333333333333333333333333333333333333333333333 +8526 3333333333333333333333333333333333333333333333333333333 +8527 3333333333333333333333333333333333333333333333333333333 +8528 3333333333333333333333333333333333333333333333333333333 +8529 3333333333333333333333333333333333333333333333333333333 +8530 3333333333333333333333333333333333333333333333333333333 +8531 3333333333333333333333333333333333333333333333333333333 +8532 3333333333333333333333333333333333333333333333333333333 +8533 3333333333333333333333333333333333333333333333333333333 +8534 3333333333333333333333333333333333333333333333333333333 +8535 3333333333333333333333333333333333333333333333333333333 +8536 3333333333333333333333333333333333333333333333333333333 +8537 3333333333333333333333333333333333333333333333333333333 +8538 3333333333333333333333333333333333333333333333333333333 +8539 3333333333333333333333333333333333333333333333333333333 +8540 3333333333333333333333333333333333333333333333333333333 +8541 3333333333333333333333333333333333333333333333333333333 +8542 3333333333333333333333333333333333333333333333333333333 +8543 3333333333333333333333333333333333333333333333333333333 +8544 3333333333333333333333333333333333333333333333333333333 +8545 3333333333333333333333333333333333333333333333333333333 +8546 3333333333333333333333333333333333333333333333333333333 +8547 3333333333333333333333333333333333333333333333333333333 +8548 3333333333333333333333333333333333333333333333333333333 +8549 3333333333333333333333333333333333333333333333333333333 +8550 3333333333333333333333333333333333333333333333333333333 +8551 3333333333333333333333333333333333333333333333333333333 +8552 3333333333333333333333333333333333333333333333333333333 +8553 3333333333333333333333333333333333333333333333333333333 +8554 3333333333333333333333333333333333333333333333333333333 +8555 3333333333333333333333333333333333333333333333333333333 +8556 3333333333333333333333333333333333333333333333333333333 +8557 3333333333333333333333333333333333333333333333333333333 +8558 3333333333333333333333333333333333333333333333333333333 +8559 3333333333333333333333333333333333333333333333333333333 +8560 3333333333333333333333333333333333333333333333333333333 +8561 3333333333333333333333333333333333333333333333333333333 +8562 3333333333333333333333333333333333333333333333333333333 +8563 3333333333333333333333333333333333333333333333333333333 +8564 3333333333333333333333333333333333333333333333333333333 +8565 3333333333333333333333333333333333333333333333333333333 +8566 3333333333333333333333333333333333333333333333333333333 +8567 3333333333333333333333333333333333333333333333333333333 +8568 3333333333333333333333333333333333333333333333333333333 +8569 3333333333333333333333333333333333333333333333333333333 +8570 3333333333333333333333333333333333333333333333333333333 +8571 3333333333333333333333333333333333333333333333333333333 +8572 3333333333333333333333333333333333333333333333333333333 +8573 3333333333333333333333333333333333333333333333333333333 +8574 3333333333333333333333333333333333333333333333333333333 +8575 3333333333333333333333333333333333333333333333333333333 +8576 3333333333333333333333333333333333333333333333333333333 +8577 3333333333333333333333333333333333333333333333333333333 +8578 3333333333333333333333333333333333333333333333333333333 +8579 3333333333333333333333333333333333333333333333333333333 +8580 3333333333333333333333333333333333333333333333333333333 +8581 3333333333333333333333333333333333333333333333333333333 +8582 3333333333333333333333333333333333333333333333333333333 +8583 3333333333333333333333333333333333333333333333333333333 +8584 3333333333333333333333333333333333333333333333333333333 +8585 3333333333333333333333333333333333333333333333333333333 +8586 3333333333333333333333333333333333333333333333333333333 +8587 3333333333333333333333333333333333333333333333333333333 +8588 3333333333333333333333333333333333333333333333333333333 +8589 3333333333333333333333333333333333333333333333333333333 +8590 3333333333333333333333333333333333333333333333333333333 +8591 3333333333333333333333333333333333333333333333333333333 +8592 3333333333333333333333333333333333333333333333333333333 +8593 3333333333333333333333333333333333333333333333333333333 +8594 3333333333333333333333333333333333333333333333333333333 +8595 3333333333333333333333333333333333333333333333333333333 +8596 3333333333333333333333333333333333333333333333333333333 +8597 3333333333333333333333333333333333333333333333333333333 +8598 3333333333333333333333333333333333333333333333333333333 +8599 3333333333333333333333333333333333333333333333333333333 +8600 3333333333333333333333333333333333333333333333333333333 +8601 3333333333333333333333333333333333333333333333333333333 +8602 3333333333333333333333333333333333333333333333333333333 +8603 3333333333333333333333333333333333333333333333333333333 +8604 3333333333333333333333333333333333333333333333333333333 +8605 3333333333333333333333333333333333333333333333333333333 +8606 3333333333333333333333333333333333333333333333333333333 +8607 3333333333333333333333333333333333333333333333333333333 +8608 3333333333333333333333333333333333333333333333333333333 +8609 3333333333333333333333333333333333333333333333333333333 +8610 3333333333333333333333333333333333333333333333333333333 +8611 3333333333333333333333333333333333333333333333333333333 +8612 3333333333333333333333333333333333333333333333333333333 +8613 3333333333333333333333333333333333333333333333333333333 +8614 3333333333333333333333333333333333333333333333333333333 +8615 3333333333333333333333333333333333333333333333333333333 +8616 3333333333333333333333333333333333333333333333333333333 +8617 3333333333333333333333333333333333333333333333333333333 +8618 3333333333333333333333333333333333333333333333333333333 +8619 3333333333333333333333333333333333333333333333333333333 +8620 3333333333333333333333333333333333333333333333333333333 +8621 3333333333333333333333333333333333333333333333333333333 +8622 3333333333333333333333333333333333333333333333333333333 +8623 3333333333333333333333333333333333333333333333333333333 +8624 3333333333333333333333333333333333333333333333333333333 +8625 3333333333333333333333333333333333333333333333333333333 +8626 3333333333333333333333333333333333333333333333333333333 +8627 3333333333333333333333333333333333333333333333333333333 +8628 3333333333333333333333333333333333333333333333333333333 +8629 3333333333333333333333333333333333333333333333333333333 +8630 3333333333333333333333333333333333333333333333333333333 +8631 3333333333333333333333333333333333333333333333333333333 +8632 3333333333333333333333333333333333333333333333333333333 +8633 3333333333333333333333333333333333333333333333333333333 +8634 3333333333333333333333333333333333333333333333333333333 +8635 3333333333333333333333333333333333333333333333333333333 +8636 3333333333333333333333333333333333333333333333333333333 +8637 3333333333333333333333333333333333333333333333333333333 +8638 3333333333333333333333333333333333333333333333333333333 +8639 3333333333333333333333333333333333333333333333333333333 +8640 3333333333333333333333333333333333333333333333333333333 +8641 3333333333333333333333333333333333333333333333333333333 +8642 3333333333333333333333333333333333333333333333333333333 +8643 3333333333333333333333333333333333333333333333333333333 +8644 3333333333333333333333333333333333333333333333333333333 +8645 3333333333333333333333333333333333333333333333333333333 +8646 3333333333333333333333333333333333333333333333333333333 +8647 3333333333333333333333333333333333333333333333333333333 +8648 3333333333333333333333333333333333333333333333333333333 +8649 3333333333333333333333333333333333333333333333333333333 +8650 3333333333333333333333333333333333333333333333333333333 +8651 3333333333333333333333333333333333333333333333333333333 +8652 3333333333333333333333333333333333333333333333333333333 +8653 3333333333333333333333333333333333333333333333333333333 +8654 3333333333333333333333333333333333333333333333333333333 +8655 3333333333333333333333333333333333333333333333333333333 +8656 3333333333333333333333333333333333333333333333333333333 +8657 3333333333333333333333333333333333333333333333333333333 +8658 3333333333333333333333333333333333333333333333333333333 +8659 3333333333333333333333333333333333333333333333333333333 +8660 3333333333333333333333333333333333333333333333333333333 +8661 3333333333333333333333333333333333333333333333333333333 +8662 3333333333333333333333333333333333333333333333333333333 +8663 3333333333333333333333333333333333333333333333333333333 +8664 3333333333333333333333333333333333333333333333333333333 +8665 3333333333333333333333333333333333333333333333333333333 +8666 3333333333333333333333333333333333333333333333333333333 +8667 3333333333333333333333333333333333333333333333333333333 +8668 3333333333333333333333333333333333333333333333333333333 +8669 3333333333333333333333333333333333333333333333333333333 +8670 3333333333333333333333333333333333333333333333333333333 +8671 3333333333333333333333333333333333333333333333333333333 +8672 3333333333333333333333333333333333333333333333333333333 +8673 3333333333333333333333333333333333333333333333333333333 +8674 3333333333333333333333333333333333333333333333333333333 +8675 3333333333333333333333333333333333333333333333333333333 +8676 3333333333333333333333333333333333333333333333333333333 +8677 3333333333333333333333333333333333333333333333333333333 +8678 3333333333333333333333333333333333333333333333333333333 +8679 3333333333333333333333333333333333333333333333333333333 +8680 3333333333333333333333333333333333333333333333333333333 +8681 3333333333333333333333333333333333333333333333333333333 +8682 3333333333333333333333333333333333333333333333333333333 +8683 3333333333333333333333333333333333333333333333333333333 +8684 3333333333333333333333333333333333333333333333333333333 +8685 3333333333333333333333333333333333333333333333333333333 +8686 3333333333333333333333333333333333333333333333333333333 +8687 3333333333333333333333333333333333333333333333333333333 +8688 3333333333333333333333333333333333333333333333333333333 +8689 3333333333333333333333333333333333333333333333333333333 +8690 3333333333333333333333333333333333333333333333333333333 +8691 3333333333333333333333333333333333333333333333333333333 +8692 3333333333333333333333333333333333333333333333333333333 +8693 3333333333333333333333333333333333333333333333333333333 +8694 3333333333333333333333333333333333333333333333333333333 +8695 3333333333333333333333333333333333333333333333333333333 +8696 3333333333333333333333333333333333333333333333333333333 +8697 3333333333333333333333333333333333333333333333333333333 +8698 3333333333333333333333333333333333333333333333333333333 +8699 3333333333333333333333333333333333333333333333333333333 +8700 3333333333333333333333333333333333333333333333333333333 +8701 3333333333333333333333333333333333333333333333333333333 +8702 3333333333333333333333333333333333333333333333333333333 +8703 3333333333333333333333333333333333333333333333333333333 +8704 3333333333333333333333333333333333333333333333333333333 +8705 3333333333333333333333333333333333333333333333333333333 +8706 3333333333333333333333333333333333333333333333333333333 +8707 3333333333333333333333333333333333333333333333333333333 +8708 3333333333333333333333333333333333333333333333333333333 +8709 3333333333333333333333333333333333333333333333333333333 +8710 3333333333333333333333333333333333333333333333333333333 +8711 3333333333333333333333333333333333333333333333333333333 +8712 3333333333333333333333333333333333333333333333333333333 +8713 3333333333333333333333333333333333333333333333333333333 +8714 3333333333333333333333333333333333333333333333333333333 +8715 3333333333333333333333333333333333333333333333333333333 +8716 3333333333333333333333333333333333333333333333333333333 +8717 3333333333333333333333333333333333333333333333333333333 +8718 3333333333333333333333333333333333333333333333333333333 +8719 3333333333333333333333333333333333333333333333333333333 +8720 3333333333333333333333333333333333333333333333333333333 +8721 3333333333333333333333333333333333333333333333333333333 +8722 3333333333333333333333333333333333333333333333333333333 +8723 3333333333333333333333333333333333333333333333333333333 +8724 3333333333333333333333333333333333333333333333333333333 +8725 3333333333333333333333333333333333333333333333333333333 +8726 3333333333333333333333333333333333333333333333333333333 +8727 3333333333333333333333333333333333333333333333333333333 +8728 3333333333333333333333333333333333333333333333333333333 +8729 3333333333333333333333333333333333333333333333333333333 +8730 3333333333333333333333333333333333333333333333333333333 +8731 3333333333333333333333333333333333333333333333333333333 +8732 3333333333333333333333333333333333333333333333333333333 +8733 3333333333333333333333333333333333333333333333333333333 +8734 3333333333333333333333333333333333333333333333333333333 +8735 3333333333333333333333333333333333333333333333333333333 +8736 3333333333333333333333333333333333333333333333333333333 +8737 3333333333333333333333333333333333333333333333333333333 +8738 3333333333333333333333333333333333333333333333333333333 +8739 3333333333333333333333333333333333333333333333333333333 +8740 3333333333333333333333333333333333333333333333333333333 +8741 3333333333333333333333333333333333333333333333333333333 +8742 3333333333333333333333333333333333333333333333333333333 +8743 3333333333333333333333333333333333333333333333333333333 +8744 3333333333333333333333333333333333333333333333333333333 +8745 3333333333333333333333333333333333333333333333333333333 +8746 3333333333333333333333333333333333333333333333333333333 +8747 3333333333333333333333333333333333333333333333333333333 +8748 3333333333333333333333333333333333333333333333333333333 +8749 3333333333333333333333333333333333333333333333333333333 +8750 3333333333333333333333333333333333333333333333333333333 +8751 3333333333333333333333333333333333333333333333333333333 +8752 3333333333333333333333333333333333333333333333333333333 +8753 3333333333333333333333333333333333333333333333333333333 +8754 3333333333333333333333333333333333333333333333333333333 +8755 3333333333333333333333333333333333333333333333333333333 +8756 3333333333333333333333333333333333333333333333333333333 +8757 3333333333333333333333333333333333333333333333333333333 +8758 3333333333333333333333333333333333333333333333333333333 +8759 3333333333333333333333333333333333333333333333333333333 +8760 3333333333333333333333333333333333333333333333333333333 +8761 3333333333333333333333333333333333333333333333333333333 +8762 3333333333333333333333333333333333333333333333333333333 +8763 3333333333333333333333333333333333333333333333333333333 +8764 3333333333333333333333333333333333333333333333333333333 +8765 3333333333333333333333333333333333333333333333333333333 +8766 3333333333333333333333333333333333333333333333333333333 +8767 3333333333333333333333333333333333333333333333333333333 +8768 3333333333333333333333333333333333333333333333333333333 +8769 3333333333333333333333333333333333333333333333333333333 +8770 3333333333333333333333333333333333333333333333333333333 +8771 3333333333333333333333333333333333333333333333333333333 +8772 3333333333333333333333333333333333333333333333333333333 +8773 3333333333333333333333333333333333333333333333333333333 +8774 3333333333333333333333333333333333333333333333333333333 +8775 3333333333333333333333333333333333333333333333333333333 +8776 3333333333333333333333333333333333333333333333333333333 +8777 3333333333333333333333333333333333333333333333333333333 +8778 3333333333333333333333333333333333333333333333333333333 +8779 3333333333333333333333333333333333333333333333333333333 +8780 3333333333333333333333333333333333333333333333333333333 +8781 3333333333333333333333333333333333333333333333333333333 +8782 3333333333333333333333333333333333333333333333333333333 +8783 3333333333333333333333333333333333333333333333333333333 +8784 3333333333333333333333333333333333333333333333333333333 +8785 3333333333333333333333333333333333333333333333333333333 +8786 3333333333333333333333333333333333333333333333333333333 +8787 3333333333333333333333333333333333333333333333333333333 +8788 3333333333333333333333333333333333333333333333333333333 +8789 3333333333333333333333333333333333333333333333333333333 +8790 3333333333333333333333333333333333333333333333333333333 +8791 3333333333333333333333333333333333333333333333333333333 +8792 3333333333333333333333333333333333333333333333333333333 +8793 3333333333333333333333333333333333333333333333333333333 +8794 3333333333333333333333333333333333333333333333333333333 +8795 3333333333333333333333333333333333333333333333333333333 +8796 3333333333333333333333333333333333333333333333333333333 +8797 3333333333333333333333333333333333333333333333333333333 +8798 3333333333333333333333333333333333333333333333333333333 +8799 3333333333333333333333333333333333333333333333333333333 +8800 3333333333333333333333333333333333333333333333333333333 +8801 3333333333333333333333333333333333333333333333333333333 +8802 3333333333333333333333333333333333333333333333333333333 +8803 3333333333333333333333333333333333333333333333333333333 +8804 3333333333333333333333333333333333333333333333333333333 +8805 3333333333333333333333333333333333333333333333333333333 +8806 3333333333333333333333333333333333333333333333333333333 +8807 3333333333333333333333333333333333333333333333333333333 +8808 3333333333333333333333333333333333333333333333333333333 +8809 3333333333333333333333333333333333333333333333333333333 +8810 3333333333333333333333333333333333333333333333333333333 +8811 3333333333333333333333333333333333333333333333333333333 +8812 3333333333333333333333333333333333333333333333333333333 +8813 3333333333333333333333333333333333333333333333333333333 +8814 3333333333333333333333333333333333333333333333333333333 +8815 3333333333333333333333333333333333333333333333333333333 +8816 3333333333333333333333333333333333333333333333333333333 +8817 3333333333333333333333333333333333333333333333333333333 +8818 3333333333333333333333333333333333333333333333333333333 +8819 3333333333333333333333333333333333333333333333333333333 +8820 3333333333333333333333333333333333333333333333333333333 +8821 3333333333333333333333333333333333333333333333333333333 +8822 3333333333333333333333333333333333333333333333333333333 +8823 3333333333333333333333333333333333333333333333333333333 +8824 3333333333333333333333333333333333333333333333333333333 +8825 3333333333333333333333333333333333333333333333333333333 +8826 3333333333333333333333333333333333333333333333333333333 +8827 3333333333333333333333333333333333333333333333333333333 +8828 3333333333333333333333333333333333333333333333333333333 +8829 3333333333333333333333333333333333333333333333333333333 +8830 3333333333333333333333333333333333333333333333333333333 +8831 3333333333333333333333333333333333333333333333333333333 +8832 3333333333333333333333333333333333333333333333333333333 +8833 3333333333333333333333333333333333333333333333333333333 +8834 3333333333333333333333333333333333333333333333333333333 +8835 3333333333333333333333333333333333333333333333333333333 +8836 3333333333333333333333333333333333333333333333333333333 +8837 3333333333333333333333333333333333333333333333333333333 +8838 3333333333333333333333333333333333333333333333333333333 +8839 3333333333333333333333333333333333333333333333333333333 +8840 3333333333333333333333333333333333333333333333333333333 +8841 3333333333333333333333333333333333333333333333333333333 +8842 3333333333333333333333333333333333333333333333333333333 +8843 3333333333333333333333333333333333333333333333333333333 +8844 3333333333333333333333333333333333333333333333333333333 +8845 3333333333333333333333333333333333333333333333333333333 +8846 3333333333333333333333333333333333333333333333333333333 +8847 3333333333333333333333333333333333333333333333333333333 +8848 3333333333333333333333333333333333333333333333333333333 +8849 3333333333333333333333333333333333333333333333333333333 +8850 3333333333333333333333333333333333333333333333333333333 +8851 3333333333333333333333333333333333333333333333333333333 +8852 3333333333333333333333333333333333333333333333333333333 +8853 3333333333333333333333333333333333333333333333333333333 +8854 3333333333333333333333333333333333333333333333333333333 +8855 3333333333333333333333333333333333333333333333333333333 +8856 3333333333333333333333333333333333333333333333333333333 +8857 3333333333333333333333333333333333333333333333333333333 +8858 3333333333333333333333333333333333333333333333333333333 +8859 3333333333333333333333333333333333333333333333333333333 +8860 3333333333333333333333333333333333333333333333333333333 +8861 3333333333333333333333333333333333333333333333333333333 +8862 3333333333333333333333333333333333333333333333333333333 +8863 3333333333333333333333333333333333333333333333333333333 +8864 3333333333333333333333333333333333333333333333333333333 +8865 3333333333333333333333333333333333333333333333333333333 +8866 3333333333333333333333333333333333333333333333333333333 +8867 3333333333333333333333333333333333333333333333333333333 +8868 3333333333333333333333333333333333333333333333333333333 +8869 3333333333333333333333333333333333333333333333333333333 +8870 3333333333333333333333333333333333333333333333333333333 +8871 3333333333333333333333333333333333333333333333333333333 +8872 3333333333333333333333333333333333333333333333333333333 +8873 3333333333333333333333333333333333333333333333333333333 +8874 3333333333333333333333333333333333333333333333333333333 +8875 3333333333333333333333333333333333333333333333333333333 +8876 3333333333333333333333333333333333333333333333333333333 +8877 3333333333333333333333333333333333333333333333333333333 +8878 3333333333333333333333333333333333333333333333333333333 +8879 3333333333333333333333333333333333333333333333333333333 +8880 3333333333333333333333333333333333333333333333333333333 +8881 3333333333333333333333333333333333333333333333333333333 +8882 3333333333333333333333333333333333333333333333333333333 +8883 3333333333333333333333333333333333333333333333333333333 +8884 3333333333333333333333333333333333333333333333333333333 +8885 3333333333333333333333333333333333333333333333333333333 +8886 3333333333333333333333333333333333333333333333333333333 +8887 3333333333333333333333333333333333333333333333333333333 +8888 3333333333333333333333333333333333333333333333333333333 +8889 3333333333333333333333333333333333333333333333333333333 +8890 3333333333333333333333333333333333333333333333333333333 +8891 3333333333333333333333333333333333333333333333333333333 +8892 3333333333333333333333333333333333333333333333333333333 +8893 3333333333333333333333333333333333333333333333333333333 +8894 3333333333333333333333333333333333333333333333333333333 +8895 3333333333333333333333333333333333333333333333333333333 +8896 3333333333333333333333333333333333333333333333333333333 +8897 3333333333333333333333333333333333333333333333333333333 +8898 3333333333333333333333333333333333333333333333333333333 +8899 3333333333333333333333333333333333333333333333333333333 +8900 3333333333333333333333333333333333333333333333333333333 +8901 3333333333333333333333333333333333333333333333333333333 +8902 3333333333333333333333333333333333333333333333333333333 +8903 3333333333333333333333333333333333333333333333333333333 +8904 3333333333333333333333333333333333333333333333333333333 +8905 3333333333333333333333333333333333333333333333333333333 +8906 3333333333333333333333333333333333333333333333333333333 +8907 3333333333333333333333333333333333333333333333333333333 +8908 3333333333333333333333333333333333333333333333333333333 +8909 3333333333333333333333333333333333333333333333333333333 +8910 3333333333333333333333333333333333333333333333333333333 +8911 3333333333333333333333333333333333333333333333333333333 +8912 3333333333333333333333333333333333333333333333333333333 +8913 3333333333333333333333333333333333333333333333333333333 +8914 3333333333333333333333333333333333333333333333333333333 +8915 3333333333333333333333333333333333333333333333333333333 +8916 3333333333333333333333333333333333333333333333333333333 +8917 3333333333333333333333333333333333333333333333333333333 +8918 3333333333333333333333333333333333333333333333333333333 +8919 3333333333333333333333333333333333333333333333333333333 +8920 3333333333333333333333333333333333333333333333333333333 +8921 3333333333333333333333333333333333333333333333333333333 +8922 3333333333333333333333333333333333333333333333333333333 +8923 3333333333333333333333333333333333333333333333333333333 +8924 3333333333333333333333333333333333333333333333333333333 +8925 3333333333333333333333333333333333333333333333333333333 +8926 3333333333333333333333333333333333333333333333333333333 +8927 3333333333333333333333333333333333333333333333333333333 +8928 3333333333333333333333333333333333333333333333333333333 +8929 3333333333333333333333333333333333333333333333333333333 +8930 3333333333333333333333333333333333333333333333333333333 +8931 3333333333333333333333333333333333333333333333333333333 +8932 3333333333333333333333333333333333333333333333333333333 +8933 3333333333333333333333333333333333333333333333333333333 +8934 3333333333333333333333333333333333333333333333333333333 +8935 3333333333333333333333333333333333333333333333333333333 +8936 3333333333333333333333333333333333333333333333333333333 +8937 3333333333333333333333333333333333333333333333333333333 +8938 3333333333333333333333333333333333333333333333333333333 +8939 3333333333333333333333333333333333333333333333333333333 +8940 3333333333333333333333333333333333333333333333333333333 +8941 3333333333333333333333333333333333333333333333333333333 +8942 3333333333333333333333333333333333333333333333333333333 +8943 3333333333333333333333333333333333333333333333333333333 +8944 3333333333333333333333333333333333333333333333333333333 +8945 3333333333333333333333333333333333333333333333333333333 +8946 3333333333333333333333333333333333333333333333333333333 +8947 3333333333333333333333333333333333333333333333333333333 +8948 3333333333333333333333333333333333333333333333333333333 +8949 3333333333333333333333333333333333333333333333333333333 +8950 3333333333333333333333333333333333333333333333333333333 +8951 3333333333333333333333333333333333333333333333333333333 +8952 3333333333333333333333333333333333333333333333333333333 +8953 3333333333333333333333333333333333333333333333333333333 +8954 3333333333333333333333333333333333333333333333333333333 +8955 3333333333333333333333333333333333333333333333333333333 +8956 3333333333333333333333333333333333333333333333333333333 +8957 3333333333333333333333333333333333333333333333333333333 +8958 3333333333333333333333333333333333333333333333333333333 +8959 3333333333333333333333333333333333333333333333333333333 +8960 3333333333333333333333333333333333333333333333333333333 +8961 3333333333333333333333333333333333333333333333333333333 +8962 3333333333333333333333333333333333333333333333333333333 +8963 3333333333333333333333333333333333333333333333333333333 +8964 3333333333333333333333333333333333333333333333333333333 +8965 3333333333333333333333333333333333333333333333333333333 +8966 3333333333333333333333333333333333333333333333333333333 +8967 3333333333333333333333333333333333333333333333333333333 +8968 3333333333333333333333333333333333333333333333333333333 +8969 3333333333333333333333333333333333333333333333333333333 +8970 3333333333333333333333333333333333333333333333333333333 +8971 3333333333333333333333333333333333333333333333333333333 +8972 3333333333333333333333333333333333333333333333333333333 +8973 3333333333333333333333333333333333333333333333333333333 +8974 3333333333333333333333333333333333333333333333333333333 +8975 3333333333333333333333333333333333333333333333333333333 +8976 3333333333333333333333333333333333333333333333333333333 +8977 3333333333333333333333333333333333333333333333333333333 +8978 3333333333333333333333333333333333333333333333333333333 +8979 3333333333333333333333333333333333333333333333333333333 +8980 3333333333333333333333333333333333333333333333333333333 +8981 3333333333333333333333333333333333333333333333333333333 +8982 3333333333333333333333333333333333333333333333333333333 +8983 3333333333333333333333333333333333333333333333333333333 +8984 3333333333333333333333333333333333333333333333333333333 +8985 3333333333333333333333333333333333333333333333333333333 +8986 3333333333333333333333333333333333333333333333333333333 +8987 3333333333333333333333333333333333333333333333333333333 +8988 3333333333333333333333333333333333333333333333333333333 +8989 3333333333333333333333333333333333333333333333333333333 +8990 3333333333333333333333333333333333333333333333333333333 +8991 3333333333333333333333333333333333333333333333333333333 +8992 3333333333333333333333333333333333333333333333333333333 +8993 3333333333333333333333333333333333333333333333333333333 +8994 3333333333333333333333333333333333333333333333333333333 +8995 3333333333333333333333333333333333333333333333333333333 +8996 3333333333333333333333333333333333333333333333333333333 +8997 3333333333333333333333333333333333333333333333333333333 +8998 3333333333333333333333333333333333333333333333333333333 +8999 3333333333333333333333333333333333333333333333333333333 +9000 3333333333333333333333333333333333333333333333333333333 +9001 3333333333333333333333333333333333333333333333333333333 +9002 3333333333333333333333333333333333333333333333333333333 +9003 3333333333333333333333333333333333333333333333333333333 +9004 3333333333333333333333333333333333333333333333333333333 +9005 3333333333333333333333333333333333333333333333333333333 +9006 3333333333333333333333333333333333333333333333333333333 +9007 3333333333333333333333333333333333333333333333333333333 +9008 3333333333333333333333333333333333333333333333333333333 +9009 3333333333333333333333333333333333333333333333333333333 +9010 3333333333333333333333333333333333333333333333333333333 +9011 3333333333333333333333333333333333333333333333333333333 +9012 3333333333333333333333333333333333333333333333333333333 +9013 3333333333333333333333333333333333333333333333333333333 +9014 3333333333333333333333333333333333333333333333333333333 +9015 3333333333333333333333333333333333333333333333333333333 +9016 3333333333333333333333333333333333333333333333333333333 +9017 3333333333333333333333333333333333333333333333333333333 +9018 3333333333333333333333333333333333333333333333333333333 +9019 3333333333333333333333333333333333333333333333333333333 +9020 3333333333333333333333333333333333333333333333333333333 +9021 3333333333333333333333333333333333333333333333333333333 +9022 3333333333333333333333333333333333333333333333333333333 +9023 3333333333333333333333333333333333333333333333333333333 +9024 3333333333333333333333333333333333333333333333333333333 +9025 3333333333333333333333333333333333333333333333333333333 +9026 3333333333333333333333333333333333333333333333333333333 +9027 3333333333333333333333333333333333333333333333333333333 +9028 3333333333333333333333333333333333333333333333333333333 +9029 3333333333333333333333333333333333333333333333333333333 +9030 3333333333333333333333333333333333333333333333333333333 +9031 3333333333333333333333333333333333333333333333333333333 +9032 3333333333333333333333333333333333333333333333333333333 +9033 3333333333333333333333333333333333333333333333333333333 +9034 3333333333333333333333333333333333333333333333333333333 +9035 3333333333333333333333333333333333333333333333333333333 +9036 3333333333333333333333333333333333333333333333333333333 +9037 3333333333333333333333333333333333333333333333333333333 +9038 3333333333333333333333333333333333333333333333333333333 +9039 3333333333333333333333333333333333333333333333333333333 +9040 3333333333333333333333333333333333333333333333333333333 +9041 3333333333333333333333333333333333333333333333333333333 +9042 3333333333333333333333333333333333333333333333333333333 +9043 3333333333333333333333333333333333333333333333333333333 +9044 3333333333333333333333333333333333333333333333333333333 +9045 3333333333333333333333333333333333333333333333333333333 +9046 3333333333333333333333333333333333333333333333333333333 +9047 3333333333333333333333333333333333333333333333333333333 +9048 3333333333333333333333333333333333333333333333333333333 +9049 3333333333333333333333333333333333333333333333333333333 +9050 3333333333333333333333333333333333333333333333333333333 +9051 3333333333333333333333333333333333333333333333333333333 +9052 3333333333333333333333333333333333333333333333333333333 +9053 3333333333333333333333333333333333333333333333333333333 +9054 3333333333333333333333333333333333333333333333333333333 +9055 3333333333333333333333333333333333333333333333333333333 +9056 3333333333333333333333333333333333333333333333333333333 +9057 3333333333333333333333333333333333333333333333333333333 +9058 3333333333333333333333333333333333333333333333333333333 +9059 3333333333333333333333333333333333333333333333333333333 +9060 3333333333333333333333333333333333333333333333333333333 +9061 3333333333333333333333333333333333333333333333333333333 +9062 3333333333333333333333333333333333333333333333333333333 +9063 3333333333333333333333333333333333333333333333333333333 +9064 3333333333333333333333333333333333333333333333333333333 +9065 3333333333333333333333333333333333333333333333333333333 +9066 3333333333333333333333333333333333333333333333333333333 +9067 3333333333333333333333333333333333333333333333333333333 +9068 3333333333333333333333333333333333333333333333333333333 +9069 3333333333333333333333333333333333333333333333333333333 +9070 3333333333333333333333333333333333333333333333333333333 +9071 3333333333333333333333333333333333333333333333333333333 +9072 3333333333333333333333333333333333333333333333333333333 +9073 3333333333333333333333333333333333333333333333333333333 +9074 3333333333333333333333333333333333333333333333333333333 +9075 3333333333333333333333333333333333333333333333333333333 +9076 3333333333333333333333333333333333333333333333333333333 +9077 3333333333333333333333333333333333333333333333333333333 +9078 3333333333333333333333333333333333333333333333333333333 +9079 3333333333333333333333333333333333333333333333333333333 +9080 3333333333333333333333333333333333333333333333333333333 +9081 3333333333333333333333333333333333333333333333333333333 +9082 3333333333333333333333333333333333333333333333333333333 +9083 3333333333333333333333333333333333333333333333333333333 +9084 3333333333333333333333333333333333333333333333333333333 +9085 3333333333333333333333333333333333333333333333333333333 +9086 3333333333333333333333333333333333333333333333333333333 +9087 3333333333333333333333333333333333333333333333333333333 +9088 3333333333333333333333333333333333333333333333333333333 +9089 3333333333333333333333333333333333333333333333333333333 +9090 3333333333333333333333333333333333333333333333333333333 +9091 3333333333333333333333333333333333333333333333333333333 +9092 3333333333333333333333333333333333333333333333333333333 +9093 3333333333333333333333333333333333333333333333333333333 +9094 3333333333333333333333333333333333333333333333333333333 +9095 3333333333333333333333333333333333333333333333333333333 +9096 3333333333333333333333333333333333333333333333333333333 +9097 3333333333333333333333333333333333333333333333333333333 +9098 3333333333333333333333333333333333333333333333333333333 +9099 3333333333333333333333333333333333333333333333333333333 +9100 3333333333333333333333333333333333333333333333333333333 +9101 3333333333333333333333333333333333333333333333333333333 +9102 3333333333333333333333333333333333333333333333333333333 +9103 3333333333333333333333333333333333333333333333333333333 +9104 3333333333333333333333333333333333333333333333333333333 +9105 3333333333333333333333333333333333333333333333333333333 +9106 3333333333333333333333333333333333333333333333333333333 +9107 3333333333333333333333333333333333333333333333333333333 +9108 3333333333333333333333333333333333333333333333333333333 +9109 3333333333333333333333333333333333333333333333333333333 +9110 3333333333333333333333333333333333333333333333333333333 +9111 3333333333333333333333333333333333333333333333333333333 +9112 3333333333333333333333333333333333333333333333333333333 +9113 3333333333333333333333333333333333333333333333333333333 +9114 3333333333333333333333333333333333333333333333333333333 +9115 3333333333333333333333333333333333333333333333333333333 +9116 3333333333333333333333333333333333333333333333333333333 +9117 3333333333333333333333333333333333333333333333333333333 +9118 3333333333333333333333333333333333333333333333333333333 +9119 3333333333333333333333333333333333333333333333333333333 +9120 3333333333333333333333333333333333333333333333333333333 +9121 3333333333333333333333333333333333333333333333333333333 +9122 3333333333333333333333333333333333333333333333333333333 +9123 3333333333333333333333333333333333333333333333333333333 +9124 3333333333333333333333333333333333333333333333333333333 +9125 3333333333333333333333333333333333333333333333333333333 +9126 3333333333333333333333333333333333333333333333333333333 +9127 3333333333333333333333333333333333333333333333333333333 +9128 3333333333333333333333333333333333333333333333333333333 +9129 3333333333333333333333333333333333333333333333333333333 +9130 3333333333333333333333333333333333333333333333333333333 +9131 3333333333333333333333333333333333333333333333333333333 +9132 3333333333333333333333333333333333333333333333333333333 +9133 3333333333333333333333333333333333333333333333333333333 +9134 3333333333333333333333333333333333333333333333333333333 +9135 3333333333333333333333333333333333333333333333333333333 +9136 3333333333333333333333333333333333333333333333333333333 +9137 3333333333333333333333333333333333333333333333333333333 +9138 3333333333333333333333333333333333333333333333333333333 +9139 3333333333333333333333333333333333333333333333333333333 +9140 3333333333333333333333333333333333333333333333333333333 +9141 3333333333333333333333333333333333333333333333333333333 +9142 3333333333333333333333333333333333333333333333333333333 +9143 3333333333333333333333333333333333333333333333333333333 +9144 3333333333333333333333333333333333333333333333333333333 +9145 3333333333333333333333333333333333333333333333333333333 +9146 3333333333333333333333333333333333333333333333333333333 +9147 3333333333333333333333333333333333333333333333333333333 +9148 3333333333333333333333333333333333333333333333333333333 +9149 3333333333333333333333333333333333333333333333333333333 +9150 3333333333333333333333333333333333333333333333333333333 +9151 3333333333333333333333333333333333333333333333333333333 +9152 3333333333333333333333333333333333333333333333333333333 +9153 3333333333333333333333333333333333333333333333333333333 +9154 3333333333333333333333333333333333333333333333333333333 +9155 3333333333333333333333333333333333333333333333333333333 +9156 3333333333333333333333333333333333333333333333333333333 +9157 3333333333333333333333333333333333333333333333333333333 +9158 3333333333333333333333333333333333333333333333333333333 +9159 3333333333333333333333333333333333333333333333333333333 +9160 3333333333333333333333333333333333333333333333333333333 +9161 3333333333333333333333333333333333333333333333333333333 +9162 3333333333333333333333333333333333333333333333333333333 +9163 3333333333333333333333333333333333333333333333333333333 +9164 3333333333333333333333333333333333333333333333333333333 +9165 3333333333333333333333333333333333333333333333333333333 +9166 3333333333333333333333333333333333333333333333333333333 +9167 3333333333333333333333333333333333333333333333333333333 +9168 3333333333333333333333333333333333333333333333333333333 +9169 3333333333333333333333333333333333333333333333333333333 +9170 3333333333333333333333333333333333333333333333333333333 +9171 3333333333333333333333333333333333333333333333333333333 +9172 3333333333333333333333333333333333333333333333333333333 +9173 3333333333333333333333333333333333333333333333333333333 +9174 3333333333333333333333333333333333333333333333333333333 +9175 3333333333333333333333333333333333333333333333333333333 +9176 3333333333333333333333333333333333333333333333333333333 +9177 3333333333333333333333333333333333333333333333333333333 +9178 3333333333333333333333333333333333333333333333333333333 +9179 3333333333333333333333333333333333333333333333333333333 +9180 3333333333333333333333333333333333333333333333333333333 +9181 3333333333333333333333333333333333333333333333333333333 +9182 3333333333333333333333333333333333333333333333333333333 +9183 3333333333333333333333333333333333333333333333333333333 +9184 3333333333333333333333333333333333333333333333333333333 +9185 3333333333333333333333333333333333333333333333333333333 +9186 3333333333333333333333333333333333333333333333333333333 +9187 3333333333333333333333333333333333333333333333333333333 +9188 3333333333333333333333333333333333333333333333333333333 +9189 3333333333333333333333333333333333333333333333333333333 +9190 3333333333333333333333333333333333333333333333333333333 +9191 3333333333333333333333333333333333333333333333333333333 +9192 3333333333333333333333333333333333333333333333333333333 +9193 3333333333333333333333333333333333333333333333333333333 +9194 3333333333333333333333333333333333333333333333333333333 +9195 3333333333333333333333333333333333333333333333333333333 +9196 3333333333333333333333333333333333333333333333333333333 +9197 3333333333333333333333333333333333333333333333333333333 +9198 3333333333333333333333333333333333333333333333333333333 +9199 3333333333333333333333333333333333333333333333333333333 +9200 3333333333333333333333333333333333333333333333333333333 +9201 3333333333333333333333333333333333333333333333333333333 +9202 3333333333333333333333333333333333333333333333333333333 +9203 3333333333333333333333333333333333333333333333333333333 +9204 3333333333333333333333333333333333333333333333333333333 +9205 3333333333333333333333333333333333333333333333333333333 +9206 3333333333333333333333333333333333333333333333333333333 +9207 3333333333333333333333333333333333333333333333333333333 +9208 3333333333333333333333333333333333333333333333333333333 +9209 3333333333333333333333333333333333333333333333333333333 +9210 3333333333333333333333333333333333333333333333333333333 +9211 3333333333333333333333333333333333333333333333333333333 +9212 3333333333333333333333333333333333333333333333333333333 +9213 3333333333333333333333333333333333333333333333333333333 +9214 3333333333333333333333333333333333333333333333333333333 +9215 3333333333333333333333333333333333333333333333333333333 +9216 3333333333333333333333333333333333333333333333333333333 +9217 3333333333333333333333333333333333333333333333333333333 +9218 3333333333333333333333333333333333333333333333333333333 +9219 3333333333333333333333333333333333333333333333333333333 +9220 3333333333333333333333333333333333333333333333333333333 +9221 3333333333333333333333333333333333333333333333333333333 +9222 3333333333333333333333333333333333333333333333333333333 +9223 3333333333333333333333333333333333333333333333333333333 +9224 3333333333333333333333333333333333333333333333333333333 +9225 3333333333333333333333333333333333333333333333333333333 +9226 3333333333333333333333333333333333333333333333333333333 +9227 3333333333333333333333333333333333333333333333333333333 +9228 3333333333333333333333333333333333333333333333333333333 +9229 3333333333333333333333333333333333333333333333333333333 +9230 3333333333333333333333333333333333333333333333333333333 +9231 3333333333333333333333333333333333333333333333333333333 +9232 3333333333333333333333333333333333333333333333333333333 +9233 3333333333333333333333333333333333333333333333333333333 +9234 3333333333333333333333333333333333333333333333333333333 +9235 3333333333333333333333333333333333333333333333333333333 +9236 3333333333333333333333333333333333333333333333333333333 +9237 3333333333333333333333333333333333333333333333333333333 +9238 3333333333333333333333333333333333333333333333333333333 +9239 3333333333333333333333333333333333333333333333333333333 +9240 3333333333333333333333333333333333333333333333333333333 +9241 3333333333333333333333333333333333333333333333333333333 +9242 3333333333333333333333333333333333333333333333333333333 +9243 3333333333333333333333333333333333333333333333333333333 +9244 3333333333333333333333333333333333333333333333333333333 +9245 3333333333333333333333333333333333333333333333333333333 +9246 3333333333333333333333333333333333333333333333333333333 +9247 3333333333333333333333333333333333333333333333333333333 +9248 3333333333333333333333333333333333333333333333333333333 +9249 3333333333333333333333333333333333333333333333333333333 +9250 3333333333333333333333333333333333333333333333333333333 +9251 3333333333333333333333333333333333333333333333333333333 +9252 3333333333333333333333333333333333333333333333333333333 +9253 3333333333333333333333333333333333333333333333333333333 +9254 3333333333333333333333333333333333333333333333333333333 +9255 3333333333333333333333333333333333333333333333333333333 +9256 3333333333333333333333333333333333333333333333333333333 +9257 3333333333333333333333333333333333333333333333333333333 +9258 3333333333333333333333333333333333333333333333333333333 +9259 3333333333333333333333333333333333333333333333333333333 +9260 3333333333333333333333333333333333333333333333333333333 +9261 3333333333333333333333333333333333333333333333333333333 +9262 3333333333333333333333333333333333333333333333333333333 +9263 3333333333333333333333333333333333333333333333333333333 +9264 3333333333333333333333333333333333333333333333333333333 +9265 3333333333333333333333333333333333333333333333333333333 +9266 3333333333333333333333333333333333333333333333333333333 +9267 3333333333333333333333333333333333333333333333333333333 +9268 3333333333333333333333333333333333333333333333333333333 +9269 3333333333333333333333333333333333333333333333333333333 +9270 3333333333333333333333333333333333333333333333333333333 +9271 3333333333333333333333333333333333333333333333333333333 +9272 3333333333333333333333333333333333333333333333333333333 +9273 3333333333333333333333333333333333333333333333333333333 +9274 3333333333333333333333333333333333333333333333333333333 +9275 3333333333333333333333333333333333333333333333333333333 +9276 3333333333333333333333333333333333333333333333333333333 +9277 3333333333333333333333333333333333333333333333333333333 +9278 3333333333333333333333333333333333333333333333333333333 +9279 3333333333333333333333333333333333333333333333333333333 +9280 3333333333333333333333333333333333333333333333333333333 +9281 3333333333333333333333333333333333333333333333333333333 +9282 3333333333333333333333333333333333333333333333333333333 +9283 3333333333333333333333333333333333333333333333333333333 +9284 3333333333333333333333333333333333333333333333333333333 +9285 3333333333333333333333333333333333333333333333333333333 +9286 3333333333333333333333333333333333333333333333333333333 +9287 3333333333333333333333333333333333333333333333333333333 +9288 3333333333333333333333333333333333333333333333333333333 +9289 3333333333333333333333333333333333333333333333333333333 +9290 3333333333333333333333333333333333333333333333333333333 +9291 3333333333333333333333333333333333333333333333333333333 +9292 3333333333333333333333333333333333333333333333333333333 +9293 3333333333333333333333333333333333333333333333333333333 +9294 3333333333333333333333333333333333333333333333333333333 +9295 3333333333333333333333333333333333333333333333333333333 +9296 3333333333333333333333333333333333333333333333333333333 +9297 3333333333333333333333333333333333333333333333333333333 +9298 3333333333333333333333333333333333333333333333333333333 +9299 3333333333333333333333333333333333333333333333333333333 +9300 3333333333333333333333333333333333333333333333333333333 +9301 3333333333333333333333333333333333333333333333333333333 +9302 3333333333333333333333333333333333333333333333333333333 +9303 3333333333333333333333333333333333333333333333333333333 +9304 3333333333333333333333333333333333333333333333333333333 +9305 3333333333333333333333333333333333333333333333333333333 +9306 3333333333333333333333333333333333333333333333333333333 +9307 3333333333333333333333333333333333333333333333333333333 +9308 3333333333333333333333333333333333333333333333333333333 +9309 3333333333333333333333333333333333333333333333333333333 +9310 3333333333333333333333333333333333333333333333333333333 +9311 3333333333333333333333333333333333333333333333333333333 +9312 3333333333333333333333333333333333333333333333333333333 +9313 3333333333333333333333333333333333333333333333333333333 +9314 3333333333333333333333333333333333333333333333333333333 +9315 3333333333333333333333333333333333333333333333333333333 +9316 3333333333333333333333333333333333333333333333333333333 +9317 3333333333333333333333333333333333333333333333333333333 +9318 3333333333333333333333333333333333333333333333333333333 +9319 3333333333333333333333333333333333333333333333333333333 +9320 3333333333333333333333333333333333333333333333333333333 +9321 3333333333333333333333333333333333333333333333333333333 +9322 3333333333333333333333333333333333333333333333333333333 +9323 3333333333333333333333333333333333333333333333333333333 +9324 3333333333333333333333333333333333333333333333333333333 +9325 3333333333333333333333333333333333333333333333333333333 +9326 3333333333333333333333333333333333333333333333333333333 +9327 3333333333333333333333333333333333333333333333333333333 +9328 3333333333333333333333333333333333333333333333333333333 +9329 3333333333333333333333333333333333333333333333333333333 +9330 3333333333333333333333333333333333333333333333333333333 +9331 3333333333333333333333333333333333333333333333333333333 +9332 3333333333333333333333333333333333333333333333333333333 +9333 3333333333333333333333333333333333333333333333333333333 +9334 3333333333333333333333333333333333333333333333333333333 +9335 3333333333333333333333333333333333333333333333333333333 +9336 3333333333333333333333333333333333333333333333333333333 +9337 3333333333333333333333333333333333333333333333333333333 +9338 3333333333333333333333333333333333333333333333333333333 +9339 3333333333333333333333333333333333333333333333333333333 +9340 3333333333333333333333333333333333333333333333333333333 +9341 3333333333333333333333333333333333333333333333333333333 +9342 3333333333333333333333333333333333333333333333333333333 +9343 3333333333333333333333333333333333333333333333333333333 +9344 3333333333333333333333333333333333333333333333333333333 +9345 3333333333333333333333333333333333333333333333333333333 +9346 3333333333333333333333333333333333333333333333333333333 +9347 3333333333333333333333333333333333333333333333333333333 +9348 3333333333333333333333333333333333333333333333333333333 +9349 3333333333333333333333333333333333333333333333333333333 +9350 3333333333333333333333333333333333333333333333333333333 +9351 3333333333333333333333333333333333333333333333333333333 +9352 3333333333333333333333333333333333333333333333333333333 +9353 3333333333333333333333333333333333333333333333333333333 +9354 3333333333333333333333333333333333333333333333333333333 +9355 3333333333333333333333333333333333333333333333333333333 +9356 3333333333333333333333333333333333333333333333333333333 +9357 3333333333333333333333333333333333333333333333333333333 +9358 3333333333333333333333333333333333333333333333333333333 +9359 3333333333333333333333333333333333333333333333333333333 +9360 3333333333333333333333333333333333333333333333333333333 +9361 3333333333333333333333333333333333333333333333333333333 +9362 3333333333333333333333333333333333333333333333333333333 +9363 3333333333333333333333333333333333333333333333333333333 +9364 3333333333333333333333333333333333333333333333333333333 +9365 3333333333333333333333333333333333333333333333333333333 +9366 3333333333333333333333333333333333333333333333333333333 +9367 3333333333333333333333333333333333333333333333333333333 +9368 3333333333333333333333333333333333333333333333333333333 +9369 3333333333333333333333333333333333333333333333333333333 +9370 3333333333333333333333333333333333333333333333333333333 +9371 3333333333333333333333333333333333333333333333333333333 +9372 3333333333333333333333333333333333333333333333333333333 +9373 3333333333333333333333333333333333333333333333333333333 +9374 3333333333333333333333333333333333333333333333333333333 +9375 3333333333333333333333333333333333333333333333333333333 +9376 3333333333333333333333333333333333333333333333333333333 +9377 3333333333333333333333333333333333333333333333333333333 +9378 3333333333333333333333333333333333333333333333333333333 +9379 3333333333333333333333333333333333333333333333333333333 +9380 3333333333333333333333333333333333333333333333333333333 +9381 3333333333333333333333333333333333333333333333333333333 +9382 3333333333333333333333333333333333333333333333333333333 +9383 3333333333333333333333333333333333333333333333333333333 +9384 3333333333333333333333333333333333333333333333333333333 +9385 3333333333333333333333333333333333333333333333333333333 +9386 3333333333333333333333333333333333333333333333333333333 +9387 3333333333333333333333333333333333333333333333333333333 +9388 3333333333333333333333333333333333333333333333333333333 +9389 3333333333333333333333333333333333333333333333333333333 +9390 3333333333333333333333333333333333333333333333333333333 +9391 3333333333333333333333333333333333333333333333333333333 +9392 3333333333333333333333333333333333333333333333333333333 +9393 3333333333333333333333333333333333333333333333333333333 +9394 3333333333333333333333333333333333333333333333333333333 +9395 3333333333333333333333333333333333333333333333333333333 +9396 3333333333333333333333333333333333333333333333333333333 +9397 3333333333333333333333333333333333333333333333333333333 +9398 3333333333333333333333333333333333333333333333333333333 +9399 3333333333333333333333333333333333333333333333333333333 +9400 3333333333333333333333333333333333333333333333333333333 +9401 3333333333333333333333333333333333333333333333333333333 +9402 3333333333333333333333333333333333333333333333333333333 +9403 3333333333333333333333333333333333333333333333333333333 +9404 3333333333333333333333333333333333333333333333333333333 +9405 3333333333333333333333333333333333333333333333333333333 +9406 3333333333333333333333333333333333333333333333333333333 +9407 3333333333333333333333333333333333333333333333333333333 +9408 3333333333333333333333333333333333333333333333333333333 +9409 3333333333333333333333333333333333333333333333333333333 +9410 3333333333333333333333333333333333333333333333333333333 +9411 3333333333333333333333333333333333333333333333333333333 +9412 3333333333333333333333333333333333333333333333333333333 +9413 3333333333333333333333333333333333333333333333333333333 +9414 3333333333333333333333333333333333333333333333333333333 +9415 3333333333333333333333333333333333333333333333333333333 +9416 3333333333333333333333333333333333333333333333333333333 +9417 3333333333333333333333333333333333333333333333333333333 +9418 3333333333333333333333333333333333333333333333333333333 +9419 3333333333333333333333333333333333333333333333333333333 +9420 3333333333333333333333333333333333333333333333333333333 +9421 3333333333333333333333333333333333333333333333333333333 +9422 3333333333333333333333333333333333333333333333333333333 +9423 3333333333333333333333333333333333333333333333333333333 +9424 3333333333333333333333333333333333333333333333333333333 +9425 3333333333333333333333333333333333333333333333333333333 +9426 3333333333333333333333333333333333333333333333333333333 +9427 3333333333333333333333333333333333333333333333333333333 +9428 3333333333333333333333333333333333333333333333333333333 +9429 3333333333333333333333333333333333333333333333333333333 +9430 3333333333333333333333333333333333333333333333333333333 +9431 3333333333333333333333333333333333333333333333333333333 +9432 3333333333333333333333333333333333333333333333333333333 +9433 3333333333333333333333333333333333333333333333333333333 +9434 3333333333333333333333333333333333333333333333333333333 +9435 3333333333333333333333333333333333333333333333333333333 +9436 3333333333333333333333333333333333333333333333333333333 +9437 3333333333333333333333333333333333333333333333333333333 +9438 3333333333333333333333333333333333333333333333333333333 +9439 3333333333333333333333333333333333333333333333333333333 +9440 3333333333333333333333333333333333333333333333333333333 +9441 3333333333333333333333333333333333333333333333333333333 +9442 3333333333333333333333333333333333333333333333333333333 +9443 3333333333333333333333333333333333333333333333333333333 +9444 3333333333333333333333333333333333333333333333333333333 +9445 3333333333333333333333333333333333333333333333333333333 +9446 3333333333333333333333333333333333333333333333333333333 +9447 3333333333333333333333333333333333333333333333333333333 +9448 3333333333333333333333333333333333333333333333333333333 +9449 3333333333333333333333333333333333333333333333333333333 +9450 3333333333333333333333333333333333333333333333333333333 +9451 3333333333333333333333333333333333333333333333333333333 +9452 3333333333333333333333333333333333333333333333333333333 +9453 3333333333333333333333333333333333333333333333333333333 +9454 3333333333333333333333333333333333333333333333333333333 +9455 3333333333333333333333333333333333333333333333333333333 +9456 3333333333333333333333333333333333333333333333333333333 +9457 3333333333333333333333333333333333333333333333333333333 +9458 3333333333333333333333333333333333333333333333333333333 +9459 3333333333333333333333333333333333333333333333333333333 +9460 3333333333333333333333333333333333333333333333333333333 +9461 3333333333333333333333333333333333333333333333333333333 +9462 3333333333333333333333333333333333333333333333333333333 +9463 3333333333333333333333333333333333333333333333333333333 +9464 3333333333333333333333333333333333333333333333333333333 +9465 3333333333333333333333333333333333333333333333333333333 +9466 3333333333333333333333333333333333333333333333333333333 +9467 3333333333333333333333333333333333333333333333333333333 +9468 3333333333333333333333333333333333333333333333333333333 +9469 3333333333333333333333333333333333333333333333333333333 +9470 3333333333333333333333333333333333333333333333333333333 +9471 3333333333333333333333333333333333333333333333333333333 +9472 3333333333333333333333333333333333333333333333333333333 +9473 3333333333333333333333333333333333333333333333333333333 +9474 3333333333333333333333333333333333333333333333333333333 +9475 3333333333333333333333333333333333333333333333333333333 +9476 3333333333333333333333333333333333333333333333333333333 +9477 3333333333333333333333333333333333333333333333333333333 +9478 3333333333333333333333333333333333333333333333333333333 +9479 3333333333333333333333333333333333333333333333333333333 +9480 3333333333333333333333333333333333333333333333333333333 +9481 3333333333333333333333333333333333333333333333333333333 +9482 3333333333333333333333333333333333333333333333333333333 +9483 3333333333333333333333333333333333333333333333333333333 +9484 3333333333333333333333333333333333333333333333333333333 +9485 3333333333333333333333333333333333333333333333333333333 +9486 3333333333333333333333333333333333333333333333333333333 +9487 3333333333333333333333333333333333333333333333333333333 +9488 3333333333333333333333333333333333333333333333333333333 +9489 3333333333333333333333333333333333333333333333333333333 +9490 3333333333333333333333333333333333333333333333333333333 +9491 3333333333333333333333333333333333333333333333333333333 +9492 3333333333333333333333333333333333333333333333333333333 +9493 3333333333333333333333333333333333333333333333333333333 +9494 3333333333333333333333333333333333333333333333333333333 +9495 3333333333333333333333333333333333333333333333333333333 +9496 3333333333333333333333333333333333333333333333333333333 +9497 3333333333333333333333333333333333333333333333333333333 +9498 3333333333333333333333333333333333333333333333333333333 +9499 3333333333333333333333333333333333333333333333333333333 +9500 3333333333333333333333333333333333333333333333333333333 +9501 3333333333333333333333333333333333333333333333333333333 +9502 3333333333333333333333333333333333333333333333333333333 +9503 3333333333333333333333333333333333333333333333333333333 +9504 3333333333333333333333333333333333333333333333333333333 +9505 3333333333333333333333333333333333333333333333333333333 +9506 3333333333333333333333333333333333333333333333333333333 +9507 3333333333333333333333333333333333333333333333333333333 +9508 3333333333333333333333333333333333333333333333333333333 +9509 3333333333333333333333333333333333333333333333333333333 +9510 3333333333333333333333333333333333333333333333333333333 +9511 3333333333333333333333333333333333333333333333333333333 +9512 3333333333333333333333333333333333333333333333333333333 +9513 3333333333333333333333333333333333333333333333333333333 +9514 3333333333333333333333333333333333333333333333333333333 +9515 3333333333333333333333333333333333333333333333333333333 +9516 3333333333333333333333333333333333333333333333333333333 +9517 3333333333333333333333333333333333333333333333333333333 +9518 3333333333333333333333333333333333333333333333333333333 +9519 3333333333333333333333333333333333333333333333333333333 +9520 3333333333333333333333333333333333333333333333333333333 +9521 3333333333333333333333333333333333333333333333333333333 +9522 3333333333333333333333333333333333333333333333333333333 +9523 3333333333333333333333333333333333333333333333333333333 +9524 3333333333333333333333333333333333333333333333333333333 +9525 3333333333333333333333333333333333333333333333333333333 +9526 3333333333333333333333333333333333333333333333333333333 +9527 3333333333333333333333333333333333333333333333333333333 +9528 3333333333333333333333333333333333333333333333333333333 +9529 3333333333333333333333333333333333333333333333333333333 +9530 3333333333333333333333333333333333333333333333333333333 +9531 3333333333333333333333333333333333333333333333333333333 +9532 3333333333333333333333333333333333333333333333333333333 +9533 3333333333333333333333333333333333333333333333333333333 +9534 3333333333333333333333333333333333333333333333333333333 +9535 3333333333333333333333333333333333333333333333333333333 +9536 3333333333333333333333333333333333333333333333333333333 +9537 3333333333333333333333333333333333333333333333333333333 +9538 3333333333333333333333333333333333333333333333333333333 +9539 3333333333333333333333333333333333333333333333333333333 +9540 3333333333333333333333333333333333333333333333333333333 +9541 3333333333333333333333333333333333333333333333333333333 +9542 3333333333333333333333333333333333333333333333333333333 +9543 3333333333333333333333333333333333333333333333333333333 +9544 3333333333333333333333333333333333333333333333333333333 +9545 3333333333333333333333333333333333333333333333333333333 +9546 3333333333333333333333333333333333333333333333333333333 +9547 3333333333333333333333333333333333333333333333333333333 +9548 3333333333333333333333333333333333333333333333333333333 +9549 3333333333333333333333333333333333333333333333333333333 +9550 3333333333333333333333333333333333333333333333333333333 +9551 3333333333333333333333333333333333333333333333333333333 +9552 3333333333333333333333333333333333333333333333333333333 +9553 3333333333333333333333333333333333333333333333333333333 +9554 3333333333333333333333333333333333333333333333333333333 +9555 3333333333333333333333333333333333333333333333333333333 +9556 3333333333333333333333333333333333333333333333333333333 +9557 3333333333333333333333333333333333333333333333333333333 +9558 3333333333333333333333333333333333333333333333333333333 +9559 3333333333333333333333333333333333333333333333333333333 +9560 3333333333333333333333333333333333333333333333333333333 +9561 3333333333333333333333333333333333333333333333333333333 +9562 3333333333333333333333333333333333333333333333333333333 +9563 3333333333333333333333333333333333333333333333333333333 +9564 3333333333333333333333333333333333333333333333333333333 +9565 3333333333333333333333333333333333333333333333333333333 +9566 3333333333333333333333333333333333333333333333333333333 +9567 3333333333333333333333333333333333333333333333333333333 +9568 3333333333333333333333333333333333333333333333333333333 +9569 3333333333333333333333333333333333333333333333333333333 +9570 3333333333333333333333333333333333333333333333333333333 +9571 3333333333333333333333333333333333333333333333333333333 +9572 3333333333333333333333333333333333333333333333333333333 +9573 3333333333333333333333333333333333333333333333333333333 +9574 3333333333333333333333333333333333333333333333333333333 +9575 3333333333333333333333333333333333333333333333333333333 +9576 3333333333333333333333333333333333333333333333333333333 +9577 3333333333333333333333333333333333333333333333333333333 +9578 3333333333333333333333333333333333333333333333333333333 +9579 3333333333333333333333333333333333333333333333333333333 +9580 3333333333333333333333333333333333333333333333333333333 +9581 3333333333333333333333333333333333333333333333333333333 +9582 3333333333333333333333333333333333333333333333333333333 +9583 3333333333333333333333333333333333333333333333333333333 +9584 3333333333333333333333333333333333333333333333333333333 +9585 3333333333333333333333333333333333333333333333333333333 +9586 3333333333333333333333333333333333333333333333333333333 +9587 3333333333333333333333333333333333333333333333333333333 +9588 3333333333333333333333333333333333333333333333333333333 +9589 3333333333333333333333333333333333333333333333333333333 +9590 3333333333333333333333333333333333333333333333333333333 +9591 3333333333333333333333333333333333333333333333333333333 +9592 3333333333333333333333333333333333333333333333333333333 +9593 3333333333333333333333333333333333333333333333333333333 +9594 3333333333333333333333333333333333333333333333333333333 +9595 3333333333333333333333333333333333333333333333333333333 +9596 3333333333333333333333333333333333333333333333333333333 +9597 3333333333333333333333333333333333333333333333333333333 +9598 3333333333333333333333333333333333333333333333333333333 +9599 3333333333333333333333333333333333333333333333333333333 +9600 3333333333333333333333333333333333333333333333333333333 +9601 3333333333333333333333333333333333333333333333333333333 +9602 3333333333333333333333333333333333333333333333333333333 +9603 3333333333333333333333333333333333333333333333333333333 +9604 3333333333333333333333333333333333333333333333333333333 +9605 3333333333333333333333333333333333333333333333333333333 +9606 3333333333333333333333333333333333333333333333333333333 +9607 3333333333333333333333333333333333333333333333333333333 +9608 3333333333333333333333333333333333333333333333333333333 +9609 3333333333333333333333333333333333333333333333333333333 +9610 3333333333333333333333333333333333333333333333333333333 +9611 3333333333333333333333333333333333333333333333333333333 +9612 3333333333333333333333333333333333333333333333333333333 +9613 3333333333333333333333333333333333333333333333333333333 +9614 3333333333333333333333333333333333333333333333333333333 +9615 3333333333333333333333333333333333333333333333333333333 +9616 3333333333333333333333333333333333333333333333333333333 +9617 3333333333333333333333333333333333333333333333333333333 +9618 3333333333333333333333333333333333333333333333333333333 +9619 3333333333333333333333333333333333333333333333333333333 +9620 3333333333333333333333333333333333333333333333333333333 +9621 3333333333333333333333333333333333333333333333333333333 +9622 3333333333333333333333333333333333333333333333333333333 +9623 3333333333333333333333333333333333333333333333333333333 +9624 3333333333333333333333333333333333333333333333333333333 +9625 3333333333333333333333333333333333333333333333333333333 +9626 3333333333333333333333333333333333333333333333333333333 +9627 3333333333333333333333333333333333333333333333333333333 +9628 3333333333333333333333333333333333333333333333333333333 +9629 3333333333333333333333333333333333333333333333333333333 +9630 3333333333333333333333333333333333333333333333333333333 +9631 3333333333333333333333333333333333333333333333333333333 +9632 3333333333333333333333333333333333333333333333333333333 +9633 3333333333333333333333333333333333333333333333333333333 +9634 3333333333333333333333333333333333333333333333333333333 +9635 3333333333333333333333333333333333333333333333333333333 +9636 3333333333333333333333333333333333333333333333333333333 +9637 3333333333333333333333333333333333333333333333333333333 +9638 3333333333333333333333333333333333333333333333333333333 +9639 3333333333333333333333333333333333333333333333333333333 +9640 3333333333333333333333333333333333333333333333333333333 +9641 3333333333333333333333333333333333333333333333333333333 +9642 3333333333333333333333333333333333333333333333333333333 +9643 3333333333333333333333333333333333333333333333333333333 +9644 3333333333333333333333333333333333333333333333333333333 +9645 3333333333333333333333333333333333333333333333333333333 +9646 3333333333333333333333333333333333333333333333333333333 +9647 3333333333333333333333333333333333333333333333333333333 +9648 3333333333333333333333333333333333333333333333333333333 +9649 3333333333333333333333333333333333333333333333333333333 +9650 3333333333333333333333333333333333333333333333333333333 +9651 3333333333333333333333333333333333333333333333333333333 +9652 3333333333333333333333333333333333333333333333333333333 +9653 3333333333333333333333333333333333333333333333333333333 +9654 3333333333333333333333333333333333333333333333333333333 +9655 3333333333333333333333333333333333333333333333333333333 +9656 3333333333333333333333333333333333333333333333333333333 +9657 3333333333333333333333333333333333333333333333333333333 +9658 3333333333333333333333333333333333333333333333333333333 +9659 3333333333333333333333333333333333333333333333333333333 +9660 3333333333333333333333333333333333333333333333333333333 +9661 3333333333333333333333333333333333333333333333333333333 +9662 3333333333333333333333333333333333333333333333333333333 +9663 3333333333333333333333333333333333333333333333333333333 +9664 3333333333333333333333333333333333333333333333333333333 +9665 3333333333333333333333333333333333333333333333333333333 +9666 3333333333333333333333333333333333333333333333333333333 +9667 3333333333333333333333333333333333333333333333333333333 +9668 3333333333333333333333333333333333333333333333333333333 +9669 3333333333333333333333333333333333333333333333333333333 +9670 3333333333333333333333333333333333333333333333333333333 +9671 3333333333333333333333333333333333333333333333333333333 +9672 3333333333333333333333333333333333333333333333333333333 +9673 3333333333333333333333333333333333333333333333333333333 +9674 3333333333333333333333333333333333333333333333333333333 +9675 3333333333333333333333333333333333333333333333333333333 +9676 3333333333333333333333333333333333333333333333333333333 +9677 3333333333333333333333333333333333333333333333333333333 +9678 3333333333333333333333333333333333333333333333333333333 +9679 3333333333333333333333333333333333333333333333333333333 +9680 3333333333333333333333333333333333333333333333333333333 +9681 3333333333333333333333333333333333333333333333333333333 +9682 3333333333333333333333333333333333333333333333333333333 +9683 3333333333333333333333333333333333333333333333333333333 +9684 3333333333333333333333333333333333333333333333333333333 +9685 3333333333333333333333333333333333333333333333333333333 +9686 3333333333333333333333333333333333333333333333333333333 +9687 3333333333333333333333333333333333333333333333333333333 +9688 3333333333333333333333333333333333333333333333333333333 +9689 3333333333333333333333333333333333333333333333333333333 +9690 3333333333333333333333333333333333333333333333333333333 +9691 3333333333333333333333333333333333333333333333333333333 +9692 3333333333333333333333333333333333333333333333333333333 +9693 3333333333333333333333333333333333333333333333333333333 +9694 3333333333333333333333333333333333333333333333333333333 +9695 3333333333333333333333333333333333333333333333333333333 +9696 3333333333333333333333333333333333333333333333333333333 +9697 3333333333333333333333333333333333333333333333333333333 +9698 3333333333333333333333333333333333333333333333333333333 +9699 3333333333333333333333333333333333333333333333333333333 +9700 3333333333333333333333333333333333333333333333333333333 +9701 3333333333333333333333333333333333333333333333333333333 +9702 3333333333333333333333333333333333333333333333333333333 +9703 3333333333333333333333333333333333333333333333333333333 +9704 3333333333333333333333333333333333333333333333333333333 +9705 3333333333333333333333333333333333333333333333333333333 +9706 3333333333333333333333333333333333333333333333333333333 +9707 3333333333333333333333333333333333333333333333333333333 +9708 3333333333333333333333333333333333333333333333333333333 +9709 3333333333333333333333333333333333333333333333333333333 +9710 3333333333333333333333333333333333333333333333333333333 +9711 3333333333333333333333333333333333333333333333333333333 +9712 3333333333333333333333333333333333333333333333333333333 +9713 3333333333333333333333333333333333333333333333333333333 +9714 3333333333333333333333333333333333333333333333333333333 +9715 3333333333333333333333333333333333333333333333333333333 +9716 3333333333333333333333333333333333333333333333333333333 +9717 3333333333333333333333333333333333333333333333333333333 +9718 3333333333333333333333333333333333333333333333333333333 +9719 3333333333333333333333333333333333333333333333333333333 +9720 3333333333333333333333333333333333333333333333333333333 +9721 3333333333333333333333333333333333333333333333333333333 +9722 3333333333333333333333333333333333333333333333333333333 +9723 3333333333333333333333333333333333333333333333333333333 +9724 3333333333333333333333333333333333333333333333333333333 +9725 3333333333333333333333333333333333333333333333333333333 +9726 3333333333333333333333333333333333333333333333333333333 +9727 3333333333333333333333333333333333333333333333333333333 +9728 3333333333333333333333333333333333333333333333333333333 +9729 3333333333333333333333333333333333333333333333333333333 +9730 3333333333333333333333333333333333333333333333333333333 +9731 3333333333333333333333333333333333333333333333333333333 +9732 3333333333333333333333333333333333333333333333333333333 +9733 3333333333333333333333333333333333333333333333333333333 +9734 3333333333333333333333333333333333333333333333333333333 +9735 3333333333333333333333333333333333333333333333333333333 +9736 3333333333333333333333333333333333333333333333333333333 +9737 3333333333333333333333333333333333333333333333333333333 +9738 3333333333333333333333333333333333333333333333333333333 +9739 3333333333333333333333333333333333333333333333333333333 +9740 3333333333333333333333333333333333333333333333333333333 +9741 3333333333333333333333333333333333333333333333333333333 +9742 3333333333333333333333333333333333333333333333333333333 +9743 3333333333333333333333333333333333333333333333333333333 +9744 3333333333333333333333333333333333333333333333333333333 +9745 3333333333333333333333333333333333333333333333333333333 +9746 3333333333333333333333333333333333333333333333333333333 +9747 3333333333333333333333333333333333333333333333333333333 +9748 3333333333333333333333333333333333333333333333333333333 +9749 3333333333333333333333333333333333333333333333333333333 +9750 3333333333333333333333333333333333333333333333333333333 +9751 3333333333333333333333333333333333333333333333333333333 +9752 3333333333333333333333333333333333333333333333333333333 +9753 3333333333333333333333333333333333333333333333333333333 +9754 3333333333333333333333333333333333333333333333333333333 +9755 3333333333333333333333333333333333333333333333333333333 +9756 3333333333333333333333333333333333333333333333333333333 +9757 3333333333333333333333333333333333333333333333333333333 +9758 3333333333333333333333333333333333333333333333333333333 +9759 3333333333333333333333333333333333333333333333333333333 +9760 3333333333333333333333333333333333333333333333333333333 +9761 3333333333333333333333333333333333333333333333333333333 +9762 3333333333333333333333333333333333333333333333333333333 +9763 3333333333333333333333333333333333333333333333333333333 +9764 3333333333333333333333333333333333333333333333333333333 +9765 3333333333333333333333333333333333333333333333333333333 +9766 3333333333333333333333333333333333333333333333333333333 +9767 3333333333333333333333333333333333333333333333333333333 +9768 3333333333333333333333333333333333333333333333333333333 +9769 3333333333333333333333333333333333333333333333333333333 +9770 3333333333333333333333333333333333333333333333333333333 +9771 3333333333333333333333333333333333333333333333333333333 +9772 3333333333333333333333333333333333333333333333333333333 +9773 3333333333333333333333333333333333333333333333333333333 +9774 3333333333333333333333333333333333333333333333333333333 +9775 3333333333333333333333333333333333333333333333333333333 +9776 3333333333333333333333333333333333333333333333333333333 +9777 3333333333333333333333333333333333333333333333333333333 +9778 3333333333333333333333333333333333333333333333333333333 +9779 3333333333333333333333333333333333333333333333333333333 +9780 3333333333333333333333333333333333333333333333333333333 +9781 3333333333333333333333333333333333333333333333333333333 +9782 3333333333333333333333333333333333333333333333333333333 +9783 3333333333333333333333333333333333333333333333333333333 +9784 3333333333333333333333333333333333333333333333333333333 +9785 3333333333333333333333333333333333333333333333333333333 +9786 3333333333333333333333333333333333333333333333333333333 +9787 3333333333333333333333333333333333333333333333333333333 +9788 3333333333333333333333333333333333333333333333333333333 +9789 3333333333333333333333333333333333333333333333333333333 +9790 3333333333333333333333333333333333333333333333333333333 +9791 3333333333333333333333333333333333333333333333333333333 +9792 3333333333333333333333333333333333333333333333333333333 +9793 3333333333333333333333333333333333333333333333333333333 +9794 3333333333333333333333333333333333333333333333333333333 +9795 3333333333333333333333333333333333333333333333333333333 +9796 3333333333333333333333333333333333333333333333333333333 +9797 3333333333333333333333333333333333333333333333333333333 +9798 3333333333333333333333333333333333333333333333333333333 +9799 3333333333333333333333333333333333333333333333333333333 +9800 3333333333333333333333333333333333333333333333333333333 +9801 3333333333333333333333333333333333333333333333333333333 +9802 3333333333333333333333333333333333333333333333333333333 +9803 3333333333333333333333333333333333333333333333333333333 +9804 3333333333333333333333333333333333333333333333333333333 +9805 3333333333333333333333333333333333333333333333333333333 +9806 3333333333333333333333333333333333333333333333333333333 +9807 3333333333333333333333333333333333333333333333333333333 +9808 3333333333333333333333333333333333333333333333333333333 +9809 3333333333333333333333333333333333333333333333333333333 +9810 3333333333333333333333333333333333333333333333333333333 +9811 3333333333333333333333333333333333333333333333333333333 +9812 3333333333333333333333333333333333333333333333333333333 +9813 3333333333333333333333333333333333333333333333333333333 +9814 3333333333333333333333333333333333333333333333333333333 +9815 3333333333333333333333333333333333333333333333333333333 +9816 3333333333333333333333333333333333333333333333333333333 +9817 3333333333333333333333333333333333333333333333333333333 +9818 3333333333333333333333333333333333333333333333333333333 +9819 3333333333333333333333333333333333333333333333333333333 +9820 3333333333333333333333333333333333333333333333333333333 +9821 3333333333333333333333333333333333333333333333333333333 +9822 3333333333333333333333333333333333333333333333333333333 +9823 3333333333333333333333333333333333333333333333333333333 +9824 3333333333333333333333333333333333333333333333333333333 +9825 3333333333333333333333333333333333333333333333333333333 +9826 3333333333333333333333333333333333333333333333333333333 +9827 3333333333333333333333333333333333333333333333333333333 +9828 3333333333333333333333333333333333333333333333333333333 +9829 3333333333333333333333333333333333333333333333333333333 +9830 3333333333333333333333333333333333333333333333333333333 +9831 3333333333333333333333333333333333333333333333333333333 +9832 3333333333333333333333333333333333333333333333333333333 +9833 3333333333333333333333333333333333333333333333333333333 +9834 3333333333333333333333333333333333333333333333333333333 +9835 3333333333333333333333333333333333333333333333333333333 +9836 3333333333333333333333333333333333333333333333333333333 +9837 3333333333333333333333333333333333333333333333333333333 +9838 3333333333333333333333333333333333333333333333333333333 +9839 3333333333333333333333333333333333333333333333333333333 +9840 3333333333333333333333333333333333333333333333333333333 +9841 3333333333333333333333333333333333333333333333333333333 +9842 3333333333333333333333333333333333333333333333333333333 +9843 3333333333333333333333333333333333333333333333333333333 +9844 3333333333333333333333333333333333333333333333333333333 +9845 3333333333333333333333333333333333333333333333333333333 +9846 3333333333333333333333333333333333333333333333333333333 +9847 3333333333333333333333333333333333333333333333333333333 +9848 3333333333333333333333333333333333333333333333333333333 +9849 3333333333333333333333333333333333333333333333333333333 +9850 3333333333333333333333333333333333333333333333333333333 +9851 3333333333333333333333333333333333333333333333333333333 +9852 3333333333333333333333333333333333333333333333333333333 +9853 3333333333333333333333333333333333333333333333333333333 +9854 3333333333333333333333333333333333333333333333333333333 +9855 3333333333333333333333333333333333333333333333333333333 +9856 3333333333333333333333333333333333333333333333333333333 +9857 3333333333333333333333333333333333333333333333333333333 +9858 3333333333333333333333333333333333333333333333333333333 +9859 3333333333333333333333333333333333333333333333333333333 +9860 3333333333333333333333333333333333333333333333333333333 +9861 3333333333333333333333333333333333333333333333333333333 +9862 3333333333333333333333333333333333333333333333333333333 +9863 3333333333333333333333333333333333333333333333333333333 +9864 3333333333333333333333333333333333333333333333333333333 +9865 3333333333333333333333333333333333333333333333333333333 +9866 3333333333333333333333333333333333333333333333333333333 +9867 3333333333333333333333333333333333333333333333333333333 +9868 3333333333333333333333333333333333333333333333333333333 +9869 3333333333333333333333333333333333333333333333333333333 +9870 3333333333333333333333333333333333333333333333333333333 +9871 3333333333333333333333333333333333333333333333333333333 +9872 3333333333333333333333333333333333333333333333333333333 +9873 3333333333333333333333333333333333333333333333333333333 +9874 3333333333333333333333333333333333333333333333333333333 +9875 3333333333333333333333333333333333333333333333333333333 +9876 3333333333333333333333333333333333333333333333333333333 +9877 3333333333333333333333333333333333333333333333333333333 +9878 3333333333333333333333333333333333333333333333333333333 +9879 3333333333333333333333333333333333333333333333333333333 +9880 3333333333333333333333333333333333333333333333333333333 +9881 3333333333333333333333333333333333333333333333333333333 +9882 3333333333333333333333333333333333333333333333333333333 +9883 3333333333333333333333333333333333333333333333333333333 +9884 3333333333333333333333333333333333333333333333333333333 +9885 3333333333333333333333333333333333333333333333333333333 +9886 3333333333333333333333333333333333333333333333333333333 +9887 3333333333333333333333333333333333333333333333333333333 +9888 3333333333333333333333333333333333333333333333333333333 +9889 3333333333333333333333333333333333333333333333333333333 +9890 3333333333333333333333333333333333333333333333333333333 +9891 3333333333333333333333333333333333333333333333333333333 +9892 3333333333333333333333333333333333333333333333333333333 +9893 3333333333333333333333333333333333333333333333333333333 +9894 3333333333333333333333333333333333333333333333333333333 +9895 3333333333333333333333333333333333333333333333333333333 +9896 3333333333333333333333333333333333333333333333333333333 +9897 3333333333333333333333333333333333333333333333333333333 +9898 3333333333333333333333333333333333333333333333333333333 +9899 3333333333333333333333333333333333333333333333333333333 +9900 3333333333333333333333333333333333333333333333333333333 +9901 3333333333333333333333333333333333333333333333333333333 +9902 3333333333333333333333333333333333333333333333333333333 +9903 3333333333333333333333333333333333333333333333333333333 +9904 3333333333333333333333333333333333333333333333333333333 +9905 3333333333333333333333333333333333333333333333333333333 +9906 3333333333333333333333333333333333333333333333333333333 +9907 3333333333333333333333333333333333333333333333333333333 +9908 3333333333333333333333333333333333333333333333333333333 +9909 3333333333333333333333333333333333333333333333333333333 +9910 3333333333333333333333333333333333333333333333333333333 +9911 3333333333333333333333333333333333333333333333333333333 +9912 3333333333333333333333333333333333333333333333333333333 +9913 3333333333333333333333333333333333333333333333333333333 +9914 3333333333333333333333333333333333333333333333333333333 +9915 3333333333333333333333333333333333333333333333333333333 +9916 3333333333333333333333333333333333333333333333333333333 +9917 3333333333333333333333333333333333333333333333333333333 +9918 3333333333333333333333333333333333333333333333333333333 +9919 3333333333333333333333333333333333333333333333333333333 +9920 3333333333333333333333333333333333333333333333333333333 +9921 3333333333333333333333333333333333333333333333333333333 +9922 3333333333333333333333333333333333333333333333333333333 +9923 3333333333333333333333333333333333333333333333333333333 +9924 3333333333333333333333333333333333333333333333333333333 +9925 3333333333333333333333333333333333333333333333333333333 +9926 3333333333333333333333333333333333333333333333333333333 +9927 3333333333333333333333333333333333333333333333333333333 +9928 3333333333333333333333333333333333333333333333333333333 +9929 3333333333333333333333333333333333333333333333333333333 +9930 3333333333333333333333333333333333333333333333333333333 +9931 3333333333333333333333333333333333333333333333333333333 +9932 3333333333333333333333333333333333333333333333333333333 +9933 3333333333333333333333333333333333333333333333333333333 +9934 3333333333333333333333333333333333333333333333333333333 +9935 3333333333333333333333333333333333333333333333333333333 +9936 3333333333333333333333333333333333333333333333333333333 +9937 3333333333333333333333333333333333333333333333333333333 +9938 3333333333333333333333333333333333333333333333333333333 +9939 3333333333333333333333333333333333333333333333333333333 +9940 3333333333333333333333333333333333333333333333333333333 +9941 3333333333333333333333333333333333333333333333333333333 +9942 3333333333333333333333333333333333333333333333333333333 +9943 3333333333333333333333333333333333333333333333333333333 +9944 3333333333333333333333333333333333333333333333333333333 +9945 3333333333333333333333333333333333333333333333333333333 +9946 3333333333333333333333333333333333333333333333333333333 +9947 3333333333333333333333333333333333333333333333333333333 +9948 3333333333333333333333333333333333333333333333333333333 +9949 3333333333333333333333333333333333333333333333333333333 +9950 3333333333333333333333333333333333333333333333333333333 +9951 3333333333333333333333333333333333333333333333333333333 +9952 3333333333333333333333333333333333333333333333333333333 +9953 3333333333333333333333333333333333333333333333333333333 +9954 3333333333333333333333333333333333333333333333333333333 +9955 3333333333333333333333333333333333333333333333333333333 +9956 3333333333333333333333333333333333333333333333333333333 +9957 3333333333333333333333333333333333333333333333333333333 +9958 3333333333333333333333333333333333333333333333333333333 +9959 3333333333333333333333333333333333333333333333333333333 +9960 3333333333333333333333333333333333333333333333333333333 +9961 3333333333333333333333333333333333333333333333333333333 +9962 3333333333333333333333333333333333333333333333333333333 +9963 3333333333333333333333333333333333333333333333333333333 +9964 3333333333333333333333333333333333333333333333333333333 +9965 3333333333333333333333333333333333333333333333333333333 +9966 3333333333333333333333333333333333333333333333333333333 +9967 3333333333333333333333333333333333333333333333333333333 +9968 3333333333333333333333333333333333333333333333333333333 +9969 3333333333333333333333333333333333333333333333333333333 +9970 3333333333333333333333333333333333333333333333333333333 +9971 3333333333333333333333333333333333333333333333333333333 +9972 3333333333333333333333333333333333333333333333333333333 +9973 3333333333333333333333333333333333333333333333333333333 +9974 3333333333333333333333333333333333333333333333333333333 +9975 3333333333333333333333333333333333333333333333333333333 +9976 3333333333333333333333333333333333333333333333333333333 +9977 3333333333333333333333333333333333333333333333333333333 +9978 3333333333333333333333333333333333333333333333333333333 +9979 3333333333333333333333333333333333333333333333333333333 +9980 3333333333333333333333333333333333333333333333333333333 +9981 3333333333333333333333333333333333333333333333333333333 +9982 3333333333333333333333333333333333333333333333333333333 +9983 3333333333333333333333333333333333333333333333333333333 +9984 3333333333333333333333333333333333333333333333333333333 +9985 3333333333333333333333333333333333333333333333333333333 +9986 3333333333333333333333333333333333333333333333333333333 +9987 3333333333333333333333333333333333333333333333333333333 +9988 3333333333333333333333333333333333333333333333333333333 +9989 3333333333333333333333333333333333333333333333333333333 +9990 3333333333333333333333333333333333333333333333333333333 +9991 3333333333333333333333333333333333333333333333333333333 +9992 3333333333333333333333333333333333333333333333333333333 +9993 3333333333333333333333333333333333333333333333333333333 +9994 3333333333333333333333333333333333333333333333333333333 +9995 3333333333333333333333333333333333333333333333333333333 +9996 3333333333333333333333333333333333333333333333333333333 +9997 3333333333333333333333333333333333333333333333333333333 +9998 3333333333333333333333333333333333333333333333333333333 +9999 3333333333333333333333333333333333333333333333333333333 diff --git a/jetty-test-webapp/src/main/webapp/data.txt.gz b/jetty-test-webapp/src/main/webapp/data.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..42b9da0307cc858b2c2556a3c62f4fcbc7d0a9e3 GIT binary patch literal 25691 zcmeHPK}Zx~7!{1HRWNJR1Z9auY%y7vpc08CJ!m>r+X*Z|VMe<&$gtsdll=(=6z8G&xl-pYo?V#2(LcK_5>($@ zCm-~u4vXEMAwhRXPX)b0td}2TJX*vy-iaFCpzTv5)8;iLU8aR33vW1MK2p->%K|-y1MzSEGL7kHsL z-l0V#3vQT@ta=p?iq%N^m6=#vkT1^96)(>wDwl3Y0;y)_v_6=AF}3h(NSyomJ^Au6 zw^?;=Pc}B%3wz{CM&zu&gm;=d#Xf89gr`t*W`&(fdT=61{#Ks+YL%xMk@l^pyoj`K zJ>@M8+S@cp=nmSP1_^WU#39YN*EE+QG=L2kbo%x zW^+Mef5i+y7%|>>iI(@b`jFk6{7OuiWtUmVT`UatoduK>~(rb|QG z!gOg!TbM2lX$#Y(A#Gv0G@K2b%>hHMFAiu6Q!l(&jYo*T>)O9L3E&z-+!%SvC$G3f zu7S@7QgIMIA4rKIsva1H)dOR&df+Ik9!Nw`R6Q`t@_{SF5K<3}83841lg!_L1A;C? z%>PheHr3tB4P=?5D*_qZFsDEUH}t}af^pDW5kKs$NGYZ_ z-DlY>B$)I5g@puHZlqaA&^5J0OC#n3X($piA4o%yEF|b2P6OrxsR%k^ zyk{Z7oHx-dB)D3h!$N{?c@7H+;wghHB#74{!$N|rKn5`>Sgbm%Kqlu+G%Ju;|m6f2Z=%e0AD&JITS(6reK3Nz+0K7$}E$@lZJ1QNB+PF^2Rq1 z-~0si(Ki_^w7s|RV6t5MRE#FRd7IU<_sLm%SZ5ziJI7yDmBxRswtPX;YBWs?5!~WV zX`jnKzth8Su;vVy6F!(zey)BLp7>CB>W4KOr0qw3w28if>`f;Q+U+p8Z%!Bx?#XHx z){FsbAqZ=}v|o5k{<>)7Pyike?g%;$(&VSpzKYFT`wR zDrX^M;CRr3qk($tMJ~gfG?=p)=1f4>OZ>eMmFKZ z{#6_645KLgIrf(Ep}0){MOd9rM%JLm7=wuWGDy2FVWh5HVfe~LjL`pd&kbuajQk^N z@{D>BMfSbkW<%5whu%d#k%;ZURdyb!Ya7ZI-%;ttJJuxJLz0cWW_*%td~zN%2=2D-ky` z(BtG2iSC_JOXHW6$^Qk_6M1L8#NWE%;LLH} zWIj0`Qp53(2ibm?;-@;O`5*Ztr|<+(CpGA+Q$34j#g!J$e~9zt#$j(AUOLK=Y~>vE zu?9lyH@sWXP4aHWYq5K$p7{f?rUNjigPh|g$c%mOb6~yGu6q2P&lSgd%&yLxlcl$% z^3Jn=&e49e4f|0W4m)dTSK~8hVtc3o?+0Ai>UUzBcl=?*!x|+Q-zjIsoMPQ_f7DOj qU^eMe?`tnuMcawfS5dwZz6;KEaVb>dF}Ls}x8hTO;ZooK`hEgZQ79q+ literal 0 HcmV?d00001 diff --git a/jetty-test-webapp/src/main/webapp/index.html b/jetty-test-webapp/src/main/webapp/index.html new file mode 100644 index 00000000000..e39e7cc36fb --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/index.html @@ -0,0 +1,53 @@ + + + Powered By Jetty + + + + + +

    Welcome to Jetty 7

    +

    +This is the Test webapp for the Jetty 7 HTTP Server and Servlet Container. +For more information about Jetty, please visit our +website +or wiki. +Commercial support for Jetty is available via webtide. +

    +

    +This is a test context that serves: +

    +

    + +

    +Other demonstration contexts, some of which may need manual deployment +(check the README.txt file for details): +

    +

    + +This webapp is deployed in $JETTY_HOME/webapp/test and configured by $JETTY_HOME/contexts/test.xml + + + diff --git a/jetty-test-webapp/src/main/webapp/jetty_banner.gif b/jetty-test-webapp/src/main/webapp/jetty_banner.gif new file mode 100644 index 0000000000000000000000000000000000000000..2304a4cfb350c0e77c0a8243b3224466566dcbf7 GIT binary patch literal 65384 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 literal 0 HcmV?d00001 diff --git a/jetty-test-webapp/src/main/webapp/jsp/bean1.jsp b/jetty-test-webapp/src/main/webapp/jsp/bean1.jsp new file mode 100644 index 00000000000..0c15da2ca4e --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/jsp/bean1.jsp @@ -0,0 +1,15 @@ + +<%@ page session="true"%> + + + +

    JSP1.2 Beans: 1

    + +Counter accessed times.
    +Counter last accessed by
    + + +Goto bean2.jsp + + + diff --git a/jetty-test-webapp/src/main/webapp/jsp/bean2.jsp b/jetty-test-webapp/src/main/webapp/jsp/bean2.jsp new file mode 100644 index 00000000000..624dc2e59d4 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/jsp/bean2.jsp @@ -0,0 +1,15 @@ + +<%@ page session="true"%> + + + +

    JSP1.2 Beans: 2

    + +Counter accessed times.
    +Counter last accessed by
    + + +Goto bean1.jsp + + + diff --git a/jetty-test-webapp/src/main/webapp/jsp/dump.jsp b/jetty-test-webapp/src/main/webapp/jsp/dump.jsp new file mode 100644 index 00000000000..fb73b0b0002 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/jsp/dump.jsp @@ -0,0 +1,23 @@ + +<%@ page import="java.util.Enumeration" %> + +

    JSP Dump

    + + + + + + +<% + Enumeration e =request.getParameterNames(); + while(e.hasMoreElements()) + { + String name = (String)e.nextElement(); +%> + + + +<% } %> + +
    Request URI:<%= request.getRequestURI() %>
    ServletPath:<%= request.getServletPath() %>
    PathInfo:<%= request.getPathInfo() %>
    getParameter("<%= name %>")<%= request.getParameter(name) %>
    + diff --git a/jetty-test-webapp/src/main/webapp/jsp/expr.jsp b/jetty-test-webapp/src/main/webapp/jsp/expr.jsp new file mode 100644 index 00000000000..e0b25e20203 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/jsp/expr.jsp @@ -0,0 +1,23 @@ + +

    JSP2.0 Expressions

    + + + + + + + + + + + + + + + + + + + +
    ExpressionResult
    \${param["A"]}${param["A"]} 
    \${header["host"]}${header["host"]}
    \${header["user-agent"]}${header["user-agent"]}
    \${1+1}${1+1}
    \${param["A"] * 2}${param["A"] * 2} 
    + diff --git a/jetty-test-webapp/src/main/webapp/jsp/index.html b/jetty-test-webapp/src/main/webapp/jsp/index.html new file mode 100644 index 00000000000..3b3af3c00df --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/jsp/index.html @@ -0,0 +1,15 @@ + + + +

    Day2

    + +JSP 1.2 embedded java
    +JSP 1.2 Bean demo
    +JSP 1.2 BodyTag demo
    +JSP 2.0 SimpleTag demo
    +JSP 2.0 Tag File demo
    +JSP 2.0 Tag Expression
    + + + + diff --git a/jetty-test-webapp/src/main/webapp/jsp/tag.jsp b/jetty-test-webapp/src/main/webapp/jsp/tag.jsp new file mode 100644 index 00000000000..069d8c67b17 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/jsp/tag.jsp @@ -0,0 +1,16 @@ + + + +<%@ taglib uri="http://www.acme.com/taglib" prefix="acme" %> + +<acme:date tz="GMT">EEE, dd/MMM/yyyy HH:mm:ss ZZZ</acme:date> +==> +EEE, dd/MMM/yyyy HH:mm:ss ZZZ +
    +<acme:date tz="EST">EEE, dd-MMM-yyyy HH:mm:ss ZZZ</acme:date> +==> +EEE, dd-MMM-yyyy HH:mm:ss ZZZ +
    + + + diff --git a/jetty-test-webapp/src/main/webapp/jsp/tag2.jsp b/jetty-test-webapp/src/main/webapp/jsp/tag2.jsp new file mode 100644 index 00000000000..8071927562a --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/jsp/tag2.jsp @@ -0,0 +1,19 @@ + + + +<%@ taglib uri="http://www.acme.com/taglib2" prefix="acme" %> + + + On ${day} of ${month} in the year ${year} + + +
    + + + ${day} - ${month} - ${year} + + +
    + + + diff --git a/jetty-test-webapp/src/main/webapp/jsp/tagfile.jsp b/jetty-test-webapp/src/main/webapp/jsp/tagfile.jsp new file mode 100644 index 00000000000..67299f0229c --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/jsp/tagfile.jsp @@ -0,0 +1,37 @@ +<%@ taglib prefix="acme" tagdir="/WEB-INF/tags" %> + + + + +

    JSP 2.0 Tag File Example

    +
    +

    Panel tag created from JSP fragment file in WEB-INF/tags +


    + + + + + + +
    + + First panel.
    +
    +
    + + Second panel.
    + Second panel.
    + Second panel.
    + Second panel.
    +
    +
    + + Third panel.
    + + A panel in a panel. + + Third panel.
    +
    +
    + + diff --git a/jetty-test-webapp/src/main/webapp/logon.html b/jetty-test-webapp/src/main/webapp/logon.html new file mode 100644 index 00000000000..2cfa699c807 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/logon.html @@ -0,0 +1,20 @@ + +

    FORM Authentication demo

    +
    + + + + + + + + + + + + +
    Username:
    Password:
    + +
    +
    + \ No newline at end of file diff --git a/jetty-test-webapp/src/main/webapp/logonError.html b/jetty-test-webapp/src/main/webapp/logonError.html new file mode 100644 index 00000000000..1b1228bd69d --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/logonError.html @@ -0,0 +1,5 @@ + +

    Authentication ERROR

    +Username, password or role incorrect. +                                                         + \ No newline at end of file diff --git a/jetty-test-webapp/src/main/webapp/rewrite.html b/jetty-test-webapp/src/main/webapp/rewrite.html new file mode 100644 index 00000000000..0d0d665eea3 --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/rewrite.html @@ -0,0 +1,14 @@ + +

    Links to test the RewriteHandler

    +To run these tests, you should start Jetty with:
    +java -jar start.jar etc/jetty.xml etc/jetty-rewrite.xml
    +
    + + diff --git a/jetty-test-webapp/src/main/webapp/snoop.jsp b/jetty-test-webapp/src/main/webapp/snoop.jsp new file mode 100644 index 00000000000..bc51540b9ce --- /dev/null +++ b/jetty-test-webapp/src/main/webapp/snoop.jsp @@ -0,0 +1,201 @@ + + + JSP snoop page + <%@ page import="javax.servlet.http.HttpUtils,java.util.Enumeration" %> + + + +

    WebApp JSP Snoop page

    + +

    Request information

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Requested URL:<%= HttpUtils.getRequestURL(request) %>
    Request method:<%= request.getMethod() %>
    Request URI:<%= request.getRequestURI() %>
    Request protocol:<%= request.getProtocol() %>
    Servlet path:<%= request.getServletPath() %>
    Path info:<%= request.getPathInfo() %>
    Path translated:<%= request.getPathTranslated() %>
    Query string:<% if(request.getQueryString()!=null) out.write(request.getQueryString().replaceAll("<", "<").replaceAll(">",">")); %>
    Content length:<%= request.getContentLength() %>
    Content type:<%= request.getContentType() %>
    Server name:<%= request.getServerName() %>
    Server port:<%= request.getServerPort() %>
    Remote user:<%= request.getRemoteUser() %>
    Remote address:<%= request.getRemoteAddr() %>
    Remote host:<%= request.getRemoteHost() %>
    Authorization scheme:<%= request.getAuthType() %>
    + +<% + Enumeration e = request.getHeaderNames(); + if(e != null && e.hasMoreElements()) { +%> +

    Request headers

    + + + + + + +<% + while(e.hasMoreElements()) { + String k = (String) e.nextElement(); +%> + + + + +<% + } +%> +
    Header:Value:
    <%= k %><%= request.getHeader(k) %>
    +<% + } +%> + + +<% + e = request.getParameterNames(); + if(e != null && e.hasMoreElements()) { +%> +

    Request parameters

    + + + + + + +<% + while(e.hasMoreElements()) { + String k = (String) e.nextElement(); + String val = request.getParameter(k); + String vals[] = request.getParameterValues(k); +%> + + + + + +<% + } +%> +
    Parameter:Value:Multiple values:
    <%= k.replaceAll("<", "<").replaceAll(">",">") %><%= val.replaceAll("<", "<").replaceAll(">",">") %><% + for(int i = 0; i < vals.length; i++) { + if(i > 0) + out.print("
    "); + out.print(vals[i].replaceAll("<", "<").replaceAll(">",">")); + } + %>
    +<% + } +%> + + +<% + e = request.getAttributeNames(); + if(e != null && e.hasMoreElements()) { +%> +

    Request Attributes

    + + + + + +<% + while(e.hasMoreElements()) { + String k = (String) e.nextElement(); + Object val = request.getAttribute(k); +%> + + + + +<% + } +%> +
    Attribute:Value:
    <%= k.replaceAll("<", "<").replaceAll(">",">") %><%= val.toString().replaceAll("<", "<").replaceAll(">",">") %>
    +<% + } +%> + + +<% + e = getServletConfig().getInitParameterNames(); + if(e != null && e.hasMoreElements()) { +%> +

    Init parameters

    + + + + + +<% + while(e.hasMoreElements()) { + String k = (String) e.nextElement(); + String val = getServletConfig().getInitParameter(k); +%> + + + + +<% + } +%> +
    Parameter:Value:
    <%= k %><%= val %>
    +<% + } +%> + + + + diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml new file mode 100644 index 00000000000..392fa043122 --- /dev/null +++ b/jetty-util/pom.xml @@ -0,0 +1,84 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-util + Jetty :: Utilities + Utility classes for Jetty + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + J2SE-1.5 + org.eclipse.jetty.util + org.slf4j;resolution:=optional,* + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + config.xml + + + + + + + + + + junit + junit + test + + + org.mortbay.jetty + servlet-api + provided + + + org.slf4j + slf4j-api + provided + true + + + + diff --git a/jetty-util/src/main/assembly/config.xml b/jetty-util/src/main/assembly/config.xml new file mode 100644 index 00000000000..b0592ae41b1 --- /dev/null +++ b/jetty-util/src/main/assembly/config.xml @@ -0,0 +1,20 @@ + + + config + false + + jar + + + + + src/main/config + + + ** + + + + + + diff --git a/jetty-util/src/main/config/etc/jetty-logging.xml b/jetty-util/src/main/config/etc/jetty-logging.xml new file mode 100644 index 00000000000..f264bba73bb --- /dev/null +++ b/jetty-util/src/main/config/etc/jetty-logging.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + /logs/yyyy_mm_dd.stderrout.log + false + 90 + GMT + + + + + + Redirecting stderr/stdout to + + + + + + + diff --git a/jetty-util/src/main/java/META-INF/MANIFEST.MF b/jetty-util/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..5e9495128c0 --- /dev/null +++ b/jetty-util/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java new file mode 100644 index 00000000000..130e1ffb8dd --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java @@ -0,0 +1,352 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.util.AbstractList; +import java.util.NoSuchElementException; +import java.util.Queue; + +/* ------------------------------------------------------------ */ +/** Queue backed by circular array. + * + * This partial Queue implementation (also with {@link #pop()} for stack operation) + * is backed by a growable circular array. + * + * + * + * @param + */ +public class ArrayQueue extends AbstractList implements Queue +{ + public final int DEFAULT_CAPACITY=64; + public final int DEFAULT_GROWTH=32; + protected Object _lock=this; + protected Object[] _elements; + protected int _nextE; + protected int _nextSlot; + protected int _size; + protected int _growCapacity; + + /* ------------------------------------------------------------ */ + public ArrayQueue() + { + _elements=new Object[64]; + _growCapacity=32; + } + + /* ------------------------------------------------------------ */ + public ArrayQueue(int capacity) + { + _elements=new Object[capacity]; + _growCapacity=-1; + } + + /* ------------------------------------------------------------ */ + public ArrayQueue(int initCapacity,int growBy) + { + _elements=new Object[initCapacity]; + _growCapacity=growBy; + } + + /* ------------------------------------------------------------ */ + public ArrayQueue(int initCapacity,int growBy,Object lock) + { + _elements=new Object[initCapacity]; + _growCapacity=growBy; + _lock=lock; + } + + /* ------------------------------------------------------------ */ + public int getCapacity() + { + return _elements.length; + } + + /* ------------------------------------------------------------ */ + @Override + public boolean add(E e) + { + if (!offer(e)) + throw new IllegalStateException("Full"); + return true; + } + + /* ------------------------------------------------------------ */ + public boolean offer(E e) + { + synchronized(_lock) + { + if (_size==_elements.length && !grow()) + return false; + + _size++; + _elements[_nextSlot++]=e; + if (_nextSlot==_elements.length) + _nextSlot=0; + } + return true; + } + + /* ------------------------------------------------------------ */ + /** + * Add without synchronization or bounds checking + * @see #add(Object) + */ + public void addUnsafe(E e) + { + if (_size==_elements.length && !grow()) + throw new IllegalStateException("Full"); + + _size++; + _elements[_nextSlot++]=e; + if (_nextSlot==_elements.length) + _nextSlot=0; + } + + /* ------------------------------------------------------------ */ + public E element() + { + synchronized(_lock) + { + if (_size==0) + throw new NoSuchElementException(); + return (E)_elements[_nextE]; + } + } + + /* ------------------------------------------------------------ */ + public E peek() + { + synchronized(_lock) + { + if (_size==0) + return null; + return (E)_elements[_nextE]; + } + } + + /* ------------------------------------------------------------ */ + public E poll() + { + synchronized(_lock) + { + if (_size==0) + return null; + E e = (E)_elements[_nextE]; + _elements[_nextE]=null; + _size--; + if (++_nextE==_elements.length) + _nextE=0; + return e; + } + } + + /* ------------------------------------------------------------ */ + public E remove() + { + synchronized(_lock) + { + if (_size==0) + throw new NoSuchElementException(); + E e = (E)_elements[_nextE]; + _elements[_nextE]=null; + _size--; + if (++_nextE==_elements.length) + _nextE=0; + return e; + } + } + + /* ------------------------------------------------------------ */ + @Override + public void clear() + { + synchronized(_lock) + { + _size=0; + _nextE=0; + _nextSlot=0; + } + } + + /* ------------------------------------------------------------ */ + public boolean isEmpty() + { + synchronized(_lock) + { + return _size==0; + } + } + + + /* ------------------------------------------------------------ */ + @Override + public int size() + { + return _size; + } + + /* ------------------------------------------------------------ */ + @Override + public E get(int index) + { + synchronized(_lock) + { + if (index<0 || index>=_size) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + int i = (_nextE+index)%_elements.length; + return (E)_elements[i]; + } + } + + /* ------------------------------------------------------------ */ + /** + * Get without synchronization or bounds checking. + * @see get(int) + */ + public E getUnsafe(int index) + { + int i = (_nextE+index)%_elements.length; + return (E)_elements[i]; + } + + /* ------------------------------------------------------------ */ + public E remove(int index) + { + synchronized(_lock) + { + if (index<0 || index>=_size) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + int i = (_nextE+index)%_elements.length; + E old=(E)_elements[i]; + + if (i<_nextSlot) + { + // 0 _elements.length + // _nextE........._nextSlot + System.arraycopy(_elements,i+1,_elements,i,_nextSlot-i); + _nextSlot--; + _size--; + } + else + { + // 0 _elements.length + // ......_nextSlot _nextE.......... + System.arraycopy(_elements,i+1,_elements,i,_elements.length-i-1); + if (_nextSlot>0) + { + _elements[_elements.length-1]=_elements[0]; + System.arraycopy(_elements,1,_elements,0,_nextSlot-1); + _nextSlot--; + } + else + _nextSlot=_elements.length-1; + + _size--; + } + + return old; + } + } + + /* ------------------------------------------------------------ */ + public E set(int index, E element) + { + synchronized(_lock) + { + if (index<0 || index>=_size) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + int i = _nextE+index; + if (i>=_elements.length) + i-=_elements.length; + E old=(E)_elements[i]; + _elements[i]=element; + return old; + } + } + + /* ------------------------------------------------------------ */ + public void add(int index, E element) + { + synchronized(_lock) + { + if (index<0 || index>_size) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + if (_size==_elements.length && !grow()) + throw new IllegalStateException("Full"); + + if (index==_size) + { + add(element); + } + else + { + int i = _nextE+index; + if (i>=_elements.length) + i-=_elements.length; + + _size++; + _nextSlot++; + if (_nextSlot==_elements.length) + _nextSlot=0; + + if (i<_nextSlot) + { + // 0 _elements.length + // _nextE.....i..._nextSlot + // 0 _elements.length + // ..i..._nextSlot _nextE.......... + System.arraycopy(_elements,i,_elements,i+1,_nextSlot-i); + _elements[i]=element; + } + else + { + // 0 _elements.length + // ......_nextSlot _nextE.....i.... + if (_nextSlot>0) + { + System.arraycopy(_elements,0,_elements,1,_nextSlot); + _elements[0]=_elements[_elements.length-1]; + } + + System.arraycopy(_elements,i,_elements,i+1,_elements.length-i-1); + _elements[i]=element; + } + } + } + } + + protected boolean grow() + { + if (_growCapacity<=0) + return false; + + Object[] elements=new Object[_elements.length+_growCapacity]; + + int split=_elements.length-_nextE; + if (split>0) + System.arraycopy(_elements,_nextE,elements,0,split); + if (_nextE!=0) + System.arraycopy(_elements,0,elements,split,_nextSlot); + + _elements=elements; + _nextE=0; + _nextSlot=_size; + return true; + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java new file mode 100644 index 00000000000..d6d9f600719 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java @@ -0,0 +1,31 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.util.Enumeration; + +/* ------------------------------------------------------------ */ +/** Attributes. + * Interface commonly used for storing attributes. + * + * + */ +public interface Attributes +{ + public void removeAttribute(String name); + public void setAttribute(String name, Object attribute); + public Object getAttribute(String name); + public Enumeration getAttributeNames(); + public void clearAttributes(); +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java new file mode 100644 index 00000000000..219148444fc --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java @@ -0,0 +1,119 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/* ------------------------------------------------------------ */ +/** AttributesMap. + * + * + */ +public class AttributesMap implements Attributes +{ + Map _map; + + /* ------------------------------------------------------------ */ + public AttributesMap() + { + _map=new HashMap(); + } + + /* ------------------------------------------------------------ */ + public AttributesMap(Map map) + { + _map=map; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.Attributes#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + _map.remove(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.Attributes#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object attribute) + { + if (attribute==null) + _map.remove(name); + else + _map.put(name, attribute); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.Attributes#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _map.get(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.Attributes#getAttributeNames() + */ + public Enumeration getAttributeNames() + { + return Collections.enumeration(_map.keySet()); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.Attributes#getAttributeNames() + */ + public static Enumeration getAttributeNamesCopy(Attributes attrs) + { + if (attrs instanceof AttributesMap) + return Collections.enumeration(((AttributesMap)attrs)._map.keySet()); + ArrayList names = new ArrayList(); + Enumeration e = attrs.getAttributeNames(); + while (e.hasMoreElements()) + names.add(e.nextElement()); + return Collections.enumeration(names); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.Attributes#clear() + */ + public void clearAttributes() + { + _map.clear(); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return _map.toString(); + } + + /* ------------------------------------------------------------ */ + public Set keySet() + { + return _map.keySet(); + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java new file mode 100644 index 00000000000..83b3ada891b --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java @@ -0,0 +1,585 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.util.AbstractList; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/* ------------------------------------------------------------ */ +/** Queue backed by a circular array. + * + * This queue is uses a variant of the two lock queue algorithm to + * provide an efficient queue or list backed by a growable circular + * array. This queue also has a partial implementation of + * {@link java.util.concurrent.BlockingQueue}, specifically the {@link #take()} and + * {@link #poll(long, TimeUnit)} methods. + * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is + * able to grow and provides a blocking put call. + *

    + * The queue has both a capacity (the size of the array currently allocated) + * and a limit (the maximum size that may be allocated), which defaults to + * {@link Integer#MAX_VALUE}. + * + * @param The element type + */ +public class BlockingArrayQueue extends AbstractList implements Queue +{ + public final int DEFAULT_CAPACITY=64; + public final int DEFAULT_GROWTH=32; + protected final int _limit; + protected final AtomicInteger _size=new AtomicInteger(); + protected final int _growCapacity; + + protected Object[] _elements; + protected int _head; + protected int _tail; + + private final ReentrantLock _headLock = new ReentrantLock(); + private final Condition _notEmpty = _headLock.newCondition(); + private final ReentrantLock _tailLock = new ReentrantLock(); + + /* ------------------------------------------------------------ */ + /** Create a growing partially blocking Queue + * + */ + public BlockingArrayQueue() + { + _elements=new Object[64]; + _growCapacity=32; + _limit=Integer.MAX_VALUE; + } + + /* ------------------------------------------------------------ */ + /** Create a fixed size partially blocking Queue + * @param limit The initial capacity and the limit. + */ + public BlockingArrayQueue(int limit) + { + _elements=new Object[limit]; + _growCapacity=-1; + _limit=limit; + } + + /* ------------------------------------------------------------ */ + /** Create a growing partially blocking Queue. + * @param capacity Initial capacity + * @param growBy Incremental capacity. + */ + public BlockingArrayQueue(int capacity,int growBy) + { + _elements=new Object[capacity]; + _growCapacity=growBy; + _limit=Integer.MAX_VALUE; + } + + /* ------------------------------------------------------------ */ + /** Create a growing limited partially blocking Queue. + * @param capacity Initial capacity + * @param growBy Incremental capacity. + * @param limit maximum capacity. + */ + public BlockingArrayQueue(int capacity,int growBy,int limit) + { + if (capacity>limit) + throw new IllegalArgumentException(); + + _elements=new Object[capacity]; + _growCapacity=growBy; + _limit=limit; + } + + /* ------------------------------------------------------------ */ + public int getCapacity() + { + return _elements.length; + } + + /* ------------------------------------------------------------ */ + public int getLimit() + { + return _limit; + } + + /* ------------------------------------------------------------ */ + public boolean add(E e) + { + return offer(e); + } + + /* ------------------------------------------------------------ */ + public E element() + { + E e = peek(); + if (e==null) + throw new NoSuchElementException(); + return e; + } + + /* ------------------------------------------------------------ */ + public E peek() + { + if (_size.get() == 0) + return null; + + E e = null; + _headLock.lock(); // Size cannot shrink + try + { + + if (_size.get() > 0) + e = (E)_elements[_head]; + } + finally + { + _headLock.unlock(); + } + + return e; + } + + /* ------------------------------------------------------------ */ + public boolean offer(E e) + { + if (e == null) + throw new NullPointerException(); + + boolean not_empty=false; + _tailLock.lock(); // size cannot grow... only shrink + try + { + if (_size.get() >= _limit) + return false; + else + { + // should we expand array? + if (_size.get()==_elements.length) + { + _headLock.lock(); // Need to grow array + try + { + if (!grow()) + return false; + } + finally + { + _headLock.unlock(); + } + } + + // add the element + _elements[_tail]=e; + _tail=(_tail+1)%_elements.length; + + not_empty=0==_size.getAndIncrement(); + } + } + finally + { + _tailLock.unlock(); + } + + if (not_empty) + { + _headLock.lock(); + try + { + _notEmpty.signal(); + } + finally + { + _headLock.unlock(); + } + } + + return true; + } + + + /* ------------------------------------------------------------ */ + public E poll() + { + if (_size.get() == 0) + return null; + + E e = null; + _headLock.lock(); // Size cannot shrink + try + { + if (_size.get() > 0) + { + final int head=_head; + e = (E)_elements[head]; + _head=(head+1)%_elements.length; + + if (_size.decrementAndGet()>0) + _notEmpty.signal(); + } + } + finally + { + _headLock.unlock(); + } + + return e; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieves and removes the head of this queue, waiting + * if no elements are present on this queue. + * @return the head of this queue + * @throws InterruptedException if interrupted while waiting. + */ + public E take() throws InterruptedException + { + E e = null; + _headLock.lockInterruptibly(); // Size cannot shrink + try + { + try + { + while (_size.get() == 0) + { + _notEmpty.await(); + } + } + catch (InterruptedException ie) + { + _notEmpty.signal(); + throw ie; + } + + final int head=_head; + e = (E)_elements[head]; + _head=(head+1)%_elements.length; + + if (_size.decrementAndGet()>0) + _notEmpty.signal(); + } + finally + { + _headLock.unlock(); + } + + return e; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieves and removes the head of this queue, waiting + * if necessary up to the specified wait time if no elements are + * present on this queue. + * @param timeout how long to wait before giving up, in units of + * unit + * @param unit a TimeUnit determining how to interpret the + * timeout parameter + * @return the head of this queue, or null if the + * specified waiting time elapses before an element is present. + * @throws InterruptedException if interrupted while waiting. + */ + public E poll(long time, TimeUnit unit) throws InterruptedException + { + + E e = null; + + long nanos = unit.toNanos(time); + + _headLock.lockInterruptibly(); // Size cannot shrink + try + { + try + { + while (_size.get() == 0) + { + if (nanos<=0) + return null; + nanos = _notEmpty.awaitNanos(nanos); + } + } + catch (InterruptedException ie) + { + _notEmpty.signal(); + throw ie; + } + + e = (E)_elements[_head]; + _head=(_head+1)%_elements.length; + + if (_size.decrementAndGet()>0) + _notEmpty.signal(); + } + finally + { + _headLock.unlock(); + } + + return e; + } + + /* ------------------------------------------------------------ */ + public E remove() + { + E e=poll(); + if (e==null) + throw new NoSuchElementException(); + return e; + } + + /* ------------------------------------------------------------ */ + public void clear() + { + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + _head=0; + _tail=0; + _size.set(0); + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + public boolean isEmpty() + { + return _size.get()==0; + } + + /* ------------------------------------------------------------ */ + public int size() + { + return _size.get(); + } + + /* ------------------------------------------------------------ */ + public E get(int index) + { + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + if (index<0 || index>=_size.get()) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + int i = _head+index; + if (i>=_elements.length) + i-=_elements.length; + return (E)_elements[i]; + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + public E remove(int index) + { + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + + if (index<0 || index>=_size.get()) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + int i = _head+index; + if (i>=_elements.length) + i-=_elements.length; + E old=(E)_elements[i]; + + if (i<_tail) + { + System.arraycopy(_elements,i+1,_elements,i,_tail-i); + _tail--; + _size.decrementAndGet(); + } + else + { + System.arraycopy(_elements,i+1,_elements,i,_elements.length-i-1); + if (_tail>0) + { + _elements[_elements.length]=_elements[0]; + System.arraycopy(_elements,1,_elements,0,_tail-1); + _tail--; + } + else + _tail=_elements.length-1; + + _size.decrementAndGet(); + } + + return old; + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + public E set(int index, E e) + { + if (e == null) + throw new NullPointerException(); + + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + + if (index<0 || index>=_size.get()) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + int i = _head+index; + if (i>=_elements.length) + i-=_elements.length; + E old=(E)_elements[i]; + _elements[i]=e; + return old; + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + public void add(int index, E e) + { + if (e == null) + throw new NullPointerException(); + + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + + if (index<0 || index>_size.get()) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + if (index==_size.get()) + { + add(e); + } + else + { + if (_tail==_head) + if (!grow()) + throw new IllegalStateException("full"); + + int i = _head+index; + if (i>=_elements.length) + i-=_elements.length; + + _size.incrementAndGet(); + _tail=(_tail+1)%_elements.length; + + + if (i<_tail) + { + System.arraycopy(_elements,i,_elements,i+1,_tail-i); + _elements[i]=e; + } + else + { + if (_tail>0) + { + System.arraycopy(_elements,0,_elements,1,_tail); + _elements[0]=_elements[_elements.length-1]; + } + + System.arraycopy(_elements,i,_elements,i+1,_elements.length-i-1); + _elements[i]=e; + } + } + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + private boolean grow() + { + if (_growCapacity<=0) + return false; + + final int head=_head; + final int tail=_tail; + final int s; + + Object[] elements=new Object[_elements.length+_growCapacity]; + + if (head=0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + char[] ca ={c}; + writeEncoded(ca,0,1); + } + } + + /* ------------------------------------------------------------ */ + public void write(char[] ca) + throws IOException + { + ensureSpareCapacity(ca.length); + for (int i=0;i=0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + writeEncoded(ca,i,ca.length-i); + break; + } + } + } + + /* ------------------------------------------------------------ */ + public void write(char[] ca,int offset, int length) + throws IOException + { + ensureSpareCapacity(length); + for (int i=0;i=0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + writeEncoded(ca,offset+i,length-i); + break; + } + } + } + + /* ------------------------------------------------------------ */ + public void write(String s) + throws IOException + { + if (s==null) + { + write("null",0,4); + return; + } + + int length=s.length(); + ensureSpareCapacity(length); + for (int i=0;i=0x0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + writeEncoded(s.toCharArray(),i,length-i); + break; + } + } + } + + /* ------------------------------------------------------------ */ + public void write(String s,int offset, int length) + throws IOException + { + ensureSpareCapacity(length); + for (int i=0;i=0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + writeEncoded(s.toCharArray(),offset+i,length-i); + break; + } + } + } + + /* ------------------------------------------------------------ */ + private void writeEncoded(char[] ca,int offset, int length) + throws IOException + { + if (_bout==null) + { + _bout = new ByteArrayOutputStream2(2*length); + _writer = new OutputStreamWriter(_bout,StringUtil.__ISO_8859_1); + } + else + _bout.reset(); + _writer.write(ca,offset,length); + _writer.flush(); + ensureSpareCapacity(_bout.getCount()); + System.arraycopy(_bout.getBuf(),0,_buf,_size,_bout.getCount()); + _size+=_bout.getCount(); + } + + /* ------------------------------------------------------------ */ + public void flush() + {} + + /* ------------------------------------------------------------ */ + public void resetWriter() + { + _size=0; + } + + /* ------------------------------------------------------------ */ + public void close() + {} + + /* ------------------------------------------------------------ */ + public void destroy() + { + _buf=null; + } + + /* ------------------------------------------------------------ */ + public void ensureSpareCapacity(int n) + throws IOException + { + if (_size+n>_buf.length) + { + if (_fixed) + throw new IOException("Buffer overflow: "+_buf.length); + byte[] buf = new byte[(_buf.length+n)*4/3]; + System.arraycopy(_buf,0,buf,0,_size); + _buf=buf; + } + } + + + /* ------------------------------------------------------------ */ + public byte[] getByteArray() + { + byte[] data=new byte[_size]; + System.arraycopy(_buf,0,data,0,_size); + return data; + } + +} + + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java new file mode 100644 index 00000000000..b1bcee2a97e --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java @@ -0,0 +1,44 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; +import java.io.ByteArrayOutputStream; + +/* ------------------------------------------------------------ */ +/** ByteArrayOutputStream with public internals + + * + */ +public class ByteArrayOutputStream2 extends ByteArrayOutputStream +{ + public ByteArrayOutputStream2(){super();} + public ByteArrayOutputStream2(int size){super(size);} + public byte[] getBuf(){return buf;} + public int getCount(){return count;} + public void setCount(int count){this.count = count;} + + public void reset(int minSize) + { + reset(); + if (buf.length= 0 ) + { + String ss1 = _formatString.substring( 0, zIndex ); + String ss2 = _formatString.substring( zIndex+3 ); + int tzOffset = tz.getRawOffset(); + + StringBuilder sb = new StringBuilder(_formatString.length()+10); + sb.append(ss1); + sb.append("'"); + if( tzOffset >= 0 ) + sb.append( '+' ); + else + { + tzOffset = -tzOffset; + sb.append( '-' ); + } + + int raw = tzOffset / (1000*60); // Convert to seconds + int hr = raw / 60; + int min = raw % 60; + + if( hr < 10 ) + sb.append( '0' ); + sb.append( hr ); + if( min < 10 ) + sb.append( '0' ); + sb.append( min ); + sb.append( '\'' ); + + sb.append(ss2); + _tzFormatString=sb.toString(); + } + else + _tzFormatString=_formatString; + setMinFormatString(); + } + + + /* ------------------------------------------------------------ */ + private void setMinFormatString() + { + int i = _tzFormatString.indexOf("ss.SSS"); + int l = 6; + if (i>=0) + throw new IllegalStateException("ms not supported"); + i = _tzFormatString.indexOf("ss"); + l=2; + + // Build a formatter that formats a second format string + String ss1=_tzFormatString.substring(0,i); + String ss2=_tzFormatString.substring(i+l); + _minFormatString =ss1+"'ss'"+ss2; + } + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * @param inDate + * @return Formatted date + */ + public synchronized String format(Date inDate) + { + return format(inDate.getTime()); + } + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * @param inDate + * @return Formatted date + */ + public synchronized String format(long inDate) + { + long seconds = inDate / 1000; + + // Is it not suitable to cache? + if (seconds<_lastSeconds || + _lastSeconds>0 && seconds>_lastSeconds+__hitWindow) + { + // It's a cache miss + Date d = new Date(inDate); + return _tzFormat.format(d); + + } + + // Check if we are in the same second + // and don't care about millis + if (_lastSeconds==seconds ) + return _lastResult; + + Date d = new Date(inDate); + + // Check if we need a new format string + long minutes = seconds/60; + if (_lastMinutes != minutes) + { + _lastMinutes = minutes; + _secFormatString=_minFormat.format(d); + + int i=_secFormatString.indexOf("ss"); + int l=2; + _secFormatString0=_secFormatString.substring(0,i); + _secFormatString1=_secFormatString.substring(i+l); + } + + // Always format if we get here + _lastSeconds = seconds; + StringBuilder sb=new StringBuilder(_secFormatString.length()); + sb.append(_secFormatString0); + int s=(int)(seconds%60); + if (s<10) + sb.append('0'); + sb.append(s); + sb.append(_secFormatString1); + _lastResult=sb.toString(); + + + return _lastResult; + } + + /* ------------------------------------------------------------ */ + /** Format to string buffer. + * @param inDate Date the format + * @param buffer StringBuilder + */ + public void format(long inDate, StringBuilder buffer) + { + buffer.append(format(inDate)); + } + + /* ------------------------------------------------------------ */ + /** Get the format. + */ + public SimpleDateFormat getFormat() + { + return _minFormat; + } + + /* ------------------------------------------------------------ */ + public String getFormatString() + { + return _formatString; + } + + /* ------------------------------------------------------------ */ + public String now() + { + long now=System.currentTimeMillis(); + _lastMs=(int)(now%1000); + return format(now); + } + + /* ------------------------------------------------------------ */ + public int lastMs() + { + return _lastMs; + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java new file mode 100644 index 00000000000..ad503a7f756 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java @@ -0,0 +1,461 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +/* ======================================================================== */ +/** IO Utilities. + * Provides stream handling utilities in + * singleton Threadpool implementation accessed by static members. + */ +public class IO +{ + /* ------------------------------------------------------------------- */ + public final static String + CRLF = "\015\012"; + + /* ------------------------------------------------------------------- */ + public final static byte[] + CRLF_BYTES = {(byte)'\015',(byte)'\012'}; + + /* ------------------------------------------------------------------- */ + public static int bufferSize = 2*8192; + + /* ------------------------------------------------------------------- */ + // TODO get rid of this singleton! + private static class Singleton { + static final QueuedThreadPool __pool=new QueuedThreadPool(); + static + { + try{__pool.start();} + catch(Exception e){Log.warn(e); System.exit(1);} + } + } + + /* ------------------------------------------------------------------- */ + static class Job implements Runnable + { + InputStream in; + OutputStream out; + Reader read; + Writer write; + + Job(InputStream in,OutputStream out) + { + this.in=in; + this.out=out; + this.read=null; + this.write=null; + } + Job(Reader read,Writer write) + { + this.in=null; + this.out=null; + this.read=read; + this.write=write; + } + + /* ------------------------------------------------------------ */ + /* + * @see java.lang.Runnable#run() + */ + public void run() + { + try { + if (in!=null) + copy(in,out,-1); + else + copy(read,write,-1); + } + catch(IOException e) + { + Log.ignore(e); + try{ + if (out!=null) + out.close(); + if (write!=null) + write.close(); + } + catch(IOException e2) + { + Log.ignore(e2); + } + } + } + } + + /* ------------------------------------------------------------------- */ + /** Copy Stream in to Stream out until EOF or exception. + * in own thread + */ + public static void copyThread(InputStream in, OutputStream out) + { + try{ + Job job=new Job(in,out); + if (!Singleton.__pool.dispatch(job)) + job.run(); + } + catch(Exception e) + { + Log.warn(e); + } + } + + /* ------------------------------------------------------------------- */ + /** Copy Stream in to Stream out until EOF or exception. + */ + public static void copy(InputStream in, OutputStream out) + throws IOException + { + copy(in,out,-1); + } + + /* ------------------------------------------------------------------- */ + /** Copy Stream in to Stream out until EOF or exception + * in own thread + */ + public static void copyThread(Reader in, Writer out) + { + try + { + Job job=new Job(in,out); + if (!Singleton.__pool.dispatch(job)) + job.run(); + } + catch(Exception e) + { + Log.warn(e); + } + } + + /* ------------------------------------------------------------------- */ + /** Copy Reader to Writer out until EOF or exception. + */ + public static void copy(Reader in, Writer out) + throws IOException + { + copy(in,out,-1); + } + + /* ------------------------------------------------------------------- */ + /** Copy Stream in to Stream for byteCount bytes or until EOF or exception. + */ + public static void copy(InputStream in, + OutputStream out, + long byteCount) + throws IOException + { + byte buffer[] = new byte[bufferSize]; + int len=bufferSize; + + if (byteCount>=0) + { + while (byteCount>0) + { + int max = byteCount=0) + { + while (byteCount>0) + { + if (byteCount + * LazyList works by passing an opaque representation of the list in + * and out of all the LazyList methods. This opaque object is either + * null for an empty list, an Object for a list with a single entry + * or an ArrayList for a list of items. + * + *

    Usage

    + *
    + *   Object lazylist =null;
    + *   while(loopCondition)
    + *   {
    + *     Object item = getItem();
    + *     if (item.isToBeAdded())
    + *         lazylist = LazyList.add(lazylist,item);
    + *   }
    + *   return LazyList.getList(lazylist);
    + * 
    + * + * An ArrayList of default size is used as the initial LazyList. + * + * @see java.util.List + * + */ +public class LazyList + implements Cloneable, Serializable +{ + private static final String[] __EMTPY_STRING_ARRAY = new String[0]; + + /* ------------------------------------------------------------ */ + private LazyList() + {} + + /* ------------------------------------------------------------ */ + /** Add an item to a LazyList + * @param list The list to add to or null if none yet created. + * @param item The item to add. + * @return The lazylist created or added to. + */ + @SuppressWarnings("unchecked") + public static Object add(Object list, Object item) + { + if (list==null) + { + if (item instanceof List || item==null) + { + List l = new ArrayList(); + l.add(item); + return l; + } + + return item; + } + + if (list instanceof List) + { + ((List)list).add(item); + return list; + } + + List l=new ArrayList(); + l.add(list); + l.add(item); + return l; + } + + /* ------------------------------------------------------------ */ + /** Add an item to a LazyList + * @param list The list to add to or null if none yet created. + * @param index The index to add the item at. + * @param item The item to add. + * @return The lazylist created or added to. + */ + @SuppressWarnings("unchecked") + public static Object add(Object list, int index, Object item) + { + if (list==null) + { + if (index>0 || item instanceof List || item==null) + { + List l = new ArrayList(); + l.add(index,item); + return l; + } + return item; + } + + if (list instanceof List) + { + ((List)list).add(index,item); + return list; + } + + List l=new ArrayList(); + l.add(list); + l.add(index,item); + return l; + } + + /* ------------------------------------------------------------ */ + /** Add the contents of a Collection to a LazyList + * @param list The list to add to or null if none yet created. + * @param collection The Collection whose contents should be added. + * @return The lazylist created or added to. + */ + public static Object addCollection(Object list, Collection collection) + { + Iterator i=collection.iterator(); + while(i.hasNext()) + list=LazyList.add(list,i.next()); + return list; + } + + /* ------------------------------------------------------------ */ + /** Add the contents of an array to a LazyList + * @param list The list to add to or null if none yet created. + * @param array The array whose contents should be added. + * @return The lazylist created or added to. + */ + public static Object addArray(Object list, Object[] array) + { + for(int i=0;array!=null && i(initialSize); + if (list instanceof ArrayList) + { + ArrayList ol=(ArrayList)list; + if (ol.size()>initialSize) + return ol; + ArrayList nl = new ArrayList(initialSize); + nl.addAll(ol); + return nl; + } + List l= new ArrayList(initialSize); + l.add(list); + return l; + } + + /* ------------------------------------------------------------ */ + public static Object remove(Object list, Object o) + { + if (list==null) + return null; + + if (list instanceof List) + { + List l = (List)list; + l.remove(o); + if (l.size()==0) + return null; + return list; + } + + if (list.equals(o)) + return null; + return list; + } + + /* ------------------------------------------------------------ */ + public static Object remove(Object list, int i) + { + if (list==null) + return null; + + if (list instanceof List) + { + List l = (List)list; + l.remove(i); + if (l.size()==0) + return null; + return list; + } + + if (i==0) + return null; + return list; + } + + + + /* ------------------------------------------------------------ */ + /** Get the real List from a LazyList. + * + * @param list A LazyList returned from LazyList.add(Object) + * @return The List of added items, which may be an EMPTY_LIST + * or a SingletonList. + */ + public static List getList(Object list) + { + return getList(list,false); + } + + + /* ------------------------------------------------------------ */ + /** Get the real List from a LazyList. + * + * @param list A LazyList returned from LazyList.add(Object) or null + * @param nullForEmpty If true, null is returned instead of an + * empty list. + * @return The List of added items, which may be null, an EMPTY_LIST + * or a SingletonList. + */ + @SuppressWarnings("unchecked") + public static List getList(Object list, boolean nullForEmpty) + { + if (list==null) + { + if (nullForEmpty) + return null; + return Collections.emptyList(); + } + if (list instanceof List) + return (List)list; + + return (List)Collections.singletonList(list); + } + + + /* ------------------------------------------------------------ */ + public static String[] toStringArray(Object list) + { + if (list==null) + return __EMTPY_STRING_ARRAY; + + if (list instanceof List) + { + List l = (List)list; + String[] a = new String[l.size()]; + for (int i=l.size();i-->0;) + { + Object o=l.get(i); + if (o!=null) + a[i]=o.toString(); + } + return a; + } + + return new String[] {list.toString()}; + } + + /* ------------------------------------------------------------ */ + /** Convert a lazylist to an array + * @param list The list to convert + * @param clazz The class of the array, which may be a primitive type + * @return + */ + @SuppressWarnings("unchecked") + public static Object toArray(Object list,Class clazz) + { + if (list==null) + return Array.newInstance(clazz,0); + + if (list instanceof List) + { + List l = (List)list; + if (clazz.isPrimitive()) + { + Object a = Array.newInstance(clazz,l.size()); + for (int i=0;i)list).size(); + return 1; + } + + /* ------------------------------------------------------------ */ + /** Get item from the list + * @param list A LazyList returned from LazyList.add(Object) or null + * @param i int index + * @return the item from the list. + */ + @SuppressWarnings("unchecked") + public static E get(Object list, int i) + { + if (list==null) + throw new IndexOutOfBoundsException(); + + if (list instanceof List) + return (E)((List)list).get(i); + + if (i==0) + return (E)list; + + throw new IndexOutOfBoundsException(); + } + + /* ------------------------------------------------------------ */ + public static boolean contains(Object list,Object item) + { + if (list==null) + return false; + + if (list instanceof List) + return ((List)list).contains(item); + + return list.equals(item); + } + + + /* ------------------------------------------------------------ */ + public static Object clone(Object list) + { + if (list==null) + return null; + if (list instanceof List) + return new ArrayList((List)list); + return list; + } + + /* ------------------------------------------------------------ */ + public static String toString(Object list) + { + if (list==null) + return "[]"; + if (list instanceof List) + return list.toString(); + return "["+list+"]"; + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + public static Iterator iterator(Object list) + { + if (list==null) + { + List empty=Collections.emptyList(); + return empty.iterator(); + } + if (list instanceof List) + { + return ((List)list).iterator(); + } + List l=getList(list); + return l.iterator(); + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + public static ListIterator listIterator(Object list) + { + if (list==null) + { + List empty=Collections.emptyList(); + return empty.listIterator(); + } + if (list instanceof List) + return ((List)list).listIterator(); + + List l=getList(list); + return l.listIterator(); + } + + /* ------------------------------------------------------------ */ + /** + * @param array Any array of object + * @return A new modifiable list initialised with the elements from array. + */ + public static List array2List(E[] array) + { + if (array==null || array.length==0) + return new ArrayList(); + return new ArrayList(Arrays.asList(array)); + } + + /* ------------------------------------------------------------ */ + /** Add element to an array + * @param array The array to add to (or null) + * @param item The item to add + * @param type The type of the array (in case of null array) + * @return new array with contents of array plus item + */ + @SuppressWarnings("unchecked") + public static Object[] addToArray(Object[] array, Object item, Class type) + { + if (array==null) + { + if (type==null && item!=null) + type= (Class)item.getClass(); + Object[] na = (Object[])Array.newInstance(type, 1); + na[0]=item; + return na; + } + else + { + Class c = array.getClass().getComponentType(); + Object[] na = (Object[])Array.newInstance(c, Array.getLength(array)+1); + System.arraycopy(array, 0, na, 0, array.length); + na[array.length]=item; + return na; + } + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + public static Object removeFromArray(Object[] array, Object item) + { + if (item==null || array==null) + return array; + for (int i=array.length;i-->0;) + { + if (item.equals(array[i])) + { + Class c = array==null?item.getClass():array.getClass().getComponentType(); + Object[] na = (Object[])Array.newInstance(c, Array.getLength(array)-1); + if (i>0) + System.arraycopy(array, 0, na, 0, i); + if (i+1Usage:
    + * public class MyClass {
    + *     void myMethod() {
    + *          ...
    + *          Class c=Loader.loadClass(this.getClass(),classname);
    + *          ...
    + *     }
    + * 
    + * + */ +public class Loader +{ + /* ------------------------------------------------------------ */ + public static URL getResource(Class loadClass,String name, boolean checkParents) + throws ClassNotFoundException + { + URL url =null; + ClassLoader loader=Thread.currentThread().getContextClassLoader(); + while (url==null && loader!=null ) + { + url=loader.getResource(name); + loader=(url==null&&checkParents)?loader.getParent():null; + } + + loader=loadClass==null?null:loadClass.getClassLoader(); + while (url==null && loader!=null ) + { + url=loader.getResource(name); + loader=(url==null&&checkParents)?loader.getParent():null; + } + + if (url==null) + { + url=ClassLoader.getSystemResource(name); + } + + return url; + } + + /* ------------------------------------------------------------ */ + public static Class loadClass(Class loadClass,String name) + throws ClassNotFoundException + { + return loadClass(loadClass,name,false); + } + + /* ------------------------------------------------------------ */ + /** Load a class. + * + * @param loadClass + * @param name + * @param checkParents If true, try loading directly from parent classloaders. + * @return Class + * @throws ClassNotFoundException + */ + public static Class loadClass(Class loadClass,String name,boolean checkParents) + throws ClassNotFoundException + { + ClassNotFoundException ex=null; + Class c =null; + ClassLoader loader=Thread.currentThread().getContextClassLoader(); + while (c==null && loader!=null ) + { + try { c=loader.loadClass(name); } + catch (ClassNotFoundException e) {if(ex==null)ex=e;} + loader=(c==null&&checkParents)?loader.getParent():null; + } + + loader=loadClass==null?null:loadClass.getClassLoader(); + while (c==null && loader!=null ) + { + try { c=loader.loadClass(name); } + catch (ClassNotFoundException e) {if(ex==null)ex=e;} + loader=(c==null&&checkParents)?loader.getParent():null; + } + + if (c==null) + { + try { c=Class.forName(name); } + catch (ClassNotFoundException e) {if(ex==null)ex=e;} + } + + if (c!=null) + return c; + throw ex; + } + + public static ResourceBundle getResourceBundle(Class loadClass,String name,boolean checkParents, Locale locale) + throws MissingResourceException + { + MissingResourceException ex=null; + ResourceBundle bundle =null; + ClassLoader loader=Thread.currentThread().getContextClassLoader(); + while (bundle==null && loader!=null ) + { + try { bundle=ResourceBundle.getBundle(name, locale, loader); } + catch (MissingResourceException e) {if(ex==null)ex=e;} + loader=(bundle==null&&checkParents)?loader.getParent():null; + } + + loader=loadClass==null?null:loadClass.getClassLoader(); + while (bundle==null && loader!=null ) + { + try { bundle=ResourceBundle.getBundle(name, locale, loader); } + catch (MissingResourceException e) {if(ex==null)ex=e;} + loader=(bundle==null&&checkParents)?loader.getParent():null; + } + + if (bundle==null) + { + try { bundle=ResourceBundle.getBundle(name, locale); } + catch (MissingResourceException e) {if(ex==null)ex=e;} + } + + if (bundle!=null) + return bundle; + throw ex; + } + + +} + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java new file mode 100644 index 00000000000..d55207684f6 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java @@ -0,0 +1,175 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.List; + + +/* ------------------------------------------------------------ */ +/** Wraps multiple exceptions. + * + * Allows multiple exceptions to be thrown as a single exception. + * + * + */ +public class MultiException extends Exception +{ + private Object nested; + + /* ------------------------------------------------------------ */ + public MultiException() + { + super("Multiple exceptions"); + } + + /* ------------------------------------------------------------ */ + public void add(Throwable e) + { + if (e instanceof MultiException) + { + MultiException me = (MultiException)e; + for (int i=0;i0) + throw this; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + if (LazyList.size(nested)>0) + return "org.eclipse.util.MultiException"+ + LazyList.getList(nested); + return "org.eclipse.util.MultiException[]"; + } + + /* ------------------------------------------------------------ */ + public void printStackTrace() + { + super.printStackTrace(); + for (int i=0;i + * Implemented as a map of LazyList values + * + * @see LazyList + * + */ +public class MultiMap implements ConcurrentMap +{ + Map _map; + ConcurrentMap _cmap; + + public MultiMap() + { + _map=new HashMap(); + } + + public MultiMap(Map map) + { + if (map instanceof ConcurrentMap) + _map=_cmap=new ConcurrentHashMap(map); + else + _map=new HashMap(map); + } + + public MultiMap(int capacity) + { + _map=new HashMap(capacity); + } + + public MultiMap(boolean concurrent) + { + if (concurrent) + _map=_cmap=new ConcurrentHashMap(); + else + _map=new HashMap(); + } + + + /* ------------------------------------------------------------ */ + /** Get multiple values. + * Single valued entries are converted to singleton lists. + * @param name The entry key. + * @return Unmodifieable List of values. + */ + public List getValues(Object name) + { + return LazyList.getList(_map.get(name),true); + } + + /* ------------------------------------------------------------ */ + /** Get a value from a multiple value. + * If the value is not a multivalue, then index 0 retrieves the + * value or null. + * @param name The entry key. + * @param i Index of element to get. + * @return Unmodifieable List of values. + */ + public Object getValue(Object name,int i) + { + Object l=_map.get(name); + if (i==0 && LazyList.size(l)==0) + return null; + return LazyList.get(l,i); + } + + + /* ------------------------------------------------------------ */ + /** Get value as String. + * Single valued items are converted to a String with the toString() + * Object method. Multi valued entries are converted to a comma separated + * List. No quoting of commas within values is performed. + * @param name The entry key. + * @return String value. + */ + public String getString(Object name) + { + Object l=_map.get(name); + switch(LazyList.size(l)) + { + case 0: + return null; + case 1: + Object o=LazyList.get(l,0); + return o==null?null:o.toString(); + default: + { + StringBuilder values=new StringBuilder(128); + for (int i=0; i0) + values.append(','); + values.append(e.toString()); + } + } + return values.toString(); + } + } + } + + /* ------------------------------------------------------------ */ + public Object get(Object name) + { + Object l=_map.get(name); + switch(LazyList.size(l)) + { + case 0: + return null; + case 1: + Object o=LazyList.get(l,0); + return o; + default: + return LazyList.getList(l,true); + } + } + + /* ------------------------------------------------------------ */ + /** Put and entry into the map. + * @param name The entry key. + * @param value The entry value. + * @return The previous value or null. + */ + public Object put(K name, Object value) + { + return _map.put(name,LazyList.add(null,value)); + } + + /* ------------------------------------------------------------ */ + /** Put multi valued entry. + * @param name The entry key. + * @param values The List of multiple values. + * @return The previous value or null. + */ + public Object putValues(K name, List values) + { + return _map.put(name,values); + } + + /* ------------------------------------------------------------ */ + /** Put multi valued entry. + * @param name The entry key. + * @param values The String array of multiple values. + * @return The previous value or null. + */ + public Object putValues(K name, String[] values) + { + Object list=null; + for (int i=0;i0) + { + ln=LazyList.remove(lo,value); + if (ln==null) + _map.remove(name); + else + _map.put(name, ln); + } + return LazyList.size(ln)!=s; + } + + /* ------------------------------------------------------------ */ + /** Put all contents of map. + * @param m Map + */ + public void putAll(Map m) + { + Iterator i = m.entrySet().iterator(); + boolean multi=m instanceof MultiMap; + while(i.hasNext()) + { + Map.Entry entry = (Map.Entry)i.next(); + if (multi) + _map.put((K)(entry.getKey()),LazyList.clone(entry.getValue())); + else + put((K)(entry.getKey()),entry.getValue()); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return Map of String arrays + */ + public Map toStringArrayMap() + { + HashMap map = new HashMap(_map.size()*3/2); + + Iterator i = _map.entrySet().iterator(); + while(i.hasNext()) + { + Map.Entry entry = (Map.Entry)i.next(); + Object l = entry.getValue(); + String[] a = LazyList.toStringArray(l); + // for (int j=a.length;j-->0;) + // if (a[j]==null) + // a[j]=""; + map.put(entry.getKey(),a); + } + return map; + } + + public void clear() + { + _map.clear(); + } + + public boolean containsKey(Object key) + { + return _map.containsKey(key); + } + + public boolean containsValue(Object value) + { + return _map.containsValue(value); + } + + public Set> entrySet() + { + return _map.entrySet(); + } + + public boolean equals(Object o) + { + return _map.equals(o); + } + + public int hashCode() + { + return _map.hashCode(); + } + + public boolean isEmpty() + { + return _map.isEmpty(); + } + + public Set keySet() + { + return _map.keySet(); + } + + public Object remove(Object key) + { + return _map.remove(key); + } + + public int size() + { + return _map.size(); + } + + public Collection values() + { + return _map.values(); + } + + + + public Object putIfAbsent(K key, Object value) + { + if (_cmap==null) + throw new UnsupportedOperationException(); + return _cmap.putIfAbsent(key,value); + } + + public boolean remove(Object key, Object value) + { + if (_cmap==null) + throw new UnsupportedOperationException(); + return _cmap.remove(key,value); + } + + public boolean replace(K key, Object oldValue, Object newValue) + { + if (_cmap==null) + throw new UnsupportedOperationException(); + return _cmap.replace(key,oldValue,newValue); + } + + public Object replace(K key, Object value) + { + if (_cmap==null) + throw new UnsupportedOperationException(); + return _cmap.replace(key,value); + } + + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java new file mode 100644 index 00000000000..ebb68c401d9 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java @@ -0,0 +1,135 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + + +/* ================================================================ */ +/** Handle a multipart MIME response. + * + * + * +*/ +public class MultiPartOutputStream extends FilterOutputStream +{ + /* ------------------------------------------------------------ */ + private static byte[] __CRLF; + private static byte[] __DASHDASH; + + public static String MULTIPART_MIXED="multipart/mixed"; + public static String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace"; + static + { + try + { + __CRLF="\015\012".getBytes(StringUtil.__ISO_8859_1); + __DASHDASH="--".getBytes(StringUtil.__ISO_8859_1); + } + catch (Exception e) {e.printStackTrace(); System.exit(1);} + } + + /* ------------------------------------------------------------ */ + private String boundary; + private byte[] boundaryBytes; + + /* ------------------------------------------------------------ */ + private boolean inPart=false; + + /* ------------------------------------------------------------ */ + public MultiPartOutputStream(OutputStream out) + throws IOException + { + super(out); + + boundary = "jetty"+System.identityHashCode(this)+ + Long.toString(System.currentTimeMillis(),36); + boundaryBytes=boundary.getBytes(StringUtil.__ISO_8859_1); + + inPart=false; + } + + + + /* ------------------------------------------------------------ */ + /** End the current part. + * @exception IOException IOException + */ + public void close() + throws IOException + { + if (inPart) + out.write(__CRLF); + out.write(__DASHDASH); + out.write(boundaryBytes); + out.write(__DASHDASH); + out.write(__CRLF); + inPart=false; + super.close(); + } + + /* ------------------------------------------------------------ */ + public String getBoundary() + { + return boundary; + } + + public OutputStream getOut() {return out;} + + /* ------------------------------------------------------------ */ + /** Start creation of the next Content. + */ + public void startPart(String contentType) + throws IOException + { + if (inPart) + out.write(__CRLF); + inPart=true; + out.write(__DASHDASH); + out.write(boundaryBytes); + out.write(__CRLF); + out.write(("Content-Type: "+contentType).getBytes(StringUtil.__ISO_8859_1)); + out.write(__CRLF); + out.write(__CRLF); + } + + /* ------------------------------------------------------------ */ + /** Start creation of the next Content. + */ + public void startPart(String contentType, String[] headers) + throws IOException + { + if (inPart) + out.write(__CRLF); + inPart=true; + out.write(__DASHDASH); + out.write(boundaryBytes); + out.write(__CRLF); + out.write(("Content-Type: "+contentType).getBytes(StringUtil.__ISO_8859_1)); + out.write(__CRLF); + for (int i=0;headers!=null && i=0 || + _delim.indexOf('"')>=0) + throw new Error("Can't use quotes as delimiters: "+_delim); + + _token=new StringBuffer(_string.length()>1024?512:_string.length()/2); + } + + /* ------------------------------------------------------------ */ + public QuotedStringTokenizer(String str, + String delim, + boolean returnDelimiters) + { + this(str,delim,returnDelimiters,false); + } + + /* ------------------------------------------------------------ */ + public QuotedStringTokenizer(String str, + String delim) + { + this(str,delim,false,false); + } + + /* ------------------------------------------------------------ */ + public QuotedStringTokenizer(String str) + { + this(str,null,false,false); + } + + /* ------------------------------------------------------------ */ + public boolean hasMoreTokens() + { + // Already found a token + if (_hasToken) + return true; + + _lastStart=_i; + + int state=0; + boolean escape=false; + while (_i<_string.length()) + { + char c=_string.charAt(_i++); + + switch (state) + { + case 0: // Start + if(_delim.indexOf(c)>=0) + { + if (_returnDelimiters) + { + _token.append(c); + return _hasToken=true; + } + } + else if (c=='\'' && _single) + { + if (_returnQuotes) + _token.append(c); + state=2; + } + else if (c=='\"' && _double) + { + if (_returnQuotes) + _token.append(c); + state=3; + } + else + { + _token.append(c); + _hasToken=true; + state=1; + } + continue; + + case 1: // Token + _hasToken=true; + if(_delim.indexOf(c)>=0) + { + if (_returnDelimiters) + _i--; + return _hasToken; + } + else if (c=='\'' && _single) + { + if (_returnQuotes) + _token.append(c); + state=2; + } + else if (c=='\"' && _double) + { + if (_returnQuotes) + _token.append(c); + state=3; + } + else + _token.append(c); + continue; + + + case 2: // Single Quote + _hasToken=true; + if (escape) + { + escape=false; + _token.append(c); + } + else if (c=='\'') + { + if (_returnQuotes) + _token.append(c); + state=1; + } + else if (c=='\\') + { + if (_returnQuotes) + _token.append(c); + escape=true; + } + else + _token.append(c); + continue; + + + case 3: // Double Quote + _hasToken=true; + if (escape) + { + escape=false; + _token.append(c); + } + else if (c=='\"') + { + if (_returnQuotes) + _token.append(c); + state=1; + } + else if (c=='\\') + { + if (_returnQuotes) + _token.append(c); + escape=true; + } + else + _token.append(c); + continue; + } + } + + return _hasToken; + } + + /* ------------------------------------------------------------ */ + public String nextToken() + throws NoSuchElementException + { + if (!hasMoreTokens() || _token==null) + throw new NoSuchElementException(); + String t=_token.toString(); + _token.setLength(0); + _hasToken=false; + return t; + } + + /* ------------------------------------------------------------ */ + public String nextToken(String delim) + throws NoSuchElementException + { + _delim=delim; + _i=_lastStart; + _token.setLength(0); + _hasToken=false; + return nextToken(); + } + + /* ------------------------------------------------------------ */ + public boolean hasMoreElements() + { + return hasMoreTokens(); + } + + /* ------------------------------------------------------------ */ + public Object nextElement() + throws NoSuchElementException + { + return nextToken(); + } + + /* ------------------------------------------------------------ */ + /** Not implemented. + */ + public int countTokens() + { + return -1; + } + + + /* ------------------------------------------------------------ */ + /** Quote a string. + * The string is quoted only if quoting is required due to + * embeded delimiters, quote characters or the + * empty string. + * @param s The string to quote. + * @return quoted string + */ + public static String quote(String s, String delim) + { + if (s==null) + return null; + if (s.length()==0) + return "\"\""; + + + for (int i=0;i=0) + { + StringBuffer b=new StringBuffer(s.length()+8); + quote(b,s); + return b.toString(); + } + } + + return s; + } + + /* ------------------------------------------------------------ */ + /** Quote a string. + * The string is quoted only if quoting is required due to + * embeded delimiters, quote characters or the + * empty string. + * @param s The string to quote. + * @return quoted string + */ + public static String quote(String s) + { + if (s==null) + return null; + if (s.length()==0) + return "\"\""; + + StringBuffer b=new StringBuffer(s.length()+8); + quote(b,s); + return b.toString(); + + } + + + /* ------------------------------------------------------------ */ + /** Quote a string into a StringBuffer. + * The characters ", \, \n, \r, \t, \f and \b are escaped + * @param buf The StringBuffer + * @param s The String to quote. + */ + public static void quote(StringBuffer buf, String s) + { + synchronized(buf) + { + buf.append('"'); + + int i=0; + loop: + for (;i=0) + { + file=new File(dir, + filename.substring(0,i)+ + _fileDateFormat.format(now)+ + filename.substring(i+YYYY_MM_DD.length())); + } + + if (file.exists()&&!file.canWrite()) + throw new IOException("Cannot write log file "+file); + + // Do we need to change the output stream? + if (out==null || !file.equals(_file)) + { + // Yep + _file=file; + if (!_append && file.exists()) + file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now))); + OutputStream oldOut=out; + out=new FileOutputStream(file.toString(),_append); + if (oldOut!=null) + oldOut.close(); + //if(log.isDebugEnabled())log.debug("Opened "+_file); + } + } + + /* ------------------------------------------------------------ */ + private void removeOldFiles() + { + if (_retainDays>0) + { + long now = System.currentTimeMillis(); + + File file= new File(_filename); + File dir = new File(file.getParent()); + String fn=file.getName(); + int s=fn.toLowerCase().indexOf(YYYY_MM_DD); + if (s<0) + return; + String prefix=fn.substring(0,s); + String suffix=fn.substring(s+YYYY_MM_DD.length()); + + String[] logList=dir.list(); + for (int i=0;i=0) + { + File f = new File(dir,fn); + long date = f.lastModified(); + if ( ((now-date)/(1000*60*60*24))>_retainDays) + f.delete(); + } + } + } + } + + /* ------------------------------------------------------------ */ + public void write (byte[] buf) + throws IOException + { + out.write (buf); + } + + /* ------------------------------------------------------------ */ + public void write (byte[] buf, int off, int len) + throws IOException + { + out.write (buf, off, len); + } + + /* ------------------------------------------------------------ */ + /** + */ + public void close() + throws IOException + { + synchronized(RolloverFileOutputStream.class) + { + try{super.close();} + finally + { + out=null; + _file=null; + } + + _rollTask.cancel(); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class RollTask extends TimerTask + { + public void run() + { + try + { + RolloverFileOutputStream.this.setFile(); + RolloverFileOutputStream.this.removeOldFiles(); + + } + catch(IOException e) + { + e.printStackTrace(); + } + } + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java new file mode 100644 index 00000000000..46099adae20 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java @@ -0,0 +1,498 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.util; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.jetty.util.log.Log; + + +/** + * Scanner + * + * Utility for scanning a directory for added, removed and changed + * files and reporting these events via registered Listeners. + * + * TODO AbstractLifeCycle + */ +public class Scanner +{ + private static int __scannerId=0; + private int _scanInterval; + private List _listeners = Collections.synchronizedList(new ArrayList()); + private Map _prevScan = new HashMap(); + private Map _currentScan = new HashMap(); + private FilenameFilter _filter; + private List _scanDirs; + private volatile boolean _running = false; + private boolean _reportExisting = true; + private Timer _timer; + private TimerTask _task; + private boolean _recursive=true; + + + /** + * Listener + * + * Marker for notifications re file changes. + */ + public interface Listener + { + } + + + public interface DiscreteListener extends Listener + { + public void fileChanged (String filename) throws Exception; + public void fileAdded (String filename) throws Exception; + public void fileRemoved (String filename) throws Exception; + } + + + public interface BulkListener extends Listener + { + public void filesChanged (List filenames) throws Exception; + } + + + /** + * + */ + public Scanner () + { + } + + /** + * Get the scan interval + * @return interval between scans in seconds + */ + public int getScanInterval() + { + return _scanInterval; + } + + /** + * Set the scan interval + * @param scanInterval pause between scans in seconds + */ + public synchronized void setScanInterval(int scanInterval) + { + this._scanInterval = scanInterval; + schedule(); + } + + /** + * Set the location of the directory to scan. + * @param dir + * @deprecated use setScanDirs(List dirs) instead + */ + public void setScanDir (File dir) + { + _scanDirs = new ArrayList(); + _scanDirs.add(dir); + } + + /** + * Get the location of the directory to scan + * @return + * @deprecated use getScanDirs() instead + */ + public File getScanDir () + { + return (_scanDirs==null?null:(File)_scanDirs.get(0)); + } + + public void setScanDirs (List dirs) + { + _scanDirs = dirs; + } + + public List getScanDirs () + { + return _scanDirs; + } + + public void setRecursive (boolean recursive) + { + _recursive=recursive; + } + + public boolean getRecursive () + { + return _recursive; + } + /** + * Apply a filter to files found in the scan directory. + * Only files matching the filter will be reported as added/changed/removed. + * @param filter + */ + public void setFilenameFilter (FilenameFilter filter) + { + this._filter = filter; + } + + /** + * Get any filter applied to files in the scan dir. + * @return + */ + public FilenameFilter getFilenameFilter () + { + return _filter; + } + + /** + * Whether or not an initial scan will report all files as being + * added. + * @param reportExisting if true, all files found on initial scan will be + * reported as being added, otherwise not + */ + public void setReportExistingFilesOnStartup (boolean reportExisting) + { + this._reportExisting = reportExisting; + } + + /** + * Add an added/removed/changed listener + * @param listener + */ + public synchronized void addListener (Listener listener) + { + if (listener == null) + return; + _listeners.add(listener); + } + + + + /** + * Remove a registered listener + * @param listener the Listener to be removed + */ + public synchronized void removeListener (Listener listener) + { + if (listener == null) + return; + _listeners.remove(listener); + } + + + /** + * Start the scanning action. + */ + public synchronized void start () + { + if (_running) + return; + + _running = true; + + if (_reportExisting) + { + // if files exist at startup, report them + scan(); + } + else + { + //just register the list of existing files and only report changes + scanFiles(); + _prevScan.putAll(_currentScan); + } + schedule(); + } + + public TimerTask newTimerTask () + { + return new TimerTask() + { + public void run() { scan(); } + }; + } + + public Timer newTimer () + { + return new Timer("Scanner-"+__scannerId++, true); + } + + public void schedule () + { + if (_running) + { + if (_timer!=null) + _timer.cancel(); + if (_task!=null) + _task.cancel(); + if (getScanInterval() > 0) + { + _timer = newTimer(); + _task = newTimerTask(); + _timer.schedule(_task, 1000L*getScanInterval(),1000L*getScanInterval()); + } + } + } + /** + * Stop the scanning. + */ + public synchronized void stop () + { + if (_running) + { + _running = false; + if (_timer!=null) + _timer.cancel(); + if (_task!=null) + _task.cancel(); + _task=null; + _timer=null; + } + } + + /** + * Perform a pass of the scanner and report changes + */ + public void scan () + { + scanFiles(); + reportDifferences(_currentScan, _prevScan); + _prevScan.clear(); + _prevScan.putAll(_currentScan); + } + + /** + * Recursively scan all files in the designated directories. + * @return Map of name of file to last modified time + */ + public void scanFiles () + { + if (_scanDirs==null) + return; + + _currentScan.clear(); + Iterator itor = _scanDirs.iterator(); + while (itor.hasNext()) + { + File dir = (File)itor.next(); + + if ((dir != null) && (dir.exists())) + scanFile(dir, _currentScan); + } + } + + + /** + * Report the adds/changes/removes to the registered listeners + * + * @param currentScan the info from the most recent pass + * @param oldScan info from the previous pass + */ + public void reportDifferences (Map currentScan, Map oldScan) + { + List bulkChanges = new ArrayList(); + + Set oldScanKeys = new HashSet(oldScan.keySet()); + Iterator itor = currentScan.entrySet().iterator(); + while (itor.hasNext()) + { + Map.Entry entry = (Map.Entry)itor.next(); + if (!oldScanKeys.contains(entry.getKey())) + { + Log.debug("File added: "+entry.getKey()); + reportAddition ((String)entry.getKey()); + bulkChanges.add(entry.getKey()); + } + else if (!oldScan.get(entry.getKey()).equals(entry.getValue())) + { + Log.debug("File changed: "+entry.getKey()); + reportChange((String)entry.getKey()); + oldScanKeys.remove(entry.getKey()); + bulkChanges.add(entry.getKey()); + } + else + oldScanKeys.remove(entry.getKey()); + } + + if (!oldScanKeys.isEmpty()) + { + + Iterator keyItor = oldScanKeys.iterator(); + while (keyItor.hasNext()) + { + String filename = (String)keyItor.next(); + Log.debug("File removed: "+filename); + reportRemoval(filename); + bulkChanges.add(filename); + } + } + + if (!bulkChanges.isEmpty()) + reportBulkChanges(bulkChanges); + } + + + /** + * Get last modified time on a single file or recurse if + * the file is a directory. + * @param f file or directory + * @param scanInfoMap map of filenames to last modified times + */ + private void scanFile (File f, Map scanInfoMap) + { + try + { + if (!f.exists()) + return; + + if (f.isFile()) + { + if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName()))) + { + String name = f.getCanonicalPath(); + long lastModified = f.lastModified(); + scanInfoMap.put(name, new Long(lastModified)); + } + } + else if (f.isDirectory() && (_recursive || _scanDirs.contains(f))) + { + File[] files = f.listFiles(); + for (int i=0;i1) + throw new IndexOutOfBoundsException("index "+i); + this.i=i; + } + public void add(Object o){throw new UnsupportedOperationException("SingletonList.add()");} + public boolean hasNext() {return i==0;} + public boolean hasPrevious() {return i==1;} + public Object next() {if (i!=0) throw new NoSuchElementException();i++;return o;} + public int nextIndex() {return i;} + public Object previous() {if (i!=1) throw new NoSuchElementException();i--;return o;} + public int previousIndex() {return i-1;} + public void remove(){throw new UnsupportedOperationException("SingletonList.remove()");} + public void set(Object o){throw new UnsupportedOperationException("SingletonList.add()");} + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java new file mode 100644 index 00000000000..1c57d84b6f2 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java @@ -0,0 +1,682 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.io.Externalizable; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/* ------------------------------------------------------------ */ +/** Map implementation Optimized for Strings keys.. + * This String Map has been optimized for mapping small sets of + * Strings where the most frequently accessed Strings have been put to + * the map first. + * + * It also has the benefit that it can look up entries by substring or + * sections of char and byte arrays. This can prevent many String + * objects from being created just to look up in the map. + * + * This map is NOT synchronized. + * + * + */ +public class StringMap extends AbstractMap implements Externalizable +{ + public static final boolean CASE_INSENSTIVE=true; + protected static final int __HASH_WIDTH=17; + + /* ------------------------------------------------------------ */ + protected int _width=__HASH_WIDTH; + protected Node _root=new Node(); + protected boolean _ignoreCase=false; + protected NullEntry _nullEntry=null; + protected Object _nullValue=null; + protected HashSet _entrySet=new HashSet(3); + protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet); + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public StringMap() + {} + + /* ------------------------------------------------------------ */ + /** Constructor. + * @param ignoreCase + */ + public StringMap(boolean ignoreCase) + { + this(); + _ignoreCase=ignoreCase; + } + + /* ------------------------------------------------------------ */ + /** Constructor. + * @param ignoreCase + * @param width Width of hash tables, larger values are faster but + * use more memory. + */ + public StringMap(boolean ignoreCase,int width) + { + this(); + _ignoreCase=ignoreCase; + _width=width; + } + + /* ------------------------------------------------------------ */ + /** Set the ignoreCase attribute. + * @param ic If true, the map is case insensitive for keys. + */ + public void setIgnoreCase(boolean ic) + { + if (_root._children!=null) + throw new IllegalStateException("Must be set before first put"); + _ignoreCase=ic; + } + + /* ------------------------------------------------------------ */ + public boolean isIgnoreCase() + { + return _ignoreCase; + } + + /* ------------------------------------------------------------ */ + /** Set the hash width. + * @param width Width of hash tables, larger values are faster but + * use more memory. + */ + public void setWidth(int width) + { + _width=width; + } + + /* ------------------------------------------------------------ */ + public int getWidth() + { + return _width; + } + + /* ------------------------------------------------------------ */ + public Object put(Object key, Object value) + { + if (key==null) + return put(null,value); + return put(key.toString(),value); + } + + /* ------------------------------------------------------------ */ + public Object put(String key, Object value) + { + if (key==null) + { + Object oldValue=_nullValue; + _nullValue=value; + if (_nullEntry==null) + { + _nullEntry=new NullEntry(); + _entrySet.add(_nullEntry); + } + return oldValue; + } + + Node node = _root; + int ni=-1; + Node prev = null; + Node parent = null; + + // look for best match + charLoop: + for (int i=0;i0) + node.split(this,ni); + + Object old = node._value; + node._key=key; + node._value=value; + _entrySet.add(node); + return old; + } + return null; + } + + /* ------------------------------------------------------------ */ + public Object get(Object key) + { + if (key==null) + return _nullValue; + if (key instanceof String) + return get((String)key); + return get(key.toString()); + } + + /* ------------------------------------------------------------ */ + public Object get(String key) + { + if (key==null) + return _nullValue; + + Map.Entry entry = getEntry(key,0,key.length()); + if (entry==null) + return null; + return entry.getValue(); + } + + /* ------------------------------------------------------------ */ + /** Get a map entry by substring key. + * @param key String containing the key + * @param offset Offset of the key within the String. + * @param length The length of the key + * @return The Map.Entry for the key or null if the key is not in + * the map. + */ + public Map.Entry getEntry(String key,int offset, int length) + { + if (key==null) + return _nullEntry; + + Node node = _root; + int ni=-1; + + // look for best match + charLoop: + for (int i=0;i0) return null; + + // try next in chain + node=node._next; + } + return null; + } + + if (ni>0) return null; + if (node!=null && node._key==null) + return null; + return node; + } + + /* ------------------------------------------------------------ */ + /** Get a map entry by char array key. + * @param key char array containing the key + * @param offset Offset of the key within the array. + * @param length The length of the key + * @return The Map.Entry for the key or null if the key is not in + * the map. + */ + public Map.Entry getEntry(char[] key,int offset, int length) + { + if (key==null) + return _nullEntry; + + Node node = _root; + int ni=-1; + + // look for best match + charLoop: + for (int i=0;i0) return null; + + // try next in chain + node=node._next; + } + return null; + } + + if (ni>0) return null; + if (node!=null && node._key==null) + return null; + return node; + } + + /* ------------------------------------------------------------ */ + /** Get a map entry by byte array key, using as much of the passed key as needed for a match. + * A simple 8859-1 byte to char mapping is assumed. + * @param key char array containing the key + * @param offset Offset of the key within the array. + * @param maxLength The length of the key + * @return The Map.Entry for the key or null if the key is not in + * the map. + */ + public Map.Entry getBestEntry(byte[] key,int offset, int maxLength) + { + if (key==null) + return _nullEntry; + + Node node = _root; + int ni=-1; + + // look for best match + charLoop: + for (int i=0;i0) + return node; // This is the best match + node=child; + } + + // While we have a node to try + while (node!=null) + { + // If it is a matching node, goto next char + if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c) + { + ni++; + if (ni==node._char.length) + ni=-1; + continue charLoop; + } + + // No char match, so if mid node then no match at all. + if (ni>0) return null; + + // try next in chain + node=node._next; + } + return null; + } + + if (ni>0) return null; + if (node!=null && node._key==null) + return null; + return node; + } + + + /* ------------------------------------------------------------ */ + public Object remove(Object key) + { + if (key==null) + return remove(null); + return remove(key.toString()); + } + + /* ------------------------------------------------------------ */ + public Object remove(String key) + { + if (key==null) + { + Object oldValue=_nullValue; + if (_nullEntry!=null) + { + _entrySet.remove(_nullEntry); + _nullEntry=null; + _nullValue=null; + } + return oldValue; + } + + Node node = _root; + int ni=-1; + + // look for best match + charLoop: + for (int i=0;i0) return null; + + // try next in chain + node=node._next; + } + return null; + } + + if (ni>0) return null; + if (node!=null && node._key==null) + return null; + + Object old = node._value; + _entrySet.remove(node); + node._value=null; + node._key=null; + + return old; + } + + /* ------------------------------------------------------------ */ + public Set entrySet() + { + return _umEntrySet; + } + + /* ------------------------------------------------------------ */ + public int size() + { + return _entrySet.size(); + } + + /* ------------------------------------------------------------ */ + public boolean isEmpty() + { + return _entrySet.isEmpty(); + } + + /* ------------------------------------------------------------ */ + public boolean containsKey(Object key) + { + if (key==null) + return _nullEntry!=null; + return + getEntry(key.toString(),0,key==null?0:key.toString().length())!=null; + } + + /* ------------------------------------------------------------ */ + public void clear() + { + _root=new Node(); + _nullEntry=null; + _nullValue=null; + _entrySet.clear(); + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private static class Node implements Map.Entry + { + char[] _char; + char[] _ochar; + Node _next; + Node[] _children; + String _key; + Object _value; + + Node(){} + + Node(boolean ignoreCase,String s, int offset) + { + int l=s.length()-offset; + _char=new char[l]; + _ochar=new char[l]; + for (int i=0;i0) + { + char c1=s.charAt(i); + if (c1<=127) + { + char c2=lowercases[c1]; + if (c1!=c2) + { + c=s.toCharArray(); + c[i]=c2; + break; + } + } + } + + while (i-->0) + { + if(c[i]<=127) + c[i] = lowercases[c[i]]; + } + + return c==null?s:new String(c); + } + + + /* ------------------------------------------------------------ */ + public static boolean startsWithIgnoreCase(String s,String w) + { + if (w==null) + return true; + + if (s==null || s.length()0;) + { + char c1=s.charAt(--sl); + char c2=w.charAt(i); + if (c1!=c2) + { + if (c1<=127) + c1=lowercases[c1]; + if (c2<=127) + c2=lowercases[c2]; + if (c1!=c2) + return false; + } + } + return true; + } + + /* ------------------------------------------------------------ */ + /** + * returns the next index of a character from the chars string + */ + public static int indexFrom(String s,String chars) + { + for (int i=0;i=0) + return i; + return -1; + } + + /* ------------------------------------------------------------ */ + /** + * replace substrings within string. + */ + public static String replace(String s, String sub, String with) + { + int c=0; + int i=s.indexOf(sub,c); + if (i == -1) + return s; + + StringBuilder buf = new StringBuilder(s.length()+with.length()); + + do + { + buf.append(s.substring(c,i)); + buf.append(with); + c=i+sub.length(); + } while ((i=s.indexOf(sub,c))!=-1); + + if (c=s.length()) + break; + buf.append(s.charAt(i)); + } + } + } + + + /* ------------------------------------------------------------ */ + /** + * append hex digit + * + */ + public static void append(StringBuilder buf,byte b,int base) + { + int bi=0xff&b; + int c='0'+(bi/base)%base; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + c='0'+bi%base; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + } + + /* ------------------------------------------------------------ */ + public static void append2digits(StringBuffer buf,int i) + { + if (i<100) + { + buf.append((char)(i/10+'0')); + buf.append((char)(i%10+'0')); + } + } + + /* ------------------------------------------------------------ */ + public static void append2digits(StringBuilder buf,int i) + { + if (i<100) + { + buf.append((char)(i/10+'0')); + buf.append((char)(i%10+'0')); + } + } + + /* ------------------------------------------------------------ */ + /** Return a non null string. + * @param s String + * @return The string passed in or empty string if it is null. + */ + public static String nonNull(String s) + { + if (s==null) + return ""; + return s; + } + + /* ------------------------------------------------------------ */ + public static boolean equals(String s,char[] buf, int offset, int length) + { + if (s.length()!=length) + return false; + for (int i=0;i=0 && i=0 && i=0 && i=0 && i=base || digit>=10) + { + digit=10+c-'A'; + if (digit<10 || digit>=base) + digit=10+c-'a'; + } + if (digit<0 || digit>=base) + throw new NumberFormatException(s.substring(offset,offset+length)); + value=value*base+digit; + } + return value; + } + + /* ------------------------------------------------------------ */ + /** Parse an int from a byte array of ascii characters. + * Negative numbers are not handled. + * @param b byte array + * @param offset Offset within string + * @param length Length of integer or -1 for remainder of string + * @param base base of the integer + * @exception NumberFormatException + */ + public static int parseInt(byte[] b, int offset, int length, int base) + throws NumberFormatException + { + int value=0; + + if (length<0) + length=b.length-offset; + + for (int i=0;i=base || digit>=10) + { + digit=10+c-'A'; + if (digit<10 || digit>=base) + digit=10+c-'a'; + } + if (digit<0 || digit>=base) + throw new NumberFormatException(new String(b,offset,length)); + value=value*base+digit; + } + return value; + } + + /* ------------------------------------------------------------ */ + public static byte[] parseBytes(String s, int base) + { + byte[] bytes=new byte[s.length()/2]; + for (int i=0;i'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + c='0'+bi%base; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + } + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param b An ASCII encoded character 0-9 a-f A-F + * @return The byte value of the character 0-16. + */ + public static byte convertHexDigit( byte b ) + { + if ((b >= '0') && (b <= '9')) return (byte)(b - '0'); + if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10); + if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10); + return 0; + } + + /* ------------------------------------------------------------ */ + public static String toHexString(byte[] b) + { + StringBuilder buf = new StringBuilder(); + for (int i=0;i'9') + c= 'A'+(c-'0'-10); + buf.append((char)c); + c='0'+bi%16; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + } + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + public static String toHexString(byte[] b,int offset,int length) + { + StringBuilder buf = new StringBuilder(); + for (int i=offset;i'9') + c= 'A'+(c-'0'-10); + buf.append((char)c); + c='0'+bi%16; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + } + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + public static byte[] fromHexString(String s) + { + if (s.length()%2!=0) + throw new IllegalArgumentException(s); + byte[] array = new byte[s.length()/2]; + for (int i=0;i=buf.length) + { + byte[] old_buf=buf; + buf=new byte[old_buf.length+256]; + System.arraycopy(old_buf, 0, buf, 0, old_buf.length); + } + buf[i++]=(byte)ch; + } + + if (ch==-1 && i==0) + return null; + + // skip a trailing LF if it exists + if (ch==CR && in.available()>=1 && in.markSupported()) + { + in.mark(1); + ch=in.read(); + if (ch!=LF) + in.reset(); + } + + byte[] old_buf=buf; + buf=new byte[i]; + System.arraycopy(old_buf, 0, buf, 0, i); + + return buf; + } + + public static URL jarFor(String className) + { + try + { + className=className.replace('.','/')+".class"; + // hack to discover jstl libraries + URL url = Loader.getResource(null,className,false); + String s=url.toString(); + if (s.startsWith("jar:file:")) + return new URL(s.substring(4,s.indexOf("!/"))); + } + catch(Exception e) + { + Log.ignore(e); + } + return null; + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java new file mode 100644 index 00000000000..dcfa966f1cf --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -0,0 +1,595 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.io.UnsupportedEncodingException; + + + +/* ------------------------------------------------------------ */ +/** URI Holder. + * This class assists with the decoding and encoding or HTTP URI's. + * It differs from the java.net.URL class as it does not provide + * communications ability, but it does assist with query string + * formatting. + *

    UTF-8 encoding is used by default for % encoded characters. This + * may be overridden with the org.eclipse.util.URI.charset system property. + * @see UrlEncoded + * + */ +public class URIUtil + implements Cloneable +{ + public static final String SLASH="/"; + public static final String HTTP="http"; + public static final String HTTP_COLON="http:"; + public static final String HTTPS="https"; + public static final String HTTPS_COLON="https:"; + + // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars + public static final String __CHARSET=System.getProperty("org.eclipse.util.URI.charset",StringUtil.__UTF8); + + private URIUtil() + {} + + /* ------------------------------------------------------------ */ + /** Encode a URI path. + * This is the same encoding offered by URLEncoder, except that + * the '/' character is not encoded. + * @param path The path the encode + * @return The encoded path + */ + public static String encodePath(String path) + { + if (path==null || path.length()==0) + return path; + + StringBuilder buf = encodePath(null,path); + return buf==null?path:buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** Encode a URI path. + * @param path The path the encode + * @param buf StringBuilder to encode path into (or null) + * @return The StringBuilder or null if no substitutions required. + */ + public static StringBuilder encodePath(StringBuilder buf, String path) + { + if (buf==null) + { + loop: + for (int i=0;i=0) + { + buf=new StringBuilder(path.length()<<1); + break loop; + } + } + if (buf==null) + return null; + } + + synchronized(buf) + { + for (int i=0;i=0) + { + buf.append('%'); + StringUtil.append(buf,(byte)(0xff&c),16); + } + else + buf.append(c); + } + } + + return buf; + } + + /* ------------------------------------------------------------ */ + /* Decode a URI path. + * @param path The path the encode + * @param buf StringBuilder to encode path into + */ + public static String decodePath(String path) + { + if (path==null) + return null; + char[] chars=null; + int n=0; + byte[] bytes=null; + int b=0; + + int len=path.length(); + + for (int i=0;i0) + { + String s; + try + { + s=new String(bytes,0,b,__CHARSET); + } + catch (UnsupportedEncodingException e) + { + s=new String(bytes,0,b); + } + s.getChars(0,s.length(),chars,n); + n+=s.length(); + b=0; + } + + chars[n++]=c; + } + + if (chars==null) + return path; + + if (b>0) + { + String s; + try + { + s=new String(bytes,0,b,__CHARSET); + } + catch (UnsupportedEncodingException e) + { + s=new String(bytes,0,b); + } + s.getChars(0,s.length(),chars,n); + n+=s.length(); + } + + return new String(chars,0,n); + } + + /* ------------------------------------------------------------ */ + /* Decode a URI path. + * @param path The path the encode + * @param buf StringBuilder to encode path into + */ + public static String decodePath(byte[] buf, int offset, int length) + { + byte[] bytes=null; + int n=0; + + for (int i=0;i=0) + return p.substring(0,slash+1); + return null; + } + + /* ------------------------------------------------------------ */ + /** Strip parameters from a path. + * Return path upto any semicolon parameters. + */ + public static String stripPath(String path) + { + if (path==null) + return null; + int semi=path.indexOf(';'); + if (semi<0) + return path; + return path.substring(0,semi); + } + + /* ------------------------------------------------------------ */ + /** Convert a path to a cananonical form. + * All instances of "." and ".." are factored out. Null is returned + * if the path tries to .. above its root. + * @param path + * @return path or null. + */ + public static String canonicalPath(String path) + { + if (path==null || path.length()==0) + return path; + + int end=path.length(); + int queryIdx=path.indexOf('?'); + int start = path.lastIndexOf('/', (queryIdx > 0 ? queryIdx : end)); + + search: + while (end>0) + { + switch(end-start) + { + case 2: // possible single dot + if (path.charAt(start+1)!='.') + break; + break search; + case 3: // possible double dot + if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.') + break; + break search; + } + + end=start; + start=path.lastIndexOf('/',end-1); + } + + // If we have checked the entire string + if (start>=end) + return path; + + StringBuilder buf = new StringBuilder(path); + int delStart=-1; + int delEnd=-1; + int skip=0; + + while (end>0) + { + switch(end-start) + { + case 2: // possible single dot + if (buf.charAt(start+1)!='.') + { + if (skip>0 && --skip==0) + { + delStart=start>=0?start:0; + if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + break; + } + + if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/') + break; + + if(delEnd<0) + delEnd=end; + delStart=start; + if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/') + { + delStart++; + if (delEnd=0 && buf.charAt(start)!='/') + start--; + continue; + + case 3: // possible double dot + if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.') + { + if (skip>0 && --skip==0) + { delStart=start>=0?start:0; + if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + break; + } + + delStart=start; + if (delEnd<0) + delEnd=end; + + skip++; + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + continue; + + default: + if (skip>0 && --skip==0) + { + delStart=start>=0?start:0; + if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + } + + // Do the delete + if (skip<=0 && delStart>=0 && delStart>=0) + { + buf.delete(delStart,delEnd); + delStart=delEnd=-1; + if (skip>0) + delEnd=end; + } + + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + } + + // Too many .. + if (skip>0) + return null; + + // Do the delete + if (delEnd>=0) + buf.delete(delStart,delEnd); + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** Convert a path to a compact form. + * All instances of "//" and "///" etc. are factored out to single "/" + * @param path + * @return path + */ + public static String compactPath(String path) + { + if (path==null || path.length()==0) + return path; + + int state=0; + int end=path.length(); + int i=0; + + loop: + while (i='a'&&c<='z' || + c>='A'&&c<='Z' || + (i>0 &&(c>='0'&&c<='9' || + c=='.' || + c=='+' || + c=='-')) + )) + break; + } + return false; + } + +} + + + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java new file mode 100644 index 00000000000..a7d647c4dec --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java @@ -0,0 +1,865 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.Iterator; +import java.util.Map; + + +/* ------------------------------------------------------------ */ +/** Handles coding of MIME "x-www-form-urlencoded". + * This class handles the encoding and decoding for either + * the query string of a URL or the _content of a POST HTTP request. + * + *

    Notes

    + * The hashtable either contains String single values, vectors + * of String or arrays of Strings. + * + * This class is only partially synchronised. In particular, simple + * get operations are not protected from concurrent updates. + * + * @see java.net.URLEncoder + * + */ +public class UrlEncoded extends MultiMap +{ + + /* ----------------------------------------------------------------- */ + public UrlEncoded(UrlEncoded url) + { + super(url); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded() + { + super(6); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded(String s) + { + super(6); + decode(s,StringUtil.__UTF8); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded(String s, String charset) + { + super(6); + decode(s,charset); + } + + /* ----------------------------------------------------------------- */ + public void decode(String query) + { + decodeTo(query,this,StringUtil.__UTF8); + } + + /* ----------------------------------------------------------------- */ + public void decode(String query,String charset) + { + decodeTo(query,this,charset); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + */ + public String encode() + { + return encode(StringUtil.__UTF8,false); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + */ + public String encode(String charset) + { + return encode(charset,false); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + * @param equalsForNullValue if True, then an '=' is always used, even + * for parameters without a value. e.g. "blah?a=&b=&c=". + */ + public synchronized String encode(String charset, boolean equalsForNullValue) + { + return encode(this,charset,equalsForNullValue); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + * @param equalsForNullValue if True, then an '=' is always used, even + * for parameters without a value. e.g. "blah?a=&b=&c=". + */ + public static String encode(MultiMap map, String charset, boolean equalsForNullValue) + { + if (charset==null) + charset=StringUtil.__UTF8; + + StringBuilder result = new StringBuilder(128); + + Iterator iter = map.entrySet().iterator(); + while(iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + + String key = entry.getKey().toString(); + Object list = entry.getValue(); + int s=LazyList.size(list); + + if (s==0) + { + result.append(encodeString(key,charset)); + if(equalsForNullValue) + result.append('='); + } + else + { + for (int i=0;i0) + result.append('&'); + Object val=LazyList.get(list,i); + result.append(encodeString(key,charset)); + + if (val!=null) + { + String str=val.toString(); + if (str.length()>0) + { + result.append('='); + result.append(encodeString(str,charset)); + } + else if (equalsForNullValue) + result.append('='); + } + else if (equalsForNullValue) + result.append('='); + } + } + if (iter.hasNext()) + result.append('&'); + } + return result.toString(); + } + + + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param content the string containing the encoded parameters + */ + public static void decodeTo(String content, MultiMap map, String charset) + { + if (charset==null) + charset=StringUtil.__UTF8; + + synchronized(map) + { + String key = null; + String value = null; + int mark=-1; + boolean encoded=false; + for (int i=0;i0) + { + map.add(value,""); + } + key = null; + value=null; + break; + case '=': + if (key!=null) + break; + key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i); + mark=i; + encoded=false; + break; + case '+': + encoded=true; + break; + case '%': + encoded=true; + break; + } + } + + if (key != null) + { + int l=content.length()-mark-1; + value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1)); + map.add(key,value); + } + else if (mark0) + { + map.add(value,""); + } + key = null; + value=null; + break; + + case '=': + if (key!=null) + { + buffer.append(b); + break; + } + key = buffer.toString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + if (i+20) + { + map.add(buffer.toString(),""); + } + } + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in InputSteam to read + * @param map MultiMap to add parameters to + * @param maxLength maximum length of content to read 0r -1 for no limit + */ + public static void decode88591To(InputStream in, MultiMap map, int maxLength) + throws IOException + { + synchronized(map) + { + StringBuffer buffer = new StringBuffer(); + String key = null; + String value = null; + + int b; + + // TODO cache of parameter names ??? + int totalLength=0; + while ((b=in.read())>=0) + { + switch ((char) b) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.setLength(0); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + break; + + case '=': + if (key!=null) + { + buffer.append((char)b); + break; + } + key = buffer.toString(); + buffer.setLength(0); + break; + + case '+': + buffer.append((char)' '); + break; + + case '%': + int dh=in.read(); + int dl=in.read(); + if (dh<0||dl<0) + break; + buffer.append((char)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl))); + break; + default: + buffer.append((char)b); + break; + } + if (maxLength>=0 && (++totalLength > maxLength)) + throw new IllegalStateException("Form too large"); + } + + if (key != null) + { + value = buffer.length()==0?"":buffer.toString(); + buffer.setLength(0); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toString(), ""); + } + } + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in InputSteam to read + * @param map MultiMap to add parameters to + * @param maxLength maximum length of conent to read 0r -1 for no limit + */ + public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength) + throws IOException + { + synchronized(map) + { + Utf8StringBuilder buffer = new Utf8StringBuilder(); + String key = null; + String value = null; + + int b; + + // TODO cache of parameter names ??? + int totalLength=0; + while ((b=in.read())>=0) + { + switch ((char) b) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + break; + + case '=': + if (key!=null) + { + buffer.append((byte)b); + break; + } + key = buffer.toString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + int dh=in.read(); + int dl=in.read(); + if (dh<0||dl<0) + break; + buffer.append((byte)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl))); + break; + default: + buffer.append((byte)b); + break; + } + if (maxLength>=0 && (++totalLength > maxLength)) + throw new IllegalStateException("Form too large"); + } + + if (key != null) + { + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toString(), ""); + } + } + } + + /* -------------------------------------------------------------- */ + public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength) throws IOException + { + InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16); + StringBuffer buf = new StringBuffer(); + + int c; + int length=0; + if (maxLength<0) + maxLength=Integer.MAX_VALUE; + while ((c=input.read())>0 && length++0) + { + switch ((char) c) + { + case '&': + size=output.size(); + value = size==0?"":output.toString(charset); + output.setCount(0); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + break; + case '=': + if (key!=null) + { + output.write(c); + break; + } + size=output.size(); + key = size==0?"":output.toString(charset); + output.setCount(0); + break; + case '+': + output.write(' '); + break; + case '%': + digits=2; + break; + default: + if (digits==2) + { + digit=TypeUtil.convertHexDigit((byte)c); + digits=1; + } + else if (digits==1) + { + output.write((digit<<4) + TypeUtil.convertHexDigit((byte)c)); + digits=0; + } + else + output.write(c); + break; + } + + totalLength++; + if (maxLength>=0 && totalLength > maxLength) + throw new IllegalStateException("Form too large"); + } + + size=output.size(); + if (key != null) + { + value = size==0?"":output.toString(charset); + output.setCount(0); + map.add(key,value); + } + else if (size>0) + map.add(output.toString(charset),""); + } + } + + /* -------------------------------------------------------------- */ + /** Decode String with % encoding. + * This method makes the assumption that the majority of calls + * will need no decoding. + */ + public static String decodeString(String encoded,int offset,int length,String charset) + { + if (charset==null || StringUtil.isUTF8(charset)) + { + Utf8StringBuffer buffer=null; + + for (int i=0;i0xff) + { + if (buffer==null) + { + buffer=new Utf8StringBuffer(length); + buffer.getStringBuffer().append(encoded,offset,offset+i+1); + } + else + buffer.getStringBuffer().append(c); + } + else if (c=='+') + { + if (buffer==null) + { + buffer=new Utf8StringBuffer(length); + buffer.getStringBuffer().append(encoded,offset,offset+i); + } + + buffer.getStringBuffer().append(' '); + } + else if (c=='%' && (i+2)0xff) + { + if (buffer==null) + { + buffer=new StringBuffer(length); + buffer.append(encoded,offset,offset+i+1); + } + else + buffer.append(c); + } + else if (c=='+') + { + if (buffer==null) + { + buffer=new StringBuffer(length); + buffer.append(encoded,offset,offset+i); + } + + buffer.append(' '); + } + else if (c=='%' && (i+2)=0 && c<=0xff) + { + if (c=='%') + { + if(i+2=length) + break; + c = encoded.charAt(offset+i); + } + + i--; + buffer.append(new String(ba,0,n,charset)); + + } + else if (buffer!=null) + buffer.append(c); + } + + if (buffer==null) + { + if (offset==0 && encoded.length()==length) + return encoded; + return encoded.substring(offset,offset+length); + } + + return buffer.toString(); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + } + + /* ------------------------------------------------------------ */ + /** Perform URL encoding. + * Assumes 8859 charset + * @param string + * @return encoded string. + */ + public static String encodeString(String string) + { + return encodeString(string,StringUtil.__UTF8); + } + + /* ------------------------------------------------------------ */ + /** Perform URL encoding. + * @param string + * @return encoded string. + */ + public static String encodeString(String string,String charset) + { + if (charset==null) + charset=StringUtil.__UTF8; + byte[] bytes=null; + try + { + bytes=string.getBytes(charset); + } + catch(UnsupportedEncodingException e) + { + // Log.warn(LogSupport.EXCEPTION,e); + bytes=string.getBytes(); + } + + int len=bytes.length; + byte[] encoded= new byte[bytes.length*3]; + int n=0; + boolean noEncode=true; + + for (int i=0;i='a' && b<='z' || + b>='A' && b<='Z' || + b>='0' && b<='9') + { + encoded[n++]=b; + } + else + { + noEncode=false; + encoded[n++]=(byte)'%'; + byte nibble= (byte) ((b&0xf0)>>4); + if (nibble>=10) + encoded[n++]=(byte)('A'+nibble-10); + else + encoded[n++]=(byte)('0'+nibble); + nibble= (byte) (b&0xf); + if (nibble>=10) + encoded[n++]=(byte)('A'+nibble-10); + else + encoded[n++]=(byte)('0'+nibble); + } + } + + if (noEncode) + return string; + + try + { + return new String(encoded,0,n,charset); + } + catch(UnsupportedEncodingException e) + { + // Log.warn(LogSupport.EXCEPTION,e); + return new String(encoded,0,n); + } + } + + + /* ------------------------------------------------------------ */ + /** + */ + public Object clone() + { + return new UrlEncoded(this); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java new file mode 100644 index 00000000000..421bf194d41 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java @@ -0,0 +1,161 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +/* ------------------------------------------------------------ */ +/** UTF-8 StringBuffer. + * + * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append + * UTF-8 encoded bytes, that are converted into characters. + * + * This class is stateful and up to 6 calls to {@link #append(byte)} may be needed before + * state a character is appended to the string buffer. + * + * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. + * The UTF-8 code was inspired by http://javolution.org + * + * This class is not synchronised and should probably be called Utf8StringBuilder + */ +public class Utf8StringBuffer +{ + StringBuffer _buffer; + int _more; + int _bits; + + public Utf8StringBuffer() + { + _buffer=new StringBuffer(); + } + + public Utf8StringBuffer(int capacity) + { + _buffer=new StringBuffer(capacity); + } + + public void append(byte[] b,int offset, int length) + { + int end=offset+length; + for (int i=offset; i=0) + { + if (_more>0) + { + _buffer.append('?'); + _more=0; + _bits=0; + } + else + _buffer.append((char)(0x7f&b)); + } + else if (_more==0) + { + if ((b&0xc0)!=0xc0) + { + // 10xxxxxx + _buffer.append('?'); + _more=0; + _bits=0; + } + else + + { + if ((b & 0xe0) == 0xc0) + { + //110xxxxx + _more=1; + _bits=b&0x1f; + } + else if ((b & 0xf0) == 0xe0) + { + //1110xxxx + _more=2; + _bits=b&0x0f; + } + else if ((b & 0xf8) == 0xf0) + { + //11110xxx + _more=3; + _bits=b&0x07; + } + else if ((b & 0xfc) == 0xf8) + { + //111110xx + _more=4; + _bits=b&0x03; + } + else if ((b & 0xfe) == 0xfc) + { + //1111110x + _more=5; + _bits=b&0x01; + } + else + { + throw new IllegalArgumentException(); + } + + if (_bits==0) + throw new IllegalArgumentException("non-shortest UTF-8 form"); + } + } + else + { + if ((b&0xc0)==0xc0) + { // 11?????? + _buffer.append('?'); + _more=0; + _bits=0; + throw new IllegalArgumentException(); + } + else + { + // 10xxxxxx + _bits=(_bits<<6)|(b&0x3f); + if (--_more==0) + _buffer.append((char)_bits); + } + } + } + + public int length() + { + return _buffer.length(); + } + + public void reset() + { + _buffer.setLength(0); + _more=0; + _bits=0; + } + + public StringBuffer getStringBuffer() + { + if (_more!=0) + throw new IllegalStateException(); + return _buffer; + } + + public String toString() + { + if (_more!=0) + throw new IllegalStateException(); + return _buffer.toString(); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java new file mode 100644 index 00000000000..1cfe1f55388 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java @@ -0,0 +1,160 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +/* ------------------------------------------------------------ */ +/** UTF-8 StringBuilder. + * + * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append + * UTF-8 encoded bytes, that are converted into characters. + * + * This class is stateful and up to 6 calls to {@link #append(byte)} may be needed before + * state a character is appended to the string buffer. + * + * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. + * The UTF-8 code was inspired by http://javolution.org + * + */ +public class Utf8StringBuilder +{ + StringBuilder _buffer; + int _more; + int _bits; + + public Utf8StringBuilder() + { + _buffer=new StringBuilder(); + } + + public Utf8StringBuilder(int capacity) + { + _buffer=new StringBuilder(capacity); + } + + public void append(byte[] b,int offset, int length) + { + int end=offset+length; + for (int i=offset; i=0) + { + if (_more>0) + { + _buffer.append('?'); + _more=0; + _bits=0; + } + else + _buffer.append((char)(0x7f&b)); + } + else if (_more==0) + { + if ((b&0xc0)!=0xc0) + { + // 10xxxxxx + _buffer.append('?'); + _more=0; + _bits=0; + } + else + + { + if ((b & 0xe0) == 0xc0) + { + //110xxxxx + _more=1; + _bits=b&0x1f; + } + else if ((b & 0xf0) == 0xe0) + { + //1110xxxx + _more=2; + _bits=b&0x0f; + } + else if ((b & 0xf8) == 0xf0) + { + //11110xxx + _more=3; + _bits=b&0x07; + } + else if ((b & 0xfc) == 0xf8) + { + //111110xx + _more=4; + _bits=b&0x03; + } + else if ((b & 0xfe) == 0xfc) + { + //1111110x + _more=5; + _bits=b&0x01; + } + else + { + throw new IllegalArgumentException(); + } + + if (_bits==0) + throw new IllegalArgumentException("non-shortest UTF-8 form"); + } + } + else + { + if ((b&0xc0)==0xc0) + { // 11?????? + _buffer.append('?'); + _more=0; + _bits=0; + throw new IllegalArgumentException(); + } + else + { + // 10xxxxxx + _bits=(_bits<<6)|(b&0x3f); + if (--_more==0) + _buffer.append((char)_bits); + } + } + } + + public int length() + { + return _buffer.length(); + } + + public void reset() + { + _buffer.setLength(0); + _more=0; + _bits=0; + } + + public StringBuilder getStringBuilder() + { + if (_more!=0) + throw new IllegalStateException(); + return _buffer; + } + + public String toString() + { + if (_more!=0) + throw new IllegalStateException(); + return _buffer.toString(); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/Continuation.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/Continuation.java new file mode 100644 index 00000000000..16ca3aad89a --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/Continuation.java @@ -0,0 +1,117 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.ajax; + +import javax.servlet.ServletRequest; + + +/* ------------------------------------------------------------ */ +/** Continuation. + * + * A continuation is a mechanism by which a HTTP Request can be + * suspended and restarted after a timeout or an asynchronous event + * has occured. + * Blocking continuations will block the process of the request during a + * call to {@link #suspend(long)}. + * Non-blocking continuation can abort the current request and arrange for it + * to be retried when {@link #resume()} is called or the timeout expires. + * + * In order to supprt non-blocking continuations, it is important that + * all actions taken by a filter or servlet before a call to + * {@link #suspend(long)} are either idempotent (can be retried) or + * are made conditional on {@link #isPending} so they are not performed on + * retried requests. + * + * With the appropriate HTTP Connector, this allows threadless waiting + * for events (see {@link org.eclipse.jetty.nio.SelectChannelConnector}). + * + * + * @deprecated use {@link ServletRequest#suspend()} + * + */ +public interface Continuation +{ + + /* ------------------------------------------------------------ */ + /** Suspend handling. + * This method will suspend the request for the timeout or until resume is + * called. + * @param timeout. A timeout of < 0 will cause an immediate return. I timeout of 0 will wait indefinitely. + * @return True if resume called or false if timeout. + */ + public boolean suspend(long timeout); + + /* ------------------------------------------------------------ */ + /** Resume the request. + * Resume a suspended request. The passed event will be returned in the getObject method. + */ + public void resume(); + + + /* ------------------------------------------------------------ */ + /** Reset the continuation. + * Cancel any pending status of the continuation. + */ + public void reset(); + + /* ------------------------------------------------------------ */ + /** Is this a newly created Continuation. + *

    + * A newly created continuation has not had {@link #getEvent(long)} called on it. + *

    + * @return True if the continuation has just been created and has not yet suspended the request. + */ + public boolean isNew(); + + /* ------------------------------------------------------------ */ + /** Get the pending status? + * A continuation is pending while the handling of a call to suspend has not completed. + * For blocking continuations, pending is true only during the call to {@link #suspend(long)}. + * For non-blocking continuations, pending is true until a second call to {@link #suspend(long)} or + * a call to {@link #reset()}, thus this method can be used to determine if a request is being + * retried. + * @return True if the continuation is handling a call to suspend. + */ + public boolean isPending(); + + /* ------------------------------------------------------------ */ + /** Get the resumed status? + * @return True if the continuation is has been resumed. + */ + public boolean isResumed(); + + /* ------------------------------------------------------------ */ + /** Get the resumed status? + * @return True if the continuation is has been expired. + */ + public boolean isExpired(); + + /* ------------------------------------------------------------ */ + /** Arbitrary object associated with the continuation for context. + * @return An arbitrary object associated with the continuation + */ + public Object getObject(); + + /* ------------------------------------------------------------ */ + /** Arbitrary object associated with the continuation for context. + * @param o An arbitrary object to associate with the continuation + */ + public void setObject(Object o); + + /* ------------------------------------------------------------ */ + /** + */ + public void setMutex(Object mutex); + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/ContinuationSupport.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/ContinuationSupport.java new file mode 100644 index 00000000000..07b23807efe --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/ContinuationSupport.java @@ -0,0 +1,35 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.ajax; + +import javax.servlet.http.HttpServletRequest; + +/* ------------------------------------------------------------ */ +/** ContinuationSupport. + * Conveniance class to avoid classloading visibility issues. + * + * + */ +public class ContinuationSupport +{ + public static Continuation getContinuation(HttpServletRequest request, Object mutex) + { + Continuation continuation = (Continuation) request.getAttribute("org.eclipse.jetty.ajax.Continuation"); + if (continuation==null) + continuation=new WaitingContinuation(mutex); + else + continuation.setMutex(mutex); + return continuation; + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java new file mode 100644 index 00000000000..be9a2b34fec --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java @@ -0,0 +1,1379 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.ajax; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + +/** JSON Parser and Generator. + * + *

    This class provides some static methods to convert POJOs to and from JSON + * notation. The mapping from JSON to java is:

    + *   object ==> Map
    + *   array  ==> Object[]
    + *   number ==> Double or Long
    + *   string ==> String
    + *   null   ==> null
    + *   bool   ==> Boolean
    + * 
    + *

    + * The java to JSON mapping is:

    + *   String --> string
    + *   Number --> number
    + *   Map    --> object
    + *   List   --> array
    + *   Array  --> array
    + *   null   --> null
    + *   Boolean--> boolean
    + *   Object --> string (dubious!)
    + * 
    + *

    + * The interface {@link JSON.Convertible} may be implemented by classes that wish to externalize and + * initialize specific fields to and from JSON objects. Only directed acyclic graphs of objects are supported. + *

    + *

    + * The interface {@link JSON.Generator} may be implemented by classes that know how to render themselves as JSON and + * the {@link #toString(Object)} method will use {@link JSON.Generator#addJSON(StringBuffer)} to generate the JSON. + * The class {@link JSON.Literal} may be used to hold pre-gnerated JSON object. + *

    + * The interface {@link Convertor} may be implemented to provide static convertors for objects that may be registered + * with {@link #registerConvertor(Class, org.eclipse.util.ajax.JSON.Convertor)}. These convertors are looked up by class, interface and + * super class by {@link #getConvertor(Class)}. + *

    + * + * + */ +public class JSON +{ + private static JSON __default = new JSON(); + + private Map _convertors=new HashMap(); + private int _stringBufferSize=256; + + + public JSON() + { + } + + /* ------------------------------------------------------------ */ + /** + * @return the initial stringBuffer size to use when creating JSON strings (default 256) + */ + public int getStringBufferSize() + { + return _stringBufferSize; + } + + + + /* ------------------------------------------------------------ */ + /** + * @param stringBufferSize the initial stringBuffer size to use when creating JSON strings (default 256) + */ + public void setStringBufferSize(int stringBufferSize) + { + _stringBufferSize=stringBufferSize; + } + + + + + /** + * Register a {@link Convertor} for a class or interface. + * @param forClass The class or interface that the convertor applies to + * @param convertor the convertor + */ + public static void registerConvertor(Class forClass, Convertor convertor) + { + __default.addConvertor(forClass,convertor); + } + + public static JSON getDefault() + { + return __default; + } + + public static void setDefault(JSON json) + { + __default=json; + } + + public static String toString(Object object) + { + StringBuffer buffer=new StringBuffer(__default.getStringBufferSize()); + synchronized (buffer) + { + __default.append(buffer,object); + return buffer.toString(); + } + } + + public static String toString(Map object) + { + StringBuffer buffer=new StringBuffer(__default.getStringBufferSize()); + synchronized (buffer) + { + __default.appendMap(buffer,object); + return buffer.toString(); + } + } + + public static String toString(Object[] array) + { + StringBuffer buffer=new StringBuffer(__default.getStringBufferSize()); + synchronized (buffer) + { + __default.appendArray(buffer,array); + return buffer.toString(); + } + } + + /** + * @param s String containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(String s) + { + return __default.parse(new StringSource(s),false); + } + + /** + * @param s String containing JSON object or array. + * @param stripOuterComment If true, an outer comment around the JSON is ignored. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(String s, boolean stripOuterComment) + { + return __default.parse(new StringSource(s),stripOuterComment); + } + + /** + * @param in Reader containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(Reader in) throws IOException + { + return __default.parse(new ReaderSource(in),false); + } + + /** + * @param s Stream containing JSON object or array. + * @param stripOuterComment If true, an outer comment around the JSON is ignored. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(Reader in, boolean stripOuterComment) throws IOException + { + return __default.parse(new ReaderSource(in),stripOuterComment); + } + + /** + * @deprecated use {@link #parse(Reader)} + * @param in Reader containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(InputStream in) throws IOException + { + return __default.parse(new StringSource(IO.toString(in)),false); + } + + /** + * @deprecated use {@link #parse(Reader, boolean)} + * @param s Stream containing JSON object or array. + * @param stripOuterComment If true, an outer comment around the JSON is ignored. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(InputStream in, boolean stripOuterComment) throws IOException + { + return __default.parse(new StringSource(IO.toString(in)),stripOuterComment); + } + + /* ------------------------------------------------------------ */ + /** Convert Object to JSON + * @param object The object to convert + * @return The JSON String + */ + public String toJSON(Object object) + { + StringBuffer buffer=new StringBuffer(getStringBufferSize()); + synchronized (buffer) + { + append(buffer,object); + return buffer.toString(); + } + } + + /* ------------------------------------------------------------ */ + /** Convert JSON to Object + * @param json The json to convert + * @return The object + */ + public Object fromJSON(String json) + { + Source source = new StringSource(json); + return parse(source); + } + + /** + * Append object as JSON to string buffer. + * @param buffer + * @param object + */ + public void append(StringBuffer buffer, Object object) + { + if (object==null) + buffer.append("null"); + else if (object instanceof Convertible) + appendJSON(buffer,(Convertible)object); + else if (object instanceof Generator) + appendJSON(buffer,(Generator)object); + else if (object instanceof Map) + appendMap(buffer,(Map)object); + else if (object instanceof Collection) + appendArray(buffer,(Collection)object); + else if (object.getClass().isArray()) + appendArray(buffer,object); + else if (object instanceof Number) + appendNumber(buffer,(Number)object); + else if (object instanceof Boolean) + appendBoolean(buffer,(Boolean)object); + else if (object instanceof String) + appendString(buffer,(String)object); + else + { + Convertor convertor=getConvertor(object.getClass()); + if (convertor!=null) + appendJSON(buffer,convertor,object); + else + appendString(buffer,object.toString()); + } + } + + public void appendNull(StringBuffer buffer) + { + buffer.append("null"); + } + + public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object) + { + appendJSON(buffer,new Convertible() + { + public void fromJSON(Map object) + { + } + + public void toJSON(Output out) + { + convertor.toJSON(object,out); + } + }); + } + + public void appendJSON(final StringBuffer buffer, Convertible converter) + { + final char[] c= + { '{' }; + converter.toJSON(new Output() + { + public void add(Object obj) + { + if (c[0]==0) + throw new IllegalStateException(); + append(buffer,obj); + c[0]=0; + } + + public void addClass(Class type) + { + if (c[0]==0) + throw new IllegalStateException(); + buffer.append(c); + buffer.append("\"class\":"); + append(buffer,type.getName()); + c[0]=','; + } + + public void add(String name, Object value) + { + if (c[0]==0) + throw new IllegalStateException(); + buffer.append(c); + QuotedStringTokenizer.quote(buffer,name); + buffer.append(':'); + append(buffer,value); + c[0]=','; + } + + public void add(String name, double value) + { + if (c[0]==0) + throw new IllegalStateException(); + buffer.append(c); + QuotedStringTokenizer.quote(buffer,name); + buffer.append(':'); + appendNumber(buffer,new Double(value)); + c[0]=','; + } + + public void add(String name, long value) + { + if (c[0]==0) + throw new IllegalStateException(); + buffer.append(c); + QuotedStringTokenizer.quote(buffer,name); + buffer.append(':'); + appendNumber(buffer,TypeUtil.newLong(value)); + c[0]=','; + } + + public void add(String name, boolean value) + { + if (c[0]==0) + throw new IllegalStateException(); + buffer.append(c); + QuotedStringTokenizer.quote(buffer,name); + buffer.append(':'); + appendBoolean(buffer,value?Boolean.TRUE:Boolean.FALSE); + c[0]=','; + } + }); + + if (c[0]=='{') + buffer.append("{}"); + else if (c[0]!=0) + buffer.append("}"); + } + + public void appendJSON(StringBuffer buffer, Generator generator) + { + generator.addJSON(buffer); + } + + public void appendMap(StringBuffer buffer, Map object) + { + if (object==null) + { + appendNull(buffer); + return; + } + + buffer.append('{'); + Iterator iter=object.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry=(Map.Entry)iter.next(); + QuotedStringTokenizer.quote(buffer,entry.getKey().toString()); + buffer.append(':'); + append(buffer,entry.getValue()); + if (iter.hasNext()) + buffer.append(','); + } + + buffer.append('}'); + } + + public void appendArray(StringBuffer buffer, Collection collection) + { + if (collection==null) + { + appendNull(buffer); + return; + } + + buffer.append('['); + Iterator iter=collection.iterator(); + boolean first=true; + while (iter.hasNext()) + { + if (!first) + buffer.append(','); + + first=false; + append(buffer,iter.next()); + } + + buffer.append(']'); + } + + public void appendArray(StringBuffer buffer, Object array) + { + if (array==null) + { + appendNull(buffer); + return; + } + + buffer.append('['); + int length=Array.getLength(array); + + for (int i=0; i + * If no match is found for the class, then the interfaces for the class are tried. If still no + * match is found, then the super class and it's interfaces are tried recursively. + * @param forClass The class + * @return a {@link Convertor} or null if none were found. + */ + protected Convertor getConvertor(Class forClass) + { + Class c=forClass; + Convertor convertor=(Convertor)_convertors.get(c); + if (convertor==null && this!=__default) + convertor=__default.getConvertor(forClass); + + while (convertor==null&&c!=null&&c!=Object.class) + { + Class[] ifs=c.getInterfaces(); + int i=0; + while (convertor==null&&ifs!=null&&i1) + { + switch (c) + { + case '*': + comment_state=3; + break; + case '/': + if (comment_state==3) + { + comment_state=0; + if (strip_state==2) + return o; + } + else + comment_state=2; + break; + default: + comment_state=2; + } + } + // handle // comment + else if (comment_state<0) + { + switch (c) + { + case '\r': + case '\n': + comment_state=0; + default: + break; + } + } + // handle unknown + else + { + if (!Character.isWhitespace(c)) + { + if (c=='/') + comment_state=1; + else if (c=='*') + comment_state=3; + else if (o==null) + { + o=parse(source); + continue; + } + } + } + + source.next(); + } + + return o; + } + + + public Object parse(Source source) + { + int comment_state=0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//" + + while (source.hasNext()) + { + char c=source.peek(); + + // handle // or /* comment + if (comment_state==1) + { + switch (c) + { + case '/': + comment_state=-1; + break; + case '*': + comment_state=2; + } + } + // handle /* */ comment + else if (comment_state>1) + { + switch (c) + { + case '*': + comment_state=3; + break; + case '/': + if (comment_state==3) + comment_state=0; + else + comment_state=2; + break; + default: + comment_state=2; + } + } + // handle // comment + else if (comment_state<0) + { + switch (c) + { + case '\r': + case '\n': + comment_state=0; + break; + default: + break; + } + } + // handle unknown + else + { + switch (c) + { + case '{': + return parseObject(source); + case '[': + return parseArray(source); + case '"': + return parseString(source); + case '-': + return parseNumber(source); + + case 'n': + complete("null",source); + return null; + case 't': + complete("true",source); + return Boolean.TRUE; + case 'f': + complete("false",source); + return Boolean.FALSE; + case 'u': + complete("undefined",source); + return null; + + case '/': + comment_state=1; + break; + + default: + if (Character.isDigit(c)) + return parseNumber(source); + else if (Character.isWhitespace(c)) + break; + return handleUnknown(source, c); + } + } + source.next(); + } + + return null; + } + + protected Object handleUnknown(Source source, char c) + { + throw new IllegalStateException("unknown char '"+c+"'("+(int)c+") in "+source); + } + + protected Object parseObject(Source source) + { + if (source.next()!='{') + throw new IllegalStateException(); + Map map=newMap(); + + char next=seekTo("\"}",source); + + while (source.hasNext()) + { + if (next=='}') + { + source.next(); + break; + } + + String name=parseString(source); + seekTo(':',source); + source.next(); + + Object value=contextFor(name).parse(source); + map.put(name,value); + + seekTo(",}",source); + next=source.next(); + if (next=='}') + break; + else + next=seekTo("\"}",source); + } + + String classname=(String)map.get("class"); + if (classname!=null) + { + try + { + Class c=Loader.loadClass(JSON.class,classname); + return convertTo(c,map); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + } + return map; + } + + + protected Object parseArray(Source source) + { + if (source.next()!='[') + throw new IllegalStateException(); + + int size=0; + ArrayList list=null; + Object item=null; + boolean coma=true; + + while (source.hasNext()) + { + char c=source.peek(); + switch (c) + { + case ']': + source.next(); + switch(size) + { + case 0: + return newArray(0); + case 1: + Object array = newArray(1); + Array.set(array,0,item); + return array; + default: + return list.toArray(newArray(list.size())); + } + + case ',': + if (coma) + throw new IllegalStateException(); + coma=true; + source.next(); + break; + + default: + if (Character.isWhitespace(c)) + source.next(); + else + { + coma=false; + if (size++==0) + item=contextForArray().parse(source); + else if (list==null) + { + list=new ArrayList(); + list.add(item); + item=contextForArray().parse(source); + list.add(item); + item=null; + } + else + { + item=contextForArray().parse(source); + list.add(item); + item=null; + } + } + } + + } + + throw new IllegalStateException("unexpected end of array"); + } + + + protected String parseString(Source source) + { + if (source.next()!='"') + throw new IllegalStateException(); + + boolean escape=false; + + StringBuffer b=null; + final char[] scratch=source.scratchBuffer(); + + if (scratch!=null) + { + int i=0; + while (source.hasNext()) + { + if(i>=scratch.length) + { + // we have filled the scratch buffer, so we must + // use the StringBuffer for a large string + b=new StringBuffer(scratch.length*2); + b.append(scratch,0,i); + break; + } + + char c=source.next(); + + if (escape) + { + escape=false; + switch (c) + { + case '"': + scratch[i++]='"'; + break; + case '\\': + scratch[i++]='\\'; + break; + case '/': + scratch[i++]='/'; + break; + case 'b': + scratch[i++]='\b'; + break; + case 'f': + scratch[i++]='\f'; + break; + case 'n': + scratch[i++]='\n'; + break; + case 'r': + scratch[i++]='\r'; + break; + case 't': + scratch[i++]='\t'; + break; + case 'u': + char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+ + (TypeUtil.convertHexDigit((byte)source.next())<<8)+ + (TypeUtil.convertHexDigit((byte)source.next())<<4)+ + (TypeUtil.convertHexDigit((byte)source.next()))); + scratch[i++]=uc; + break; + default: + scratch[i++]=c; + } + } + else if (c=='\\') + { + escape=true; + continue; + } + else if (c=='\"') + { + // Return string that fits within scratch buffer + return toString(scratch,0,i); + } + else + scratch[i++]=c; + } + + // Missing end quote, but return string anyway ? + if (b==null) + return toString(scratch,0,i); + } + else + b=new StringBuffer(getStringBufferSize()); + + + // parse large string into string buffer + synchronized (b) + { + while (source.hasNext()) + { + char c=source.next(); + + if (escape) + { + escape=false; + switch (c) + { + case '"': + b.append('"'); + break; + case '\\': + b.append('\\'); + break; + case '/': + b.append('/'); + break; + case 'b': + b.append('\b'); + break; + case 'f': + b.append('\f'); + break; + case 'n': + b.append('\n'); + break; + case 'r': + b.append('\r'); + break; + case 't': + b.append('\t'); + break; + case 'u': + char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+ + (TypeUtil.convertHexDigit((byte)source.next())<<8)+ + (TypeUtil.convertHexDigit((byte)source.next())<<4)+ + (TypeUtil.convertHexDigit((byte)source.next()))); + b.append(uc); + break; + default: + b.append(c); + } + } + else if (c=='\\') + { + escape=true; + continue; + } + else if (c=='\"') + break; + else + b.append(c); + } + + return b.toString(); + } + } + + public Number parseNumber(Source source) + { + boolean minus=false; + long number=0; + StringBuilder buffer=null; + + longLoop: while (source.hasNext()) + { + char c=source.peek(); + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + number=number*10+(c-'0'); + source.next(); + break; + + case '-': + case '+': + if (number!=0) + throw new IllegalStateException("bad number"); + minus=true; + source.next(); + break; + + case '.': + case 'e': + case 'E': + buffer=new StringBuilder(16); + if(minus) + buffer.append('-'); + buffer.append(number); + buffer.append(c); + source.next(); + break longLoop; + + + default: + break longLoop; + } + } + + if (buffer==null) + return TypeUtil.newLong(minus?-1*number:number); + + + doubleLoop: while (source.hasNext()) + { + char c=source.peek(); + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '.': + case '+': + case 'e': + case 'E': + buffer.append(c); + source.next(); + break; + + default: + break doubleLoop; + } + } + return new Double(buffer.toString()); + + } + + protected void seekTo(char seek, Source source) + { + while (source.hasNext()) + { + char c=source.peek(); + if (c==seek) + return; + + if (!Character.isWhitespace(c)) + throw new IllegalStateException("Unexpected '"+c+" while seeking '"+seek+"'"); + source.next(); + } + + throw new IllegalStateException("Expected '"+seek+"'"); + } + + protected char seekTo(String seek, Source source) + { + while (source.hasNext()) + { + char c=source.peek(); + if (seek.indexOf(c)>=0) + { + return c; + } + + if (!Character.isWhitespace(c)) + throw new IllegalStateException("Unexpected '"+c+"' while seeking one of '"+seek+"'"); + source.next(); + } + + throw new IllegalStateException("Expected one of '"+seek+"'"); + } + + protected static void complete(String seek, Source source) + { + int i=0; + while (source.hasNext()&&i + * A JSON.Convertible object may be written to a JSONObject + * or initialized from a Map of field names to values. + *

    + * If the JSON is to be convertible back to an Object, then + * the method {@link Output#addClass(Class)} must be called from within toJSON() + * + */ + public interface Convertible + { + public void toJSON(Output out); + + public void fromJSON(Map object); + } + + /* ------------------------------------------------------------ */ + /** Static JSON Convertor. + *

    + * may be implemented to provide static convertors for objects that may be registered + * with {@link JSON#registerConvertor(Class, org.eclipse.util.ajax.JSON.Convertor). + * These convertors are looked up by class, interface and + * super class by {@link JSON#getConvertor(Class)}. Convertors should be used when the + * classes to be converted cannot implement {@link Convertible} or {@link Generator}. + */ + public interface Convertor + { + public void toJSON(Object obj, Output out); + + public Object fromJSON(Map object); + } + + /* ------------------------------------------------------------ */ + /** JSON Generator. + * A class that can add it's JSON representation directly to a StringBuffer. + * This is useful for object instances that are frequently converted and wish to + * avoid multiple Conversions + */ + public interface Generator + { + public void addJSON(StringBuffer buffer); + } + + /* ------------------------------------------------------------ */ + /** A Literal JSON generator + * A utility instance of {@link JSON.Generator} that holds a pre-generated string on JSON text. + */ + public static class Literal implements Generator + { + private String _json; + + /* ------------------------------------------------------------ */ + /** Construct a literal JSON instance for use by {@link JSON#toString(Object)}. + * If {@link Log#isDebugEnabled()} is true, the JSON will be parsed to check validity + * @param json A literal JSON string. + */ + public Literal(String json) + { + if (Log.isDebugEnabled()) + parse(json); + _json=json; + } + + public String toString() + { + return _json; + } + + public void addJSON(StringBuffer buffer) + { + buffer.append(_json); + } + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java new file mode 100644 index 00000000000..6a36becc5cf --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java @@ -0,0 +1,100 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.ajax; + +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.eclipse.jetty.util.DateCache; +import org.eclipse.jetty.util.ajax.JSON.Output; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** +* Convert a {@link Date} to JSON. +* If fromJSON is true in the constructor, the JSON generated will +* be of the form {class="java.util.Date",value="1/1/1970 12:00 GMT"} +* If fromJSON is false, then only the string value of the date is generated. +*/ +public class JSONDateConvertor implements JSON.Convertor +{ + private boolean _fromJSON; + DateCache _dateCache; + SimpleDateFormat _format; + + public JSONDateConvertor() + { + this(false); + } + + public JSONDateConvertor(boolean fromJSON) + { + this(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),fromJSON); + } + + public JSONDateConvertor(String format,TimeZone zone,boolean fromJSON) + { + _dateCache=new DateCache(format); + _dateCache.setTimeZone(zone); + _fromJSON=fromJSON; + _format=new SimpleDateFormat(format); + _format.setTimeZone(zone); + } + + public JSONDateConvertor(String format, TimeZone zone, boolean fromJSON, Locale locale) + { + _dateCache = new DateCache(format, locale); + _dateCache.setTimeZone(zone); + _fromJSON = fromJSON; + _format = new SimpleDateFormat(format, new DateFormatSymbols(locale)); + _format.setTimeZone(zone); + } + + public Object fromJSON(Map map) + { + if (!_fromJSON) + throw new UnsupportedOperationException(); + try + { + synchronized(_format) + { + return _format.parseObject((String)map.get("value")); + } + } + catch(Exception e) + { + Log.warn(e); + } + return null; + } + + public void toJSON(Object obj, Output out) + { + String date = _dateCache.format((Date)obj); + if (_fromJSON) + { + out.addClass(obj.getClass()); + out.add("value",date); + } + else + { + out.add(date); + } + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java new file mode 100644 index 00000000000..17e91f5671c --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java @@ -0,0 +1,87 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.ajax; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.ajax.JSON.Output; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** + * Convert an {@link Enum} to JSON. + * If fromJSON is true in the constructor, the JSON generated will + * be of the form {class="com.acme.TrafficLight",value="Green"} + * If fromJSON is false, then only the string value of the enum is generated. + * + * + */ +public class JSONEnumConvertor implements JSON.Convertor +{ + private boolean _fromJSON; + private Method _valueOf; + { + try + { + Class e = Loader.loadClass(getClass(),"java.lang.Enum"); + _valueOf=e.getMethod("valueOf",new Class[]{Class.class,String.class}); + } + catch(Exception e) + { + throw new RuntimeException("!Enums",e); + } + } + + public JSONEnumConvertor() + { + this(false); + } + + public JSONEnumConvertor(boolean fromJSON) + { + _fromJSON=fromJSON; + } + + public Object fromJSON(Map map) + { + if (!_fromJSON) + throw new UnsupportedOperationException(); + try + { + Class c=Loader.loadClass(getClass(),(String)map.get("class")); + return _valueOf.invoke(null,new Object[]{c,map.get("value")}); + } + catch(Exception e) + { + Log.warn(e); + } + return null; + } + + public void toJSON(Object obj, Output out) + { + if (_fromJSON) + { + out.addClass(obj.getClass()); + out.add("value",obj.toString()); + } + else + { + out.add(obj.toString()); + } + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java new file mode 100755 index 00000000000..470564c1f31 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java @@ -0,0 +1,110 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.ajax; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jetty.util.ajax.JSON.Output; + +/* ------------------------------------------------------------ */ +/** + * Convert an Object to JSON using reflection on getters methods. + * + * + * + */ +public class JSONObjectConvertor implements JSON.Convertor +{ + private boolean _fromJSON; + private Set _excluded=null; + + public JSONObjectConvertor() + { + _fromJSON=false; + } + + public JSONObjectConvertor(boolean fromJSON) + { + _fromJSON=fromJSON; + } + + /* ------------------------------------------------------------ */ + /** + * @param fromJSON + * @param excluded An array of field names to exclude from the conversion + */ + public JSONObjectConvertor(boolean fromJSON,String[] excluded) + { + _fromJSON=fromJSON; + if (excluded!=null) + _excluded=new HashSet(Arrays.asList(excluded)); + } + + public Object fromJSON(Map map) + { + if (_fromJSON) + throw new UnsupportedOperationException(); + return map; + } + + public void toJSON(Object obj, Output out) + { + try + { + Class c=obj.getClass(); + + if (_fromJSON) + out.addClass(obj.getClass()); + + Method[] methods = obj.getClass().getMethods(); + + for (int i=0;i, NumberType> __numberTypes = new HashMap, NumberType>(); + + public static NumberType getNumberType(Class clazz) + { + return __numberTypes.get(clazz); + } + + protected boolean _fromJSON; + protected Class _pojoClass; + protected Map _getters = new HashMap(); + protected Map _setters = new HashMap(); + protected Set _excluded; + + public JSONPojoConvertor(Class pojoClass) + { + this(pojoClass, (Set)null, true); + } + + public JSONPojoConvertor(Class pojoClass, String[] excluded) + { + this(pojoClass, new HashSet(Arrays.asList(excluded)), true); + } + + public JSONPojoConvertor(Class pojoClass, Set excluded) + { + this(pojoClass, excluded, true); + } + + public JSONPojoConvertor(Class pojoClass, Set excluded, boolean fromJSON) + { + _pojoClass = pojoClass; + _excluded = excluded; + _fromJSON = fromJSON; + init(); + } + + public JSONPojoConvertor(Class pojoClass, boolean fromJSON) + { + this(pojoClass, (Set)null, fromJSON); + } + + /* ------------------------------------------------------------ */ + protected void init() + { + Method[] methods = _pojoClass.getMethods(); + for (int i=0;i2) + name=name.substring(2,3).toLowerCase()+name.substring(3); + else if (name.startsWith("get") && name.length()>3) + name=name.substring(3,4).toLowerCase()+name.substring(4); + else + break; + if(includeField(name, m)) + addGetter(name, m); + } + break; + case 1: + if (name.startsWith("set") && name.length()>3) + { + name=name.substring(3,4).toLowerCase()+name.substring(4); + if(includeField(name, m)) + addSetter(name, m); + } + break; + } + } + } + } + + /* ------------------------------------------------------------ */ + protected void addGetter(String name, Method method) + { + _getters.put(name, method); + } + + /* ------------------------------------------------------------ */ + protected void addSetter(String name, Method method) + { + _setters.put(name, new Setter(name, method)); + } + + /* ------------------------------------------------------------ */ + protected Setter getSetter(String name) + { + return _setters.get(name); + } + + /* ------------------------------------------------------------ */ + protected boolean includeField(String name, Method m) + { + return _excluded==null || !_excluded.contains(name); + } + + /* ------------------------------------------------------------ */ + protected int getExcludedCount() + { + return _excluded==null ? 0 : _excluded.size(); + } + + /* ------------------------------------------------------------ */ + public Object fromJSON(Map object) + { + Object obj = null; + try + { + obj = _pojoClass.newInstance(); + } + catch(Exception e) + { + // TODO return Map instead? + throw new RuntimeException(e); + } + + setProps(obj, object); + return obj; + } + + /* ------------------------------------------------------------ */ + public int setProps(Object obj, Map props) + { + int count = 0; + for(Iterator iterator = props.entrySet().iterator(); iterator.hasNext();) + { + Map.Entry entry = (Map.Entry) iterator.next(); + Setter setter = getSetter((String)entry.getKey()); + if(setter!=null) + { + try + { + setter.invoke(obj, entry.getValue()); + count++; + } + catch(Exception e) + { + // TODO throw exception? + Log.warn("{} property '{}' not set. (errors)", _pojoClass.getName(), + setter.getPropertyName()); + log(e); + } + } + } + return count; + } + + /* ------------------------------------------------------------ */ + public void toJSON(Object obj, Output out) + { + if(_fromJSON) + out.addClass(_pojoClass); + for(Map.Entry entry : _getters.entrySet()) + { + try + { + out.add(entry.getKey(), entry.getValue().invoke(obj, GETTER_ARG)); + } + catch(Exception e) + { + // TODO throw exception? + Log.warn("{} property '{}' excluded. (errors)", _pojoClass.getName(), + entry.getKey()); + log(e); + } + } + } + + /* ------------------------------------------------------------ */ + protected void log(Throwable t) + { + Log.ignore(t); + } + + public static class Setter + { + protected String _propertyName; + protected Method _method; + protected NumberType _numberType; + protected Class _type; + protected Class _componentType; + + public Setter(String propertyName, Method method) + { + _propertyName = propertyName; + _method = method; + _type = method.getParameterTypes()[0]; + _numberType = (NumberType)__numberTypes.get(_type); + if(_numberType==null && _type.isArray()) + { + _componentType = _type.getComponentType(); + _numberType = (NumberType)__numberTypes.get(_componentType); + } + } + + public String getPropertyName() + { + return _propertyName; + } + + public Method getMethod() + { + return _method; + } + + public NumberType getNumberType() + { + return _numberType; + } + + public Class getType() + { + return _type; + } + + public Class getComponentType() + { + return _componentType; + } + + public boolean isPropertyNumber() + { + return _numberType!=null; + } + + public void invoke(Object obj, Object value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException + { + if(value==null) + _method.invoke(obj, NULL_ARG); + else + invokeObject(obj, value); + } + + protected void invokeObject(Object obj, Object value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException + { + if(_numberType!=null && value instanceof Number) + _method.invoke(obj, new Object[]{_numberType.getActualValue((Number)value)}); + else if(_componentType!=null && value.getClass().isArray()) + { + if(_numberType==null) + { + int len = Array.getLength(value); + Object array = Array.newInstance(_componentType, len); + try + { + System.arraycopy(value, 0, array, 0, len); + } + catch(Exception e) + { + // unusual array with multiple types + Log.ignore(e); + _method.invoke(obj, new Object[]{value}); + return; + } + _method.invoke(obj, new Object[]{array}); + } + else + { + Object[] old = (Object[])value; + Object array = Array.newInstance(_componentType, old.length); + try + { + for(int i=0; i=0) + { + if (timeout==0) + _mutex.wait(); + else if (timeout>0) + _mutex.wait(timeout); + + } + } + catch (InterruptedException e) + { + _expired=true; + Log.ignore(e); + } + finally + { + result=_resumed; + _resumed=false; + _pending=false; + } + + return result; + } + } + + public boolean isPending() + { + synchronized (_mutex) + { + return _pending; + } + } + + public boolean isResumed() + { + synchronized (_mutex) + { + return _resumed; + } + } + + public boolean isExpired() + { + synchronized (_mutex) + { + return _expired; + } + } + + public Object getObject() + { + return _object; + } + + public void setObject(Object object) + { + _object = object; + } + + public Object getMutex() + { + return _mutex; + } + + public void setMutex(Object mutex) + { + if (_pending && mutex!=_mutex) + throw new IllegalStateException(); + _mutex = mutex==null ? this : mutex; + } + + public String toString() + { + synchronized (this) + { + return "WaitingContinuation@"+hashCode()+ + (_new?",new":"")+ + (_pending?",pending":"")+ + (_resumed?",resumed":"")+ + (_expired?",expired":""); + } + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java new file mode 100644 index 00000000000..63a4c7017a2 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java @@ -0,0 +1,195 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.component; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; + +/** + * Basic implementation of the life cycle interface for components. + * + * + */ +public abstract class AbstractLifeCycle implements LifeCycle +{ + private final Object _lock = new Object(); + private final int FAILED = -1, STOPPED = 0, STARTING = 1, STARTED = 2, STOPPING = 3; + private volatile int _state = STOPPED; + protected LifeCycle.Listener[] _listeners; + + protected void doStart() throws Exception + { + } + + protected void doStop() throws Exception + { + } + + public final void start() throws Exception + { + synchronized (_lock) + { + try + { + if (_state == STARTED || _state == STARTING) + return; + setStarting(); + doStart(); + Log.debug("started {}",this); + setStarted(); + } + catch (Exception e) + { + Log.warn("failed " + this,e); + setFailed(e); + throw e; + } + catch (Error e) + { + Log.warn("failed " + this,e); + setFailed(e); + throw e; + } + } + } + + public final void stop() throws Exception + { + synchronized (_lock) + { + try + { + if (_state == STOPPING || _state == STOPPED) + return; + setStopping(); + doStop(); + Log.debug("stopped {}",this); + setStopped(); + } + catch (Exception e) + { + Log.warn("failed " + this,e); + setFailed(e); + throw e; + } + catch (Error e) + { + Log.warn("failed " + this,e); + setFailed(e); + throw e; + } + } + } + + public boolean isRunning() + { + return _state == STARTED || _state == STARTING; + } + + public boolean isStarted() + { + return _state == STARTED; + } + + public boolean isStarting() + { + return _state == STARTING; + } + + public boolean isStopping() + { + return _state == STOPPING; + } + + public boolean isStopped() + { + return _state == STOPPED; + } + + public boolean isFailed() + { + return _state == FAILED; + } + + public void addLifeCycleListener(LifeCycle.Listener listener) + { + _listeners = (LifeCycle.Listener[])LazyList.addToArray(_listeners,listener,LifeCycle.Listener.class); + } + + public void removeLifeCycleListener(LifeCycle.Listener listener) + { + LazyList.removeFromArray(_listeners,listener); + } + + private void setStarted() + { + _state = STARTED; + if (_listeners != null) + { + for (int i = 0; i < _listeners.length; i++) + { + _listeners[i].lifeCycleStarted(this); + } + } + } + + private void setStarting() + { + _state = STARTING; + if (_listeners != null) + { + for (int i = 0; i < _listeners.length; i++) + { + _listeners[i].lifeCycleStarting(this); + } + } + } + + private void setStopping() + { + _state = STOPPING; + if (_listeners != null) + { + for (int i = 0; i < _listeners.length; i++) + { + _listeners[i].lifeCycleStopping(this); + } + } + } + + private void setStopped() + { + _state = STOPPED; + if (_listeners != null) + { + for (int i = 0; i < _listeners.length; i++) + { + _listeners[i].lifeCycleStopped(this); + } + } + } + + private void setFailed(Throwable error) + { + _state = FAILED; + if (_listeners != null) + { + for (int i = 0; i < _listeners.length; i++) + { + _listeners[i].lifeCycleFailure(this,error); + } + } + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java new file mode 100644 index 00000000000..3a640441217 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java @@ -0,0 +1,298 @@ +// ======================================================================== +// Copyright (c) 2005-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.component; +import java.util.EventListener; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Container. + * This class allows a containment events to be generated from update methods. + * + * The style of usage is:

    + *   public void setFoo(Foo foo)
    + *   {
    + *       getContainer().update(this,this.foo,foo,"foo");
    + *       this.foo=foo;
    + *   }
    + *   
    + *   public void setBars(Bar[] bars)
    + *   {
    + *       getContainer().update(this,this.bars,bars,"bar");
    + *       this.bars=bars;
    + *   }
    + * 
    + * + * + * + */ +public class Container +{ + private Object _listeners; + + public synchronized void addEventListener(Container.Listener listener) + { + _listeners=LazyList.add(_listeners,listener); + } + + public synchronized void removeEventListener(Container.Listener listener) + { + _listeners=LazyList.remove(_listeners,listener); + } + + /* ------------------------------------------------------------ */ + /** Update single parent to child relationship. + * @param parent The parent of the child. + * @param oldChild The previous value of the child. If this is non null and differs from child, then a remove event is generated. + * @param child The current child. If this is non null and differs from oldChild, then an add event is generated. + * @param relationship The name of the relationship + */ + public synchronized void update(Object parent, Object oldChild, final Object child, String relationship) + { + if (oldChild!=null && !oldChild.equals(child)) + remove(parent,oldChild,relationship); + if (child!=null && !child.equals(oldChild)) + add(parent,child,relationship); + } + + /* ------------------------------------------------------------ */ + /** Update single parent to child relationship. + * @param parent The parent of the child. + * @param oldChild The previous value of the child. If this is non null and differs from child, then a remove event is generated. + * @param child The current child. If this is non null and differs from oldChild, then an add event is generated. + * @param relationship The name of the relationship + * @param addRemoveBean If true add/remove is called for the new/old children as well as the relationships + */ + public synchronized void update(Object parent, Object oldChild, final Object child, String relationship,boolean addRemove) + { + if (oldChild!=null && !oldChild.equals(child)) + { + remove(parent,oldChild,relationship); + if (addRemove) + removeBean(oldChild); + } + + if (child!=null && !child.equals(oldChild)) + { + if (addRemove) + addBean(child); + add(parent,child,relationship); + } + } + + /* ------------------------------------------------------------ */ + /** Update multiple parent to child relationship. + * @param parent The parent of the child. + * @param oldChildren The previous array of children. A remove event is generated for any child in this array but not in the children array. + * This array is modified and children that remain in the new children array are nulled out of the old children array. + * @param children The current array of children. An add event is generated for any child in this array but not in the oldChildren array. + * @param relationship The name of the relationship + */ + public synchronized void update(Object parent, Object[] oldChildren, final Object[] children, String relationship) + { + update(parent,oldChildren,children,relationship,false); + } + + /* ------------------------------------------------------------ */ + /** Update multiple parent to child relationship. + * @param parent The parent of the child. + * @param oldChildren The previous array of children. A remove event is generated for any child in this array but not in the children array. + * This array is modified and children that remain in the new children array are nulled out of the old children array. + * @param children The current array of children. An add event is generated for any child in this array but not in the oldChildren array. + * @param relationship The name of the relationship + * @param addRemoveBean If true add/remove is called for the new/old children as well as the relationships + */ + public synchronized void update(Object parent, Object[] oldChildren, final Object[] children, String relationship, boolean addRemove) + { + Object[] newChildren = null; + if (children!=null) + { + newChildren = new Object[children.length]; + + for (int i=children.length;i-->0;) + { + boolean new_child=true; + if (oldChildren!=null) + { + for (int j=oldChildren.length;j-->0;) + { + if (children[i]!=null && children[i].equals(oldChildren[j])) + { + oldChildren[j]=null; + new_child=false; + } + } + } + if (new_child) + newChildren[i]=children[i]; + } + } + + if (oldChildren!=null) + { + for (int i=oldChildren.length;i-->0;) + { + if (oldChildren[i]!=null) + { + remove(parent,oldChildren[i],relationship); + if (addRemove) + removeBean(oldChildren[i]); + } + } + } + + if (newChildren!=null) + { + for (int i=0;i"+_child; + } + + public int hashCode() + { + return _parent.hashCode()+_child.hashCode()+_relationship.hashCode(); + } + + public boolean equals(Object o) + { + if (o==null || !(o instanceof Relationship)) + return false; + Relationship r = (Relationship)o; + return r._parent==_parent && r._child==_child && r._relationship.equals(_relationship); + } + } + + /* ------------------------------------------------------------ */ + /** Listener. + * A listener for Container events. + */ + public interface Listener extends EventListener + { + public void addBean(Object bean); + public void removeBean(Object bean); + public void add(Container.Relationship relationship); + public void remove(Container.Relationship relationship); + + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/LifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/LifeCycle.java new file mode 100644 index 00000000000..ddc3ddc551e --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/LifeCycle.java @@ -0,0 +1,114 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.component; + +import java.util.EventListener; + +/* ------------------------------------------------------------ */ +/** + * The lifecycle interface for generic components. + *
    + * Classes implementing this interface have a defined life cycle + * defined by the methods of this interface. + * + * + */ +public interface LifeCycle +{ + /* ------------------------------------------------------------ */ + /** + * Starts the component. + * @throws Exception If the component fails to start + * @see #isStarted() + * @see #stop() + * @see #isFailed() + */ + public void start() + throws Exception; + + /* ------------------------------------------------------------ */ + /** + * Stops the component. + * The component may wait for current activities to complete + * normally, but it can be interrupted. + * @exception Exception If the component fails to stop + * @see #isStopped() + * @see #start() + * @see #isFailed() + */ + public void stop() + throws Exception; + + /* ------------------------------------------------------------ */ + /** + * @return true if the component is starting or has been started. + */ + public boolean isRunning(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component has been started. + * @see #start() + * @see #isStarting() + */ + public boolean isStarted(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component is starting. + * @see #isStarted() + */ + public boolean isStarting(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component is stopping. + * @see #isStopped() + */ + public boolean isStopping(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component has been stopped. + * @see #stop() + * @see #isStopping() + */ + public boolean isStopped(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component has failed to start or has failed to stop. + */ + public boolean isFailed(); + + /* ------------------------------------------------------------ */ + public void addLifeCycleListener(LifeCycle.Listener listener); + + /* ------------------------------------------------------------ */ + public void removeLifeCycleListener(LifeCycle.Listener listener); + + + /* ------------------------------------------------------------ */ + /** Listener. + * A listener for Lifecycle events. + */ + public interface Listener extends EventListener + { + public void lifeCycleStarting(LifeCycle event); + public void lifeCycleStarted(LifeCycle event); + public void lifeCycleFailure(LifeCycle event,Throwable cause); + public void lifeCycleStopping(LifeCycle event); + public void lifeCycleStopped(LifeCycle event); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java new file mode 100644 index 00000000000..4406d1fa9cc --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java @@ -0,0 +1,307 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.log; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import org.eclipse.jetty.util.Loader; + + + +/*-----------------------------------------------------------------------*/ +/** Logging. + * This class provides a static logging interface. If an instance of the + * org.slf4j.Logger class is found on the classpath, the static log methods + * are directed to a slf4j logger for "org.eclipse.log". Otherwise the logs + * are directed to stderr. + * + * If the system property VERBOSE is set, then ignored exceptions are logged in detail. + * + */ +public class Log +{ + private static final String[] __nestedEx = + {"getTargetException","getTargetError","getException","getRootCause"}; + /*-------------------------------------------------------------------*/ + private static final Class[] __noArgs=new Class[0]; + public final static String EXCEPTION= "EXCEPTION "; + public final static String IGNORED= "IGNORED"; + public final static String IGNORED_FMT= "IGNORED: {}"; + public final static String NOT_IMPLEMENTED= "NOT IMPLEMENTED "; + + public static String __logClass; + public static boolean __verbose; + public static boolean __ignored; + + static + { + AccessController.doPrivileged(new PrivilegedAction() + { + public Boolean run() + { + __logClass = System.getProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.Slf4jLog"); + __verbose = System.getProperty("VERBOSE",null)!=null; + __ignored = System.getProperty("IGNORED",null)!=null; return true; + } + }); + } + + private static Logger __log; + private static boolean _initialized; + + public static boolean initialized() + { + if (__log!=null) + return true; + + synchronized (Log.class) + { + if (_initialized) + return __log!=null; + _initialized=true; + } + + Class log_class=null; + try + { + log_class=Loader.loadClass(Log.class, __logClass); + if (__log==null || !__log.getClass().equals(log_class)) + { + __log=(Logger) log_class.newInstance(); + __log.info("Logging to {} via {}",__log,log_class.getName()); + } + } + catch(NoClassDefFoundError e) + { + initStandardLogging(e); + } + catch(Exception e) + { + initStandardLogging(e); + } + + return __log!=null; + } + + private static void initStandardLogging(Throwable e) { + Class log_class; + if (__log==null) + { + log_class= StdErrLog.class; + __log=new StdErrLog(); + __log.info("Logging to {} via {}",__log,log_class.getName()); + if(e != null && __verbose) + e.printStackTrace(); + } + } + + public static void setLog(Logger log) + { + Log.__log=log; + } + + public static Logger getLog() + { + initialized(); + return __log; + } + + + /** + * Set Log to parent Logger. + *

    + * If there is a different Log class available from a parent classloader, + * call {@link #getLogger(String)} on it and construct a {@link LoggerLog} instance + * as this Log's Logger, so that logging is delegated to the parent Log. + *

    + * This should be used if a webapp is using Log, but wishes the logging to be + * directed to the containers log. + *

    + * If there is not parent Log, then this call is equivalent to

    +     *   Log.setLog(Log.getLogger(name));
    +     * 
    + * @param name Logger name + */ + public static void setLogToParent(String name) + { + ClassLoader loader = Log.class.getClassLoader(); + if (loader.getParent()!=null) + { + try + { + Class uberlog = loader.getParent().loadClass("org.eclipse.jetty.util.log.Log"); + Method getLogger=uberlog.getMethod("getLogger",new Class[]{String.class}); + Object logger = getLogger.invoke(null,name); + setLog(new LoggerLog(logger)); + return; + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + setLog(getLogger(name)); + } + + public static void debug(Throwable th) + { + if (!isDebugEnabled()) + return; + __log.debug(EXCEPTION,th); + unwind(th); + } + + public static void debug(String msg) + { + if (!initialized()) + return; + + __log.debug(msg,null,null); + } + + public static void debug(String msg,Object arg) + { + if (!initialized()) + return; + __log.debug(msg,arg,null); + } + + public static void debug(String msg,Object arg0, Object arg1) + { + if (!initialized()) + return; + __log.debug(msg,arg0,arg1); + } + + /* ------------------------------------------------------------ */ + /** + * Ignore an exception unless trace is enabled. + * This works around the problem that log4j does not support the trace level. + */ + public static void ignore(Throwable th) + { + if (!initialized()) + return; + if (__ignored) + { + __log.warn(IGNORED,th); + unwind(th); + } + else if (__verbose) + { + __log.warn(IGNORED,th); + unwind(th); + } + } + + public static void info(String msg) + { + if (!initialized()) + return; + __log.info(msg,null,null); + } + + public static void info(String msg,Object arg) + { + if (!initialized()) + return; + __log.info(msg,arg,null); + } + + public static void info(String msg,Object arg0, Object arg1) + { + if (!initialized()) + return; + __log.info(msg,arg0,arg1); + } + + public static boolean isDebugEnabled() + { + if (!initialized()) + return false; + return __log.isDebugEnabled(); + } + + public static void warn(String msg) + { + if (!initialized()) + return; + __log.warn(msg,null,null); + } + + public static void warn(String msg,Object arg) + { + if (!initialized()) + return; + __log.warn(msg,arg,null); + } + + public static void warn(String msg,Object arg0, Object arg1) + { + if (!initialized()) + return; + __log.warn(msg,arg0,arg1); + } + + public static void warn(String msg, Throwable th) + { + if (!initialized()) + return; + __log.warn(msg,th); + unwind(th); + } + + public static void warn(Throwable th) + { + if (!initialized()) + return; + __log.warn(EXCEPTION,th); + unwind(th); + } + + /** Obtain a named Logger. + * Obtain a named Logger or the default Logger if null is passed. + */ + public static Logger getLogger(String name) + { + if (!initialized()) + return null; + + if (name==null) + return __log; + return __log.getLogger(name); + } + + private static void unwind(Throwable th) + { + if (th==null) + return; + for (int i=0;i<__nestedEx.length;i++) + { + try + { + Method get_target = th.getClass().getMethod(__nestedEx[i],__noArgs); + Throwable th2=(Throwable)get_target.invoke(th,(Object[])null); + if (th2!=null && th2!=th) + warn("Nested in "+th+":",th2); + } + catch(Exception ignore){} + } + } + + +} + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java new file mode 100644 index 00000000000..66d40d3e9cc --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java @@ -0,0 +1,37 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.log; + +/** Logging Facade + * A simple logging facade that is intended simply to capture the style + * of logging as used by Jetty. + * + */ +public interface Logger +{ + public boolean isDebugEnabled(); + + /** Mutator used to turn debug on programatically. + * Implementations operation in which case an appropriate + * warning message shall be generated. + */ + public void setDebugEnabled(boolean enabled); + + public void info(String msg,Object arg0, Object arg1); + public void debug(String msg,Throwable th); + public void debug(String msg,Object arg0, Object arg1); + public void warn(String msg,Object arg0, Object arg1); + public void warn(String msg, Throwable th); + public Logger getLogger(String name); +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java new file mode 100644 index 00000000000..534591fc72f --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java @@ -0,0 +1,154 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.log; + +import java.lang.reflect.Method; + +public class LoggerLog implements Logger +{ + boolean _debug; + Object _logger; + Method _debugMT; + Method _debugMAA; + Method _infoMAA; + Method _warnMT; + Method _warnMAA; + Method _isDebugEnabled; + Method _setDebugEnabledE; + Method _getLoggerN; + + public LoggerLog(Object logger) + { + try + { + _logger=logger; + Class lc=logger.getClass(); + _debugMT=lc.getMethod("debug",new Class[]{String.class,Throwable.class}); + _debugMAA=lc.getMethod("debug",new Class[]{String.class,Object.class,Object.class}); + _infoMAA=lc.getMethod("info",new Class[]{String.class,Object.class,Object.class}); + _warnMT=lc.getMethod("warn",new Class[]{String.class,Throwable.class}); + _warnMAA=lc.getMethod("warn",new Class[]{String.class,Object.class,Object.class}); + _isDebugEnabled=lc.getMethod("isDebugEnabled",new Class[]{}); + _setDebugEnabledE=lc.getMethod("setDebugEnabled",new Class[]{Boolean.TYPE}); + _getLoggerN=lc.getMethod("getLogger",new Class[]{String.class}); + + _debug=((Boolean)_isDebugEnabled.invoke(_logger,(Object[])null)).booleanValue(); + } + catch(Exception e) + { + e.printStackTrace(); + throw new IllegalStateException(e); + } + } + + public void debug(String msg, Throwable th) + { + if (_debug) + { + try + { + _debugMT.invoke(_logger,msg,th); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + public void debug(String msg, Object arg0, Object arg1) + { + if (_debug) + { + try + { + _debugMAA.invoke(_logger,msg,arg0,arg1); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + public Logger getLogger(String name) + { + try + { + Object logger=_getLoggerN.invoke(_logger,name); + return new LoggerLog(logger); + } + catch (Exception e) + { + e.printStackTrace(); + } + return this; + } + + public void info(String msg, Object arg0, Object arg1) + { + try + { + _infoMAA.invoke(_logger,msg,arg0,arg1); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public boolean isDebugEnabled() + { + return _debug; + } + + public void setDebugEnabled(boolean enabled) + { + try + { + _setDebugEnabledE.invoke(_logger,enabled); + _debug=enabled; + } + catch (Exception e) + { + e.printStackTrace(); + } + + } + + public void warn(String msg, Object arg0, Object arg1) + { + try + { + _warnMAA.invoke(_logger,msg,arg0,arg1); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void warn(String msg, Throwable th) + { + try + { + _warnMT.invoke(_logger,msg,th); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java new file mode 100644 index 00000000000..a8600322338 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java @@ -0,0 +1,113 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.log; + + +/* ------------------------------------------------------------ */ +/** Slf4jLog Logger + * + */ +public class Slf4jLog implements Logger +{ + private org.slf4j.Logger logger; + + + public Slf4jLog() throws Exception + { + this("org.eclipse.jetty.util.log"); + } + + public Slf4jLog(String name) + { + logger = org.slf4j.LoggerFactory.getLogger( name ); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.log.Log#doDebug(java.lang.String, java.lang.Object, java.lang.Object) + */ + public void debug(String msg, Object arg0, Object arg1) + { + logger.debug(msg, arg0, arg1); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.log.Log#doDebug(java.lang.String, java.lang.Throwable) + */ + public void debug(String msg, Throwable th) + { + logger.debug(msg, th); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.log.Log#doDebugEnabled() + */ + public boolean isDebugEnabled() + { + return logger.isDebugEnabled(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.log.Log#doInfo(java.lang.String, java.lang.Object, java.lang.Object) + */ + public void info(String msg, Object arg0, Object arg1) + { + logger.info(msg, arg0, arg1); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.log.Log#doWarn(java.lang.String, java.lang.Object, java.lang.Object) + */ + public void warn(String msg, Object arg0, Object arg1) + { + logger.warn(msg, arg0, arg1); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.log.Log#doWarn(java.lang.String, java.lang.Throwable) + */ + public void warn(String msg, Throwable th) + { + + if (th instanceof RuntimeException || th instanceof Error) + logger.error(msg, th); + else + logger.warn(msg,th); + + } + + /* ------------------------------------------------------------ */ + public Logger getLogger(String name) + { + return new Slf4jLog(name); + + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return logger.toString(); + } + + /* ------------------------------------------------------------ */ + public void setDebugEnabled(boolean enabled) + { + warn("setDebugEnabled not implemented",null,null); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java new file mode 100644 index 00000000000..cb3460fe983 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java @@ -0,0 +1,156 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.log; + +import org.eclipse.jetty.util.DateCache; + +/*-----------------------------------------------------------------------*/ +/** StdErr Logging. + * This implementation of the Logging facade sends all logs to StdErr with minimal formatting. + * + * If the system property DEBUG is set, then debug logs are printed if stderr is being used. + * + */ +public class StdErrLog implements Logger +{ + private static DateCache _dateCache; + private static boolean _debug = System.getProperty("DEBUG",null)!=null; + private String _name; + private boolean _hideStacks=false; + + static + { + try + { + _dateCache=new DateCache("yyyy-MM-dd HH:mm:ss"); + } + catch(Exception e) + { + e.printStackTrace(); + } + + } + + public StdErrLog() + { + this(null); + } + + public StdErrLog(String name) + { + this._name=name==null?"":name; + } + + public boolean isDebugEnabled() + { + return _debug; + } + + public void setDebugEnabled(boolean enabled) + { + _debug=enabled; + } + + public boolean isHideStacks() + { + return _hideStacks; + } + + public void setHideStacks(boolean hideStacks) + { + _hideStacks = hideStacks; + } + + public void info(String msg,Object arg0, Object arg1) + { + String d=_dateCache.now(); + int ms=_dateCache.lastMs(); + System.err.println(d+(ms>99?".":(ms>0?".0":".00"))+ms+":"+_name+":INFO: "+format(msg,arg0,arg1)); + } + + public void debug(String msg,Throwable th) + { + if (_debug) + { + String d=_dateCache.now(); + int ms=_dateCache.lastMs(); + System.err.println(d+(ms>99?".":(ms>0?".0":".00"))+ms+":"+_name+":DEBUG: "+msg); + if (th!=null) + { + if (_hideStacks) + System.err.println(th); + else + th.printStackTrace(); + } + } + } + + public void debug(String msg,Object arg0, Object arg1) + { + if (_debug) + { + String d=_dateCache.now(); + int ms=_dateCache.lastMs(); + System.err.println(d+(ms>99?".":(ms>0?".0":".00"))+ms+":"+_name+":DEBUG: "+format(msg,arg0,arg1)); + } + } + + public void warn(String msg,Object arg0, Object arg1) + { + String d=_dateCache.now(); + int ms=_dateCache.lastMs(); + System.err.println(d+(ms>99?".":(ms>0?".0":".00"))+ms+":"+_name+":WARN: "+format(msg,arg0,arg1)); + } + + public void warn(String msg, Throwable th) + { + String d=_dateCache.now(); + int ms=_dateCache.lastMs(); + System.err.println(d+(ms>99?".":(ms>0?".0":".00"))+ms+":"+_name+":WARN: "+msg); + if (th!=null) + { + if (_hideStacks) + System.err.println(th); + else + th.printStackTrace(); + } + } + + private String format(String msg, Object arg0, Object arg1) + { + int i0=msg.indexOf("{}"); + int i1=i0<0?-1:msg.indexOf("{}",i0+2); + + if (arg1!=null && i1>=0) + msg=msg.substring(0,i1)+arg1+msg.substring(i1+2); + if (arg0!=null && i0>=0) + msg=msg.substring(0,i0)+arg0+msg.substring(i0+2); + return msg; + } + + public Logger getLogger(String name) + { + if ((name==null && this._name==null) || + (name!=null && name.equals(this._name))) + return this; + return new StdErrLog(name); + } + + public String toString() + { + return "STDERR"+_name; + } + +} + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java new file mode 100644 index 00000000000..cc36d7ebaf3 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java @@ -0,0 +1,114 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.util.resource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; + + +/* ------------------------------------------------------------ */ +/** Bad Resource. + * + * A Resource that is returned for a bade URL. Acts as a resource + * that does not exist and throws appropriate exceptions. + * + * + */ +class BadResource extends URLResource +{ + /* ------------------------------------------------------------ */ + private String _message=null; + + /* -------------------------------------------------------- */ + BadResource(URL url, String message) + { + super(url,null); + _message=message; + } + + + /* -------------------------------------------------------- */ + public boolean exists() + { + return false; + } + + /* -------------------------------------------------------- */ + public long lastModified() + { + return -1; + } + + /* -------------------------------------------------------- */ + public boolean isDirectory() + { + return false; + } + + /* --------------------------------------------------------- */ + public long length() + { + return -1; + } + + + /* ------------------------------------------------------------ */ + public File getFile() + { + return null; + } + + /* --------------------------------------------------------- */ + public InputStream getInputStream() throws IOException + { + throw new FileNotFoundException(_message); + } + + /* --------------------------------------------------------- */ + public OutputStream getOutputStream() + throws java.io.IOException, SecurityException + { + throw new FileNotFoundException(_message); + } + + /* --------------------------------------------------------- */ + public boolean delete() + throws SecurityException + { + throw new SecurityException(_message); + } + + /* --------------------------------------------------------- */ + public boolean renameTo( Resource dest) + throws SecurityException + { + throw new SecurityException(_message); + } + + /* --------------------------------------------------------- */ + public String[] list() + { + return null; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return super.toString()+"; BadResource="+_message; + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java new file mode 100644 index 00000000000..88874209528 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java @@ -0,0 +1,363 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.util.resource; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.security.Permission; + +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +/** File Resource. + * + * Handle resources of implied or explicit file type. + * This class can check for aliasing in the filesystem (eg case + * insensitivity). By default this is turned on, or it can be controlled with the + * "org.eclipse.util.FileResource.checkAliases" system parameter. + * + * + */ +public class FileResource extends URLResource +{ + private static boolean __checkAliases; + static + { + __checkAliases= + "true".equalsIgnoreCase + (System.getProperty("org.eclipse.util.FileResource.checkAliases","true")); + + if (__checkAliases) + Log.debug("Checking Resource aliases"); + } + + /* ------------------------------------------------------------ */ + private File _file; + private transient URL _alias=null; + private transient boolean _aliasChecked=false; + + /* ------------------------------------------------------------------------------- */ + /** setCheckAliases. + * @param checkAliases True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found. + */ + public static void setCheckAliases(boolean checkAliases) + { + __checkAliases=checkAliases; + } + + /* ------------------------------------------------------------------------------- */ + /** getCheckAliases. + * @return True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found. + */ + public static boolean getCheckAliases() + { + return __checkAliases; + } + + /* -------------------------------------------------------- */ + public FileResource(URL url) + throws IOException, URISyntaxException + { + super(url,null); + + try + { + // Try standard API to convert URL to file. + _file =new File(new URI(url.toString())); + } + catch (Exception e) + { + Log.ignore(e); + try + { + // Assume that File.toURL produced unencoded chars. So try + // encoding them. + String file_url="file:"+URIUtil.encodePath(url.toString().substring(5)); + URI uri = new URI(file_url); + if (uri.getAuthority()==null) + _file = new File(uri); + else + _file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile())); + } + catch (Exception e2) + { + Log.ignore(e2); + + // Still can't get the file. Doh! try good old hack! + checkConnection(); + Permission perm = _connection.getPermission(); + _file = new File(perm==null?url.getFile():perm.getName()); + } + } + if (_file.isDirectory()) + { + if (!_urlString.endsWith("/")) + _urlString=_urlString+"/"; + } + else + { + if (_urlString.endsWith("/")) + _urlString=_urlString.substring(0,_urlString.length()-1); + } + + } + + /* -------------------------------------------------------- */ + FileResource(URL url, URLConnection connection, File file) + { + super(url,connection); + _file=file; + if (_file.isDirectory() && !_urlString.endsWith("/")) + _urlString=_urlString+"/"; + } + + /* -------------------------------------------------------- */ + public Resource addPath(String path) + throws IOException,MalformedURLException + { + URLResource r=null; + String url=null; + + path = org.eclipse.jetty.util.URIUtil.canonicalPath(path); + + if ("/".equals(path)) + return this; + else if (!isDirectory()) + { + r=(FileResource)super.addPath(path); + url=r._urlString; + } + else + { + if (path==null) + throw new MalformedURLException(); + + // treat all paths being added as relative + String rel=path; + if (path.startsWith("/")) + rel = path.substring(1); + + url=URIUtil.addPaths(_urlString,URIUtil.encodePath(rel)); + r=(URLResource)Resource.newResource(url); + } + + String encoded=URIUtil.encodePath(path); + int expected=r.toString().length()-encoded.length(); + int index = r._urlString.lastIndexOf(encoded, expected); + + if (expected!=index && ((expected-1)!=index || path.endsWith("/") || !r.isDirectory())) + { + if (!(r instanceof BadResource)) + { + ((FileResource)r)._alias=new URL(url); + ((FileResource)r)._aliasChecked=true; + } + } + return r; + } + + + /* ------------------------------------------------------------ */ + public URL getAlias() + { + if (__checkAliases && !_aliasChecked) + { + try + { + String abs=_file.getAbsolutePath(); + String can=_file.getCanonicalPath(); + + if (abs.length()!=can.length() || !abs.equals(can)) + _alias=new File(can).toURI().toURL(); + + _aliasChecked=true; + + if (_alias!=null && Log.isDebugEnabled()) + { + Log.debug("ALIAS abs="+abs); + Log.debug("ALIAS can="+can); + } + } + catch(Exception e) + { + Log.warn(Log.EXCEPTION,e); + return getURL(); + } + } + return _alias; + } + + /* -------------------------------------------------------- */ + /** + * Returns true if the resource exists. + */ + public boolean exists() + { + return _file.exists(); + } + + /* -------------------------------------------------------- */ + /** + * Returns the last modified time + */ + public long lastModified() + { + return _file.lastModified(); + } + + /* -------------------------------------------------------- */ + /** + * Returns true if the respresenetd resource is a container/directory. + */ + public boolean isDirectory() + { + return _file.isDirectory(); + } + + /* --------------------------------------------------------- */ + /** + * Return the length of the resource + */ + public long length() + { + return _file.length(); + } + + + /* --------------------------------------------------------- */ + /** + * Returns the name of the resource + */ + public String getName() + { + return _file.getAbsolutePath(); + } + + /* ------------------------------------------------------------ */ + /** + * Returns an File representing the given resource or NULL if this + * is not possible. + */ + public File getFile() + { + return _file; + } + + /* --------------------------------------------------------- */ + /** + * Returns an input stream to the resource + */ + public InputStream getInputStream() throws IOException + { + return new FileInputStream(_file); + } + + /* --------------------------------------------------------- */ + /** + * Returns an output stream to the resource + */ + public OutputStream getOutputStream() + throws java.io.IOException, SecurityException + { + return new FileOutputStream(_file); + } + + /* --------------------------------------------------------- */ + /** + * Deletes the given resource + */ + public boolean delete() + throws SecurityException + { + return _file.delete(); + } + + /* --------------------------------------------------------- */ + /** + * Rename the given resource + */ + public boolean renameTo( Resource dest) + throws SecurityException + { + if( dest instanceof FileResource) + return _file.renameTo( ((FileResource)dest)._file); + else + return false; + } + + /* --------------------------------------------------------- */ + /** + * Returns a list of resources contained in the given resource + */ + public String[] list() + { + String[] list =_file.list(); + if (list==null) + return null; + for (int i=list.length;i-->0;) + { + if (new File(_file,list[i]).isDirectory() && + !list[i].endsWith("/")) + list[i]+="/"; + } + return list; + } + + /* ------------------------------------------------------------ */ + /** Encode according to this resource type. + * File URIs are encoded. + * @param uri URI to encode. + * @return The uri unchanged. + */ + public String encode(String uri) + { + return uri; + } + + /* ------------------------------------------------------------ */ + /** + * @param o + * @return true of the object o is a {@link FileResource} pointing to the same file as this resource. + */ + public boolean equals( Object o) + { + if (this == o) + return true; + + if (null == o || ! (o instanceof FileResource)) + return false; + + FileResource f=(FileResource)o; + return f._file == _file || (null != _file && _file.equals(f._file)); + } + + /* ------------------------------------------------------------ */ + /** + * @return the hashcode. + */ + public int hashCode() + { + return null == _file ? super.hashCode() : _file.hashCode(); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java new file mode 100644 index 00000000000..3953bd02955 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java @@ -0,0 +1,329 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.util.resource; + +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +class JarFileResource extends JarResource +{ + + transient JarFile _jarFile; + transient File _file; + transient String[] _list; + transient JarEntry _entry; + transient boolean _directory; + transient String _jarUrl; + transient String _path; + transient boolean _exists; + + + /* -------------------------------------------------------- */ + JarFileResource(URL url) + { + super(url); + } + + JarFileResource(URL url, boolean useCaches) + { + super(url, useCaches); + } + + + /* ------------------------------------------------------------ */ + public synchronized void release() + { + _list=null; + _entry=null; + _file=null; + _jarFile=null; + super.release(); + } + + /* ------------------------------------------------------------ */ + protected boolean checkConnection() + { + try{ + super.checkConnection(); + } + finally + { + if (_jarConnection==null) + { + _entry=null; + _file=null; + _jarFile=null; + _list=null; + } + } + return _jarFile!=null; + } + + + /* ------------------------------------------------------------ */ + protected void newConnection() + throws IOException + { + super.newConnection(); + + _entry=null; + _file=null; + _jarFile=null; + _list=null; + + int sep = _urlString.indexOf("!/"); + _jarUrl=_urlString.substring(0,sep+2); + _path=_urlString.substring(sep+2); + if (_path.length()==0) + _path=null; + _jarFile=_jarConnection.getJarFile(); + _file=new File(_jarFile.getName()); + } + + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresenetd resource exists. + */ + public boolean exists() + { + if (_exists) + return true; + + if (_urlString.endsWith("!/")) + { + + String file_url=_urlString.substring(4,_urlString.length()-2); + try{return newResource(file_url).exists();} + catch(Exception e) {Log.ignore(e); return false;} + } + + boolean check=checkConnection(); + + // Is this a root URL? + if (_jarUrl!=null && _path==null) + { + // Then if it exists it is a directory + _directory=check; + return true; + } + else + { + // Can we find a file for it? + JarFile jarFile=null; + if (check) + // Yes + jarFile=_jarFile; + else + { + // No - so lets look if the root entry exists. + try + { + JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection()); + c.setUseCaches(getUseCaches()); + jarFile=c.getJarFile(); + } + catch(Exception e) + { + Log.ignore(e); + } + } + + // Do we need to look more closely? + if (jarFile!=null && _entry==null && !_directory) + { + // OK - we have a JarFile, lets look at the entries for our path + Enumeration e=jarFile.entries(); + while(e.hasMoreElements()) + { + JarEntry entry = (JarEntry) e.nextElement(); + String name=entry.getName().replace('\\','/'); + + // Do we have a match + if (name.equals(_path)) + { + _entry=entry; + // Is the match a directory + _directory=_path.endsWith("/"); + break; + } + else if (_path.endsWith("/")) + { + if (name.startsWith(_path)) + { + _directory=true; + break; + } + } + else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/') + { + _directory=true; + break; + } + } + } + } + + _exists= ( _directory || _entry!=null); + return _exists; + } + + + /* ------------------------------------------------------------ */ + /** + * Returns true if the represented resource is a container/directory. + * If the resource is not a file, resources ending with "/" are + * considered directories. + */ + public boolean isDirectory() + { + return _urlString.endsWith("/") || exists() && _directory; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the last modified time + */ + public long lastModified() + { + if (checkConnection() && _file!=null) + return _file.lastModified(); + return -1; + } + + /* ------------------------------------------------------------ */ + public synchronized String[] list() + { + + if(isDirectory() && _list==null) + { + ArrayList list = new ArrayList(32); + + checkConnection(); + + JarFile jarFile=_jarFile; + if(jarFile==null) + { + try + { + JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection()); + jc.setUseCaches(getUseCaches()); + jarFile=jc.getJarFile(); + } + catch(Exception e) + { + Log.ignore(e); + } + } + + Enumeration e=jarFile.entries(); + String dir=_urlString.substring(_urlString.indexOf("!/")+2); + while(e.hasMoreElements()) + { + + JarEntry entry = (JarEntry) e.nextElement(); + String name=entry.getName().replace('\\','/'); + if(!name.startsWith(dir) || name.length()==dir.length()) + { + continue; + } + String listName=name.substring(dir.length()); + int dash=listName.indexOf('/'); + if (dash>=0) + { + //when listing jar:file urls, you get back one + //entry for the dir itself, which we ignore + if (dash==0 && listName.length()==1) + continue; + //when listing jar:file urls, all files and + //subdirs have a leading /, which we remove + if (dash==0) + listName=listName.substring(dash+1, listName.length()); + else + listName=listName.substring(0,dash+1); + + if (list.contains(listName)) + continue; + } + + list.add(listName); + } + + _list=new String[list.size()]; + list.toArray(_list); + } + return _list; + } + + /* ------------------------------------------------------------ */ + /** + * Return the length of the resource + */ + public long length() + { + if (isDirectory()) + return -1; + + if (_entry!=null) + return _entry.getSize(); + + return -1; + } + + /* ------------------------------------------------------------ */ + /** Encode according to this resource type. + * File URIs are not encoded. + * @param uri URI to encode. + * @return The uri unchanged. + */ + public String encode(String uri) + { + return uri; + } + + + /** + * Take a Resource that possibly might use URLConnection caching + * and turn it into one that doesn't. + * @param resource + * @return + */ + public static Resource getNonCachingResource (Resource resource) + { + if (!(resource instanceof JarFileResource)) + return resource; + + JarFileResource oldResource = (JarFileResource)resource; + + JarFileResource newResource = new JarFileResource(oldResource.getURL(), false); + return newResource; + + } +} + + + + + + + + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java new file mode 100644 index 00000000000..21b664cae35 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java @@ -0,0 +1,242 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.util.resource; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +public class JarResource extends URLResource +{ + + protected transient JarURLConnection _jarConnection; + + /* -------------------------------------------------------- */ + JarResource(URL url) + { + super(url,null); + } + + /* ------------------------------------------------------------ */ + JarResource(URL url, boolean useCaches) + { + super(url, null, useCaches); + } + + /* ------------------------------------------------------------ */ + public synchronized void release() + { + _jarConnection=null; + super.release(); + } + + /* ------------------------------------------------------------ */ + protected boolean checkConnection() + { + super.checkConnection(); + try + { + if (_jarConnection!=_connection) + newConnection(); + } + catch(IOException e) + { + Log.ignore(e); + _jarConnection=null; + } + + return _jarConnection!=null; + } + + /* ------------------------------------------------------------ */ + /** + * @throws IOException Sub-classes of JarResource may throw an IOException (or subclass) + */ + protected void newConnection() throws IOException + { + _jarConnection=(JarURLConnection)_connection; + } + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresenetd resource exists. + */ + public boolean exists() + { + if (_urlString.endsWith("!/")) + return checkConnection(); + else + return super.exists(); + } + + /* ------------------------------------------------------------ */ + public File getFile() + throws IOException + { + return null; + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() + throws java.io.IOException + { + checkConnection(); + if (!_urlString.endsWith("!/")) + return new FilterInputStream(super.getInputStream()) + { + public void close() throws IOException {this.in=IO.getClosedStream();} + }; + + URL url = new URL(_urlString.substring(4,_urlString.length()-2)); + InputStream is = url.openStream(); + return is; + } + + /* ------------------------------------------------------------ */ + public static void extract(Resource resource, File directory, boolean deleteOnExit) + throws IOException + { + if(Log.isDebugEnabled())Log.debug("Extract "+resource+" to "+directory); + + + String urlString = resource.getURL().toExternalForm().trim(); + int endOfJarUrl = urlString.indexOf("!/"); + int startOfJarUrl = (endOfJarUrl >= 0?4:0); + + if (endOfJarUrl < 0) + throw new IOException("Not a valid jar url: "+urlString); + + URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl)); + String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null); + boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false); + + if (Log.isDebugEnabled()) Log.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL); + + InputStream is = jarFileURL.openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is); + JarEntry entry; + boolean shouldExtract; + while((entry=jin.getNextJarEntry())!=null) + { + String entryName = entry.getName(); + if ((subEntryName != null) && (entryName.startsWith(subEntryName))) + { + //if there is a particular subEntry that we are looking for, only + //extract it. + if (subEntryIsDir) + { + //if it is a subdirectory we are looking for, then we + //are looking to extract its contents into the target + //directory. Remove the name of the subdirectory so + //that we don't wind up creating it too. + entryName = entryName.substring(subEntryName.length()); + if (!entryName.equals("")) + { + //the entry is + shouldExtract = true; + } + else + shouldExtract = false; + } + else + shouldExtract = true; + } + else if ((subEntryName != null) && (!entryName.startsWith(subEntryName))) + { + //there is a particular entry we are looking for, and this one + //isn't it + shouldExtract = false; + } + else + { + //we are extracting everything + shouldExtract = true; + } + + + if (!shouldExtract) + { + if (Log.isDebugEnabled()) Log.debug("Skipping entry: "+entryName); + continue; + } + + + File file=new File(directory,entryName); + if (entry.isDirectory()) + { + // Make directory + if (!file.exists()) + file.mkdirs(); + } + else + { + // make directory (some jars don't list dirs) + File dir = new File(file.getParent()); + if (!dir.exists()) + dir.mkdirs(); + + // Make file + FileOutputStream fout = null; + try + { + fout = new FileOutputStream(file); + IO.copy(jin,fout); + } + finally + { + IO.close(fout); + } + + // touch the file. + if (entry.getTime()>=0) + file.setLastModified(entry.getTime()); + } + if (deleteOnExit) + file.deleteOnExit(); + } + + if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF"))) + { + Manifest manifest = jin.getManifest(); + if (manifest != null) + { + File metaInf = new File (directory, "META-INF"); + metaInf.mkdir(); + File f = new File(metaInf, "MANIFEST.MF"); + FileOutputStream fout = new FileOutputStream(f); + manifest.write(fout); + fout.close(); + } + } + IO.close(jin); + } + + /* ------------------------------------------------------------ */ + public void extract(File directory, boolean deleteOnExit) + throws IOException + { + extract(this,directory,deleteOnExit); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java new file mode 100644 index 00000000000..6fa6b0f97c6 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -0,0 +1,499 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.util.resource; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.text.DateFormat; +import java.util.Arrays; +import java.util.Date; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +/** Abstract resource class. + * + * + * + */ +public abstract class Resource implements Serializable +{ + public static boolean __defaultUseCaches = true; + Object _associate; + + /** + * Change the default setting for url connection caches. + * Subsequent URLConnections will use this default. + * @param useCaches + */ + public static void setDefaultUseCaches (boolean useCaches) + { + __defaultUseCaches=useCaches; + } + + public static boolean getDefaultUseCaches () + { + return __defaultUseCaches; + } + + /* ------------------------------------------------------------ */ + /** Construct a resource from a url. + * @param url A URL. + * @return A Resource object. + */ + public static Resource newResource(URL url) + throws IOException + { + return newResource(url, __defaultUseCaches); + } + + /* ------------------------------------------------------------ */ + /** + * Construct a resource from a url. + * @param url the url for which to make the resource + * @param useCaches true enables URLConnection caching if applicable to the type of resource + * @return + */ + static Resource newResource(URL url, boolean useCaches) + { + if (url==null) + return null; + + String url_string=url.toExternalForm(); + if( url_string.startsWith( "file:")) + { + try + { + FileResource fileResource= new FileResource(url); + return fileResource; + } + catch(Exception e) + { + Log.debug(Log.EXCEPTION,e); + return new BadResource(url,e.toString()); + } + } + else if( url_string.startsWith( "jar:file:")) + { + return new JarFileResource(url, useCaches); + } + else if( url_string.startsWith( "jar:")) + { + return new JarResource(url, useCaches); + } + + return new URLResource(url,null,useCaches); + } + + + + /* ------------------------------------------------------------ */ + /** Construct a resource from a string. + * @param resource A URL or filename. + * @return A Resource object. + */ + public static Resource newResource(String resource) + throws MalformedURLException, IOException + { + return newResource(resource, __defaultUseCaches); + } + + /* ------------------------------------------------------------ */ + /** Construct a resource from a string. + * @param resource A URL or filename. + * @param useCaches controls URLConnection caching + * @return A Resource object. + */ + public static Resource newResource (String resource, boolean useCaches) + throws MalformedURLException, IOException + { + URL url=null; + try + { + // Try to format as a URL? + url = new URL(resource); + } + catch(MalformedURLException e) + { + if(!resource.startsWith("ftp:") && + !resource.startsWith("file:") && + !resource.startsWith("jar:")) + { + try + { + // It's a file. + if (resource.startsWith("./")) + resource=resource.substring(2); + + File file=new File(resource).getCanonicalFile(); + url=new URL(URIUtil.encodePath(file.toURL().toString())); + + URLConnection connection=url.openConnection(); + connection.setUseCaches(useCaches); + FileResource fileResource= new FileResource(url,connection,file); + return fileResource; + } + catch(Exception e2) + { + Log.debug(Log.EXCEPTION,e2); + throw e; + } + } + else + { + Log.warn("Bad Resource: "+resource); + throw e; + } + } + + // Make sure that any special characters stripped really are ignorable. + String nurl=url.toString(); + if (nurl.length()>0 && nurl.charAt(nurl.length()-1)!=resource.charAt(resource.length()-1)) + { + if ((nurl.charAt(nurl.length()-1)!='/' || + nurl.charAt(nurl.length()-2)!=resource.charAt(resource.length()-1)) + && + (resource.charAt(resource.length()-1)!='/' || + resource.charAt(resource.length()-2)!=nurl.charAt(nurl.length()-1) + )) + { + return new BadResource(url,"Trailing special characters stripped by URL in "+resource); + } + } + return newResource(url); + } + + /* ------------------------------------------------------------ */ + /** Construct a system resource from a string. + * The resource is tried as classloader resource before being + * treated as a normal resource. + */ + public static Resource newSystemResource(String resource) + throws IOException + { + URL url=null; + // Try to format as a URL? + ClassLoader + loader=Thread.currentThread().getContextClassLoader(); + if (loader!=null) + { + url=loader.getResource(resource); + if (url==null && resource.startsWith("/")) + url=loader.getResource(resource.substring(1)); + } + if (url==null) + { + loader=Resource.class.getClassLoader(); + if (loader!=null) + { + url=loader.getResource(resource); + if (url==null && resource.startsWith("/")) + url=loader.getResource(resource.substring(1)); + } + } + + if (url==null) + { + url=ClassLoader.getSystemResource(resource); + if (url==null && resource.startsWith("/")) + url=loader.getResource(resource.substring(1)); + } + + if (url==null) + return null; + + return newResource(url); + } + + /* ------------------------------------------------------------ */ + /** Find a classpath resource. + */ + public static Resource newClassPathResource(String resource) + { + return newClassPathResource(resource,true,false); + } + + /* ------------------------------------------------------------ */ + /** Find a classpath resource. + * The {@java.lang.Class#getResource} method is used to lookup the resource. If it is not + * found, then the {@link Loader#getResource(Class, String, boolean)} method is used. + * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used. + * Unlike {@link #getSystemResource} this method does not check for normal resources. + * @param name The relative name of the resouce + * @param useCaches True if URL caches are to be used. + * @param checkParents True if forced searching of parent classloaders is performed to work around + * loaders with inverted priorities + * @return Resource or null + */ + public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents) + { + URL url=Resource.class.getResource(name); + + if (url==null) + { + try + { + url=Loader.getResource(Resource.class,name,checkParents); + } + catch(ClassNotFoundException e) + { + url=ClassLoader.getSystemResource(name); + } + } + if (url==null) + return null; + return newResource(url,useCaches); + } + + + + /* ------------------------------------------------------------ */ + protected void finalize() + { + release(); + } + + /* ------------------------------------------------------------ */ + /** Release any resources held by the resource. + */ + public abstract void release(); + + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresened resource exists. + */ + public abstract boolean exists(); + + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresenetd resource is a container/directory. + * If the resource is not a file, resources ending with "/" are + * considered directories. + */ + public abstract boolean isDirectory(); + + /* ------------------------------------------------------------ */ + /** + * Returns the last modified time + */ + public abstract long lastModified(); + + + /* ------------------------------------------------------------ */ + /** + * Return the length of the resource + */ + public abstract long length(); + + + /* ------------------------------------------------------------ */ + /** + * Returns an URL representing the given resource + */ + public abstract URL getURL(); + + + /* ------------------------------------------------------------ */ + /** + * Returns an File representing the given resource or NULL if this + * is not possible. + */ + public abstract File getFile() + throws IOException; + + + /* ------------------------------------------------------------ */ + /** + * Returns the name of the resource + */ + public abstract String getName(); + + + /* ------------------------------------------------------------ */ + /** + * Returns an input stream to the resource + */ + public abstract InputStream getInputStream() + throws java.io.IOException; + + /* ------------------------------------------------------------ */ + /** + * Returns an output stream to the resource + */ + public abstract OutputStream getOutputStream() + throws java.io.IOException, SecurityException; + + /* ------------------------------------------------------------ */ + /** + * Deletes the given resource + */ + public abstract boolean delete() + throws SecurityException; + + /* ------------------------------------------------------------ */ + /** + * Rename the given resource + */ + public abstract boolean renameTo( Resource dest) + throws SecurityException; + + /* ------------------------------------------------------------ */ + /** + * Returns a list of resource names contained in the given resource + * The resource names are not URL encoded. + */ + public abstract String[] list(); + + /* ------------------------------------------------------------ */ + /** + * Returns the resource contained inside the current resource with the + * given name. + * @param path The path segment to add, which should be encoded by the + * encode method. + */ + public abstract Resource addPath(String path) + throws IOException,MalformedURLException; + + + /* ------------------------------------------------------------ */ + /** Encode according to this resource type. + * The default implementation calls URI.encodePath(uri) + * @param uri + * @return String encoded for this resource type. + */ + public String encode(String uri) + { + return URIUtil.encodePath(uri); + } + + /* ------------------------------------------------------------ */ + public Object getAssociate() + { + return _associate; + } + + /* ------------------------------------------------------------ */ + public void setAssociate(Object o) + { + _associate=o; + } + + /* ------------------------------------------------------------ */ + /** + * @return The canonical Alias of this resource or null if none. + */ + public URL getAlias() + { + return null; + } + + /* ------------------------------------------------------------ */ + /** Get the resource list as a HTML directory listing. + * @param base The base URL + * @param parent True if the parent directory should be included + * @return String of HTML + */ + public String getListHTML(String base, + boolean parent) + throws IOException + { + if (!isDirectory()) + return null; + + String[] ls = list(); + if (ls==null) + return null; + Arrays.sort(ls); + + String decodedBase = URIUtil.decodePath(base); + String title = "Directory: "+decodedBase; + + StringBuilder buf=new StringBuilder(4096); + buf.append(""); + buf.append(title); + buf.append("\n

    "); + buf.append(title); + buf.append("

    "); + + if (parent) + { + buf.append("\n"); + } + + DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM, + DateFormat.MEDIUM); + for (int i=0 ; i< ls.length ; i++) + { + String encoded=URIUtil.encodePath(ls[i]); + Resource item = addPath(ls[i]); + + buf.append("\n"); + } + buf.append("
    Parent Directory
    "); + buf.append(StringUtil.replace(StringUtil.replace(ls[i],"<","<"),">",">")); + buf.append(" "); + buf.append(""); + buf.append(item.length()); + buf.append(" bytes "); + buf.append(dfmt.format(new Date(item.lastModified()))); + buf.append("
    \n"); + buf.append("\n"); + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param out + * @param start First byte to write + * @param count Bytes to write or -1 for all of them. + */ + public void writeTo(OutputStream out,long start,long count) + throws IOException + { + InputStream in = getInputStream(); + try + { + in.skip(start); + if (count<0) + IO.copy(in,out); + else + IO.copy(in,out,count); + } + finally + { + in.close(); + } + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java new file mode 100644 index 00000000000..ccb4f577fc3 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java @@ -0,0 +1,442 @@ +// ======================================================================== +// Copyright (c) 2007-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.resource; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.StringTokenizer; + +import org.eclipse.jetty.util.URIUtil; + +/** + * A collection of resources (dirs). + * Allows webapps to have multiple (static) sources. + * The first resource in the collection is the main resource. + * If a resource is not found in the main resource, it looks it up in + * the order the resources were constructed. + * + * + * + */ +public class ResourceCollection extends Resource +{ + + private Resource[] _resources; + + public ResourceCollection() + { + + } + + /* ------------------------------------------------------------ */ + public ResourceCollection(Resource[] resources) + { + setResources(resources); + } + + /* ------------------------------------------------------------ */ + public ResourceCollection(String[] resources) + { + setResources(resources); + } + + /* ------------------------------------------------------------ */ + public ResourceCollection(String csvResources) + { + setResources(csvResources); + } + + /* ------------------------------------------------------------ */ + /** + * + * @param resources Resource array + */ + public void setResources(Resource[] resources) + { + if(_resources!=null) + throw new IllegalStateException("*resources* already set."); + + if(resources==null) + throw new IllegalArgumentException("*resources* must not be null."); + + if(resources.length==0) + throw new IllegalArgumentException("arg *resources* must be one or more resources."); + + _resources = resources; + for(Resource r : _resources) + { + if(!r.exists() || !r.isDirectory()) + throw new IllegalArgumentException(r + " is not an existing directory."); + } + } + + /* ------------------------------------------------------------ */ + /** + * + * @param resources String array + */ + public void setResources(String[] resources) + { + if(_resources!=null) + throw new IllegalStateException("*resources* already set."); + + if(resources==null) + throw new IllegalArgumentException("*resources* must not be null."); + + if(resources.length==0) + throw new IllegalArgumentException("arg *resources* must be one or more resources."); + + _resources = new Resource[resources.length]; + try + { + for(int i=0; i resources = null; + int i=0; + for(; i<_resources.length; i++) + { + resource = _resources[i].addPath(path); + if (resource.exists()) + { + if (resource.isDirectory()) + break; + return resource; + } + } + + for(i++; i<_resources.length; i++) + { + Resource r = _resources[i].addPath(path); + if (r.exists() && r.isDirectory()) + { + if (resource!=null) + { + resources = new ArrayList(); + resources.add(resource); + resource=null; + } + resources.add(r); + } + } + + if (resource!=null) + return resource; + if (resources!=null) + return new ResourceCollection(resources.toArray(new Resource[resources.size()])); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @param path + * @return the resource(file) if found, returns a list of resource dirs if its a dir, else null. + * @throws IOException + * @throws MalformedURLException + */ + protected Object findResource(String path) throws IOException, MalformedURLException + { + Resource resource=null; + ArrayList resources = null; + int i=0; + for(; i<_resources.length; i++) + { + resource = _resources[i].addPath(path); + if (resource.exists()) + { + if (resource.isDirectory()) + break; + + return resource; + } + } + + for(i++; i<_resources.length; i++) + { + Resource r = _resources[i].addPath(path); + if (r.exists() && r.isDirectory()) + { + if (resource!=null) + { + resources = new ArrayList(); + resources.add(resource); + } + resources.add(r); + } + } + + if (resource!=null) + return resource; + if (resources!=null) + return resources; + return null; + } + + /* ------------------------------------------------------------ */ + public boolean delete() throws SecurityException + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + public boolean exists() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + return true; + } + + /* ------------------------------------------------------------ */ + public File getFile() throws IOException + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + File f = r.getFile(); + if(f!=null) + return f; + } + return null; + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() throws IOException + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + InputStream is = r.getInputStream(); + if(is!=null) + return is; + } + return null; + } + + /* ------------------------------------------------------------ */ + public String getName() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + String name = r.getName(); + if(name!=null) + return name; + } + return null; + } + + /* ------------------------------------------------------------ */ + public OutputStream getOutputStream() throws IOException, SecurityException + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + OutputStream os = r.getOutputStream(); + if(os!=null) + return os; + } + return null; + } + + /* ------------------------------------------------------------ */ + public URL getURL() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + URL url = r.getURL(); + if(url!=null) + return url; + } + return null; + } + + /* ------------------------------------------------------------ */ + public boolean isDirectory() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + return true; + } + + /* ------------------------------------------------------------ */ + public long lastModified() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + long lm = r.lastModified(); + if (lm!=-1) + return lm; + } + return -1; + } + + /* ------------------------------------------------------------ */ + public long length() + { + return -1; + } + + /* ------------------------------------------------------------ */ + /** + * @return The list of resource names(merged) contained in the collection of resources. + */ + public String[] list() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + HashSet set = new HashSet(); + for(Resource r : _resources) + { + for(String s : r.list()) + set.add(s); + } + return set.toArray(new String[set.size()]); + } + + /* ------------------------------------------------------------ */ + public void release() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + r.release(); + } + + /* ------------------------------------------------------------ */ + public boolean renameTo(Resource dest) throws SecurityException + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + /** + * @return the list of resources separated by a path separator + */ + public String toString() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + StringBuilder buffer = new StringBuilder(); + for(Resource r : _resources) + buffer.append(r.toString()).append(';'); + return buffer.toString(); + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java new file mode 100644 index 00000000000..3de1539930f --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -0,0 +1,25 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.resource; + + +/* ------------------------------------------------------------ */ +/** ResourceFactory. + * + * + */ +public interface ResourceFactory +{ + Resource getResource(String path); +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java new file mode 100644 index 00000000000..64e45b7729f --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java @@ -0,0 +1,292 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.util.resource; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.security.Permission; + +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Abstract resource class. + * + * + * + */ +public class URLResource extends Resource +{ + + + protected URL _url; + protected String _urlString; + protected transient URLConnection _connection; + protected transient InputStream _in=null; + transient boolean _useCaches = Resource.__defaultUseCaches; + + /* ------------------------------------------------------------ */ + protected URLResource(URL url, URLConnection connection) + { + _url = url; + _urlString=_url.toString(); + _connection=connection; + } + + protected URLResource (URL url, URLConnection connection, boolean useCaches) + { + this (url, connection); + _useCaches = useCaches; + } + + /* ------------------------------------------------------------ */ + protected synchronized boolean checkConnection() + { + if (_connection==null) + { + try{ + _connection=_url.openConnection(); + _connection.setUseCaches(_useCaches); + } + catch(IOException e) + { + Log.ignore(e); + } + } + return _connection!=null; + } + + /* ------------------------------------------------------------ */ + /** Release any resources held by the resource. + */ + public synchronized void release() + { + if (_in!=null) + { + try{_in.close();}catch(IOException e){Log.ignore(e);} + _in=null; + } + + if (_connection!=null) + _connection=null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns true if the represented resource exists. + */ + public boolean exists() + { + try + { + synchronized(this) + { + if (checkConnection() && _in==null ) + _in = _connection.getInputStream(); + } + } + catch (IOException e) + { + Log.ignore(e); + } + return _in!=null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresenetd resource is a container/directory. + * If the resource is not a file, resources ending with "/" are + * considered directories. + */ + public boolean isDirectory() + { + return exists() && _url.toString().endsWith("/"); + } + + + /* ------------------------------------------------------------ */ + /** + * Returns the last modified time + */ + public long lastModified() + { + if (checkConnection()) + return _connection.getLastModified(); + return -1; + } + + + /* ------------------------------------------------------------ */ + /** + * Return the length of the resource + */ + public long length() + { + if (checkConnection()) + return _connection.getContentLength(); + return -1; + } + + /* ------------------------------------------------------------ */ + /** + * Returns an URL representing the given resource + */ + public URL getURL() + { + return _url; + } + + /* ------------------------------------------------------------ */ + /** + * Returns an File representing the given resource or NULL if this + * is not possible. + */ + public File getFile() + throws IOException + { + // Try the permission hack + if (checkConnection()) + { + Permission perm = _connection.getPermission(); + if (perm instanceof java.io.FilePermission) + return new File(perm.getName()); + } + + // Try the URL file arg + try {return new File(_url.getFile());} + catch(Exception e) {Log.ignore(e);} + + // Don't know the file + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the name of the resource + */ + public String getName() + { + return _url.toExternalForm(); + } + + /* ------------------------------------------------------------ */ + /** + * Returns an input stream to the resource + */ + public synchronized InputStream getInputStream() + throws java.io.IOException + { + if (!checkConnection()) + throw new IOException( "Invalid resource"); + + try + { + if( _in != null) + { + InputStream in = _in; + _in=null; + return in; + } + return _connection.getInputStream(); + } + finally + { + _connection=null; + } + } + + + /* ------------------------------------------------------------ */ + /** + * Returns an output stream to the resource + */ + public OutputStream getOutputStream() + throws java.io.IOException, SecurityException + { + throw new IOException( "Output not supported"); + } + + /* ------------------------------------------------------------ */ + /** + * Deletes the given resource + */ + public boolean delete() + throws SecurityException + { + throw new SecurityException( "Delete not supported"); + } + + /* ------------------------------------------------------------ */ + /** + * Rename the given resource + */ + public boolean renameTo( Resource dest) + throws SecurityException + { + throw new SecurityException( "RenameTo not supported"); + } + + /* ------------------------------------------------------------ */ + /** + * Returns a list of resource names contained in the given resource + */ + public String[] list() + { + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the resource contained inside the current resource with the + * given name + */ + public Resource addPath(String path) + throws IOException,MalformedURLException + { + if (path==null) + return null; + + path = URIUtil.canonicalPath(path); + + return newResource(URIUtil.addPaths(_url.toExternalForm(),path)); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return _urlString; + } + + /* ------------------------------------------------------------ */ + public int hashCode() + { + return _url.hashCode(); + } + + /* ------------------------------------------------------------ */ + public boolean equals( Object o) + { + return o instanceof URLResource && + _url.equals(((URLResource)o)._url); + } + + public boolean getUseCaches () + { + return _useCaches; + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java new file mode 100644 index 00000000000..e2fff3131e0 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java @@ -0,0 +1,159 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.thread; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Jetty ThreadPool using java 5 ThreadPoolExecutor + * This class wraps a {@link ExecutorService} as a {@link ThreadPool} and + * {@link LifeCycle} interfaces so that it may be used by the Jetty {@link org.eclipse.jetty.Server} + * + * + * + */ +public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, LifeCycle +{ + private final ExecutorService _executor; + + /* ------------------------------------------------------------ */ + public ExecutorThreadPool(ExecutorService executor) + { + _executor=executor; + } + + /* ------------------------------------------------------------ */ + /** constructor. + * Wraps an {@link ThreadPoolExecutor}. + * Core size is 32, max pool size is 256, pool thread timeout after 60 seconds and + * an unbounded {@link LinkedBlockingQueue} is used for the job queue; + */ + public ExecutorThreadPool() + { + this(new ThreadPoolExecutor(32,256,60,TimeUnit.SECONDS,new LinkedBlockingQueue())); + } + + /* ------------------------------------------------------------ */ + /** constructor. + * Wraps an {@link ThreadPoolExecutor}. + * Core size is 32, max pool size is 256, pool thread timeout after 60 seconds + * @param queueSize if -1, an unbounded {@link LinkedBlockingQueue} is used, if 0 then a + * {@link SynchronousQueue} is used, other a {@link ArrayBlockingQueue} of the given size is used. + */ + public ExecutorThreadPool(int queueSize) + { + this(new ThreadPoolExecutor(32,256,60,TimeUnit.SECONDS, + queueSize<0?new LinkedBlockingQueue() + : (queueSize==0?new SynchronousQueue() + :new ArrayBlockingQueue(queueSize)))); + } + + /* ------------------------------------------------------------ */ + /** constructor. + * Wraps an {@link ThreadPoolExecutor} using + * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue; + */ + public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) + { + this(new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,new LinkedBlockingQueue())); + } + + /* ------------------------------------------------------------ */ + public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) + { + this(new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue)); + } + + + /* ------------------------------------------------------------ */ + public boolean dispatch(Runnable job) + { + try + { + _executor.execute(job); + return true; + } + catch(RejectedExecutionException e) + { + Log.warn(e); + return false; + } + } + + /* ------------------------------------------------------------ */ + public int getIdleThreads() + { + if (_executor instanceof ThreadPoolExecutor) + { + final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; + return tpe.getPoolSize() -tpe.getActiveCount(); + } + return -1; + } + + /* ------------------------------------------------------------ */ + public int getThreads() + { + if (_executor instanceof ThreadPoolExecutor) + { + final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; + return tpe.getPoolSize(); + } + return -1; + } + + /* ------------------------------------------------------------ */ + public boolean isLowOnThreads() + { + if (_executor instanceof ThreadPoolExecutor) + { + final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; + return tpe.getActiveCount()>=tpe.getMaximumPoolSize(); + } + return false; + } + + /* ------------------------------------------------------------ */ + public void join() throws InterruptedException + { + _executor.awaitTermination(Long.MAX_VALUE,TimeUnit.MILLISECONDS); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + super.doStart(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + _executor.shutdownNow(); + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/OldQueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/OldQueuedThreadPool.java new file mode 100644 index 00000000000..2999f020cb5 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/OldQueuedThreadPool.java @@ -0,0 +1,608 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.thread; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** A pool of threads. + *

    + * Avoids the expense of thread creation by pooling threads after + * their run methods exit for reuse. + *

    + * If an idle thread is available a job is directly dispatched, + * otherwise the job is queued. After queuing a job, if the total + * number of threads is less than the maximum pool size, a new thread + * is spawned. + *

    + * + */ +public class OldQueuedThreadPool extends AbstractLifeCycle implements Serializable, ThreadPool +{ + private static int __id; + + private String _name; + private Set _threads; + private List _idle; + private Runnable[] _jobs; + private int _nextJob; + private int _nextJobSlot; + private int _queued; + private int _maxQueued; + + private boolean _daemon; + private int _id; + + private final Object _lock = new Lock(); + private final Object _threadsLock = new Lock(); + private final Object _joinLock = new Lock(); + + private long _lastShrink; + private int _maxIdleTimeMs=60000; + private int _maxThreads=250; + private int _minThreads=2; + private boolean _warned=false; + private int _lowThreads=0; + private int _priority= Thread.NORM_PRIORITY; + private int _spawnOrShrinkAt=0; + private int _maxStopTimeMs; + + + /* ------------------------------------------------------------------- */ + /* Construct + */ + public OldQueuedThreadPool() + { + _name="qtp"+__id++; + } + + /* ------------------------------------------------------------------- */ + /* Construct + */ + public OldQueuedThreadPool(int maxThreads) + { + this(); + setMaxThreads(maxThreads); + } + + /* ------------------------------------------------------------ */ + /** Run job. + * @return true + */ + public boolean dispatch(Runnable job) + { + if (!isRunning() || job==null) + return false; + + PoolThread thread=null; + boolean spawn=false; + + synchronized(_lock) + { + // Look for an idle thread + int idle=_idle.size(); + if (idle>0) + thread=(PoolThread)_idle.remove(idle-1); + else + { + // queue the job + _queued++; + if (_queued>_maxQueued) + _maxQueued=_queued; + _jobs[_nextJobSlot++]=job; + if (_nextJobSlot==_jobs.length) + _nextJobSlot=0; + if (_nextJobSlot==_nextJob) + { + // Grow the job queue + Runnable[] jobs= new Runnable[_jobs.length+_maxThreads]; + int split=_jobs.length-_nextJob; + if (split>0) + System.arraycopy(_jobs,_nextJob,jobs,0,split); + if (_nextJob!=0) + System.arraycopy(_jobs,0,jobs,split,_nextJobSlot); + + _jobs=jobs; + _nextJob=0; + _nextJobSlot=_queued; + } + + spawn=_queued>_spawnOrShrinkAt; + } + } + + if (thread!=null) + { + thread.dispatch(job); + } + else if (spawn) + { + newThread(); + } + return true; + } + + /* ------------------------------------------------------------ */ + /** Get the number of idle threads in the pool. + * @see #getThreads + * @return Number of threads + */ + public int getIdleThreads() + { + return _idle==null?0:_idle.size(); + } + + /* ------------------------------------------------------------ */ + /** + * @return low resource threads threshhold + */ + public int getLowThreads() + { + return _lowThreads; + } + + /* ------------------------------------------------------------ */ + /** + * @return maximum queue size + */ + public int getMaxQueued() + { + return _maxQueued; + } + + /* ------------------------------------------------------------ */ + /** Get the maximum thread idle time. + * Delegated to the named or anonymous Pool. + * @see #setMaxIdleTimeMs + * @return Max idle time in ms. + */ + public int getMaxIdleTimeMs() + { + return _maxIdleTimeMs; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum number of threads. + * Delegated to the named or anonymous Pool. + * @see #setMaxThreads + * @return maximum number of threads. + */ + public int getMaxThreads() + { + return _maxThreads; + } + + /* ------------------------------------------------------------ */ + /** Get the minimum number of threads. + * Delegated to the named or anonymous Pool. + * @see #setMinThreads + * @return minimum number of threads. + */ + public int getMinThreads() + { + return _minThreads; + } + + /* ------------------------------------------------------------ */ + /** + * @return The name of the BoundedThreadPool. + */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** Get the number of threads in the pool. + * @see #getIdleThreads + * @return Number of threads + */ + public int getThreads() + { + return _threads.size(); + } + + /* ------------------------------------------------------------ */ + /** Get the priority of the pool threads. + * @return the priority of the pool threads. + */ + public int getThreadsPriority() + { + return _priority; + } + + /* ------------------------------------------------------------ */ + public int getQueueSize() + { + return _queued; + } + + /* ------------------------------------------------------------ */ + /** + * @return the spawnOrShrinkAt The number of queued jobs (or idle threads) needed + * before the thread pool is grown (or shrunk) + */ + public int getSpawnOrShrinkAt() + { + return _spawnOrShrinkAt; + } + + /* ------------------------------------------------------------ */ + /** + * @param spawnOrShrinkAt The number of queued jobs (or idle threads) needed + * before the thread pool is grown (or shrunk) + */ + public void setSpawnOrShrinkAt(int spawnOrShrinkAt) + { + _spawnOrShrinkAt=spawnOrShrinkAt; + } + + /* ------------------------------------------------------------ */ + /** + * @return maximum total time that stop() will wait for threads to die. + */ + public int getMaxStopTimeMs() + { + return _maxStopTimeMs; + } + + /* ------------------------------------------------------------ */ + /** + * @param stopTimeMs maximum total time that stop() will wait for threads to die. + */ + public void setMaxStopTimeMs(int stopTimeMs) + { + _maxStopTimeMs = stopTimeMs; + } + + /* ------------------------------------------------------------ */ + /** + * Delegated to the named or anonymous Pool. + */ + public boolean isDaemon() + { + return _daemon; + } + + /* ------------------------------------------------------------ */ + public boolean isLowOnThreads() + { + return _queued>_lowThreads; + } + + /* ------------------------------------------------------------ */ + public void join() throws InterruptedException + { + synchronized (_joinLock) + { + while (isRunning()) + _joinLock.wait(); + } + + // TODO remove this semi busy loop! + while (isStopping()) + Thread.sleep(100); + } + + /* ------------------------------------------------------------ */ + /** + * Delegated to the named or anonymous Pool. + */ + public void setDaemon(boolean daemon) + { + _daemon=daemon; + } + + /* ------------------------------------------------------------ */ + /** + * @param lowThreads low resource threads threshhold + */ + public void setLowThreads(int lowThreads) + { + _lowThreads = lowThreads; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum thread idle time. + * Threads that are idle for longer than this period may be + * stopped. + * Delegated to the named or anonymous Pool. + * @see #getMaxIdleTimeMs + * @param maxIdleTimeMs Max idle time in ms. + */ + public void setMaxIdleTimeMs(int maxIdleTimeMs) + { + _maxIdleTimeMs=maxIdleTimeMs; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum number of threads. + * Delegated to the named or anonymous Pool. + * @see #getMaxThreads + * @param maxThreads maximum number of threads. + */ + public void setMaxThreads(int maxThreads) + { + if (isStarted() && maxThreads<_minThreads) + throw new IllegalArgumentException("!minThreads_maxThreads)) + throw new IllegalArgumentException("!0<=minThreads0 && _maxStopTimeMs < (System.currentTimeMillis()-start))) + break; + + try + { + Thread.sleep(i*100); + } + catch(InterruptedException e){} + + + } + + // TODO perhaps force stops + if (_threads.size()>0) + Log.warn(_threads.size()+" threads could not be stopped"); + + synchronized (_joinLock) + { + _joinLock.notifyAll(); + } + } + + /* ------------------------------------------------------------ */ + protected void newThread() + { + synchronized (_threadsLock) + { + if (_threads.size()<_maxThreads) + { + PoolThread thread =new PoolThread(); + _threads.add(thread); + thread.setName(thread.getId()+"@"+_name+"-"+_id++); + thread.start(); + } + else if (!_warned) + { + _warned=true; + Log.debug("Max threads for {}",this); + } + } + } + + /* ------------------------------------------------------------ */ + /** Stop a Job. + * This method is called by the Pool if a job needs to be stopped. + * The default implementation does nothing and should be extended by a + * derived thread pool class if special action is required. + * @param thread The thread allocated to the job, or null if no thread allocated. + * @param job The job object passed to run. + */ + protected void stopJob(Thread thread, Object job) + { + thread.interrupt(); + } + + + /* ------------------------------------------------------------ */ + /** Pool Thread class. + * The PoolThread allows the threads job to be + * retrieved and active status to be indicated. + */ + public class PoolThread extends Thread + { + Runnable _job=null; + + /* ------------------------------------------------------------ */ + PoolThread() + { + setDaemon(_daemon); + setPriority(_priority); + } + + /* ------------------------------------------------------------ */ + /** BoundedThreadPool run. + * Loop getting jobs and handling them until idle or stopped. + */ + public void run() + { + boolean idle=false; + Runnable job=null; + try + { + while (isRunning()) + { + // Run any job that we have. + if (job!=null) + { + final Runnable todo=job; + job=null; + idle=false; + todo.run(); + } + + synchronized(_lock) + { + // is there a queued job? + if (_queued>0) + { + _queued--; + job=_jobs[_nextJob++]; + if (_nextJob==_jobs.length) + _nextJob=0; + continue; + } + + // Should we shrink? + final int threads=_threads.size(); + if (threads>_minThreads && + (threads>_maxThreads || + _idle.size()>_spawnOrShrinkAt)) + { + long now = System.currentTimeMillis(); + if ((now-_lastShrink)>getMaxIdleTimeMs()) + { + _lastShrink=now; + _idle.remove(this); + return; + } + } + + if (!idle) + { + // Add ourselves to the idle set. + _idle.add(this); + idle=true; + } + } + + // We are idle + // wait for a dispatched job + synchronized (this) + { + if (_job==null) + this.wait(getMaxIdleTimeMs()); + job=_job; + _job=null; + } + } + } + catch (InterruptedException e) + { + Log.ignore(e); + } + finally + { + synchronized (_lock) + { + _idle.remove(this); + } + synchronized (_threadsLock) + { + _threads.remove(this); + } + synchronized (this) + { + job=_job; + } + + // we died with a job! reschedule it + if (job!=null) + { + OldQueuedThreadPool.this.dispatch(job); + } + } + } + + /* ------------------------------------------------------------ */ + void dispatch(Runnable job) + { + synchronized (this) + { + _job=job; + this.notify(); + } + } + } + + private class Lock{} +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java new file mode 100644 index 00000000000..9b2a81beb11 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java @@ -0,0 +1,463 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.util.thread; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; + + +public class QueuedThreadPool extends AbstractLifeCycle implements ThreadPool, Executor +{ + private final AtomicInteger _threadsStarted = new AtomicInteger(); + private final AtomicInteger _threadsIdle = new AtomicInteger(); + private final AtomicLong _lastShrink = new AtomicLong(); + private final ConcurrentLinkedQueue _threads=new ConcurrentLinkedQueue(); + private final Object _joinLock = new Object(); + private BlockingArrayQueue _jobs; + private String _name; + private int _maxIdleTimeMs=60000; + private int _maxThreads=254; + private int _minThreads=8; + private int _maxQueued=-1; + private int _priority=Thread.NORM_PRIORITY; + private boolean _daemon=false; + private int _maxStopTime=100; + + /* ------------------------------------------------------------------- */ + /* Construct + */ + public QueuedThreadPool() + { + _name="qtp"+super.hashCode(); + } + + /* ------------------------------------------------------------------- */ + /* Construct + */ + public QueuedThreadPool(int maxThreads) + { + this(); + setMaxThreads(maxThreads); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + super.doStart(); + _threadsStarted.set(0); + + _jobs=_maxQueued>0 ?new BlockingArrayQueue(_minThreads,_minThreads,_maxQueued) + :new BlockingArrayQueue(_minThreads,_minThreads); + + int threads=_threadsStarted.get(); + while (isRunning() && threads<_minThreads) + { + startThread(threads); + threads=_threadsStarted.get(); + } + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + long start=System.currentTimeMillis(); + + // let jobs complete naturally for a while + while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < (_maxStopTime/2)) + Thread.sleep(1); + + // kill queued jobs and flush out idle jobs + _jobs.clear(); + Runnable noop = new Runnable(){public void run(){}}; + for (int i=_threadsIdle.get();i-->0;) + _jobs.offer(noop); + Thread.yield(); + + // interrupt remaining threads + if (_threadsStarted.get()>0) + for (Thread thread : _threads) + thread.interrupt(); + + // wait for remaining threads to die + while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < _maxStopTime) + { + Thread.sleep(1); + } + + if (_threads.size()>0) + Log.warn(_threads.size()+" threads could not be stopped"); + + synchronized (_joinLock) + { + _joinLock.notifyAll(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Delegated to the named or anonymous Pool. + */ + public void setDaemon(boolean daemon) + { + _daemon=daemon; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum thread idle time. + * Threads that are idle for longer than this period may be + * stopped. + * Delegated to the named or anonymous Pool. + * @see #getMaxIdleTimeMs + * @param maxIdleTimeMs Max idle time in ms. + */ + public void setMaxIdleTimeMs(int maxIdleTimeMs) + { + _maxIdleTimeMs=maxIdleTimeMs; + } + + /* ------------------------------------------------------------ */ + /** + * @param stopTimeMs maximum total time that stop() will wait for threads to die. + */ + public void setMaxStopTimeMs(int stopTimeMs) + { + _maxStopTime = stopTimeMs; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum number of threads. + * Delegated to the named or anonymous Pool. + * @see #getMaxThreads + * @param maxThreads maximum number of threads. + */ + public void setMaxThreads(int maxThreads) + { + if (isStarted() && maxThreads<_minThreads) + throw new IllegalArgumentException("!minThreads_maxThreads)) + throw new IllegalArgumentException("!0<=minThreadsidle) + { + int threads=_threadsStarted.get(); + if (threads<_maxThreads) + startThread(threads); + } + return true; + } + } + return false; + } + + /* ------------------------------------------------------------ */ + public void execute(Runnable job) + { + if (!dispatch(job)) + throw new RejectedExecutionException(); + } + + /* ------------------------------------------------------------ */ + /** + * Blocks until the thread pool is {@link LifeCycle#stop stopped}. + */ + public void join() throws InterruptedException + { + synchronized (_joinLock) + { + while (isRunning()) + _joinLock.wait(); + } + + while (isStopping()) + Thread.sleep(1); + } + + /* ------------------------------------------------------------ */ + /** + * @return The total number of threads currently in the pool + */ + public int getThreads() + { + return _threadsStarted.get(); + } + + /* ------------------------------------------------------------ */ + /** + * @return The number of idle threads in the pool + */ + public int getIdleThreads() + { + return _threadsIdle.get(); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if the pool is at maxThreads and there are more queued jobs than idle threads + */ + public boolean isLowOnThreads() + { + return _threadsStarted.get()==_maxThreads && _jobs.size()>_threadsIdle.get(); + } + + /* ------------------------------------------------------------ */ + private boolean startThread(int threads) + { + final int next=threads+1; + if (!_threadsStarted.compareAndSet(threads,next)) + return false; + + boolean started=false; + try + { + Thread thread=newThread(_runnable); + thread.setDaemon(_daemon); + thread.setPriority(_priority); + thread.setName(_name+"-"+thread.getId()); + _threads.add(thread); + + thread.start(); + started=true; + } + finally + { + if (!started) + _threadsStarted.decrementAndGet(); + } + return started; + } + + /* ------------------------------------------------------------ */ + protected Thread newThread(Runnable runnable) + { + return new Thread(runnable); + } + + /* ------------------------------------------------------------ */ + private Runnable _runnable = new Runnable() + { + public void run() + { + boolean shrink=false; + try + { + Runnable job=_jobs.poll(); + while (isRunning()) + { + // Job loop + while (job!=null && isRunning()) + { + job.run(); + job=_jobs.poll(); + } + + // Idle loop + try + { + _threadsIdle.incrementAndGet(); + + while (isRunning() && job==null) + { + if (_maxIdleTimeMs<=0) + job=_jobs.take(); + else + { + job=_jobs.poll(_maxIdleTimeMs,TimeUnit.MILLISECONDS); + + if (job==null) + { + // maybe we should shrink? + final int size=_threadsStarted.get(); + if (size>_minThreads) + { + long last=_lastShrink.get(); + long now=System.currentTimeMillis(); + if (last==0 || (now-last)>_maxIdleTimeMs) + { + shrink=_lastShrink.compareAndSet(last,now) && + _threadsStarted.compareAndSet(size,size-1); + if (shrink) + return; + } + } + } + } + } + } + finally + { + _threadsIdle.decrementAndGet(); + } + } + } + catch(InterruptedException e) + { + Log.ignore(e); + } + catch(Exception e) + { + Log.warn(e); + } + finally + { + if (!shrink) + _threadsStarted.decrementAndGet(); + _threads.remove(Thread.currentThread()); + } + } + }; +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java new file mode 100644 index 00000000000..d44790fb129 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java @@ -0,0 +1,51 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.thread; + +import org.eclipse.jetty.util.component.LifeCycle; + +/* ------------------------------------------------------------ */ +/** ThreadPool. + * + * + */ +public interface ThreadPool +{ + /* ------------------------------------------------------------ */ + public abstract boolean dispatch(Runnable job); + + /* ------------------------------------------------------------ */ + /** + * Blocks until the thread pool is {@link LifeCycle#stop stopped}. + */ + public void join() throws InterruptedException; + + /* ------------------------------------------------------------ */ + /** + * @return The total number of threads currently in the pool + */ + public int getThreads(); + + /* ------------------------------------------------------------ */ + /** + * @return The number of idle threads in the pool + */ + public int getIdleThreads(); + + /* ------------------------------------------------------------ */ + /** + * @return True if the pool is low on threads + */ + public boolean isLowOnThreads(); +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java new file mode 100644 index 00000000000..993e8cfac7b --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java @@ -0,0 +1,387 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.thread; + +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +/** Timeout queue. + * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire. + * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration + * is changed, this affects all scheduled tasks. + *

    + * The nested class Task should be extended by users of this class to obtain call back notification of + * expiries. + * + * + * + */ +public class Timeout +{ + private Object _lock; + private long _duration; + private long _now=System.currentTimeMillis(); + private Task _head=new Task(); + + /* ------------------------------------------------------------ */ + public Timeout() + { + _lock=new Object(); + _head._timeout=this; + } + + /* ------------------------------------------------------------ */ + public Timeout(Object lock) + { + _lock=lock; + _head._timeout=this; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the duration. + */ + public long getDuration() + { + return _duration; + } + + /* ------------------------------------------------------------ */ + /** + * @param duration The duration to set. + */ + public void setDuration(long duration) + { + _duration = duration; + } + + /* ------------------------------------------------------------ */ + public long setNow() + { + synchronized (_lock) + { + _now=System.currentTimeMillis(); + return _now; + } + } + + /* ------------------------------------------------------------ */ + public long getNow() + { + synchronized (_lock) + { + return _now; + } + } + + /* ------------------------------------------------------------ */ + public void setNow(long now) + { + synchronized (_lock) + { + _now=now; + } + } + + /* ------------------------------------------------------------ */ + /** Get an expired tasks. + * This is called instead of {@link #tick()} to obtain the next + * expired Task, but without calling it's {@link Task#expire()} or + * {@link Task#expired()} methods. + * + * @returns the next expired task or null. + */ + public Task expired() + { + synchronized (_lock) + { + long _expiry = _now-_duration; + + if (_head._next!=_head) + { + Task task = _head._next; + if (task._timestamp>_expiry) + return null; + + task.unlink(); + task._expired=true; + return task; + } + return null; + } + } + + /* ------------------------------------------------------------ */ + public void tick(long now) + { + long _expiry = -1; + + Task task=null; + while (true) + { + try + { + synchronized (_lock) + { + if (_expiry==-1) + { + if (now!=-1) + _now=now; + _expiry = _now-_duration; + } + + task= _head._next; + if (task==_head || task._timestamp>_expiry) + break; + task.unlink(); + task._expired=true; + task.expire(); + } + + task.expired(); + } + catch(Throwable th) + { + Log.warn(Log.EXCEPTION,th); + } + } + } + + /* ------------------------------------------------------------ */ + public void tick() + { + tick(-1); + } + + /* ------------------------------------------------------------ */ + public void schedule(Task task) + { + schedule(task,0L); + } + + /* ------------------------------------------------------------ */ + /** + * @param task + * @param delay A delay in addition to the default duration of the timeout + */ + public void schedule(Task task,long delay) + { + synchronized (_lock) + { + if (task._timestamp!=0) + { + task.unlink(); + task._timestamp=0; + } + task._timeout=this; + task._expired=false; + task._delay=delay; + task._timestamp = _now+delay; + + Task last=_head._prev; + while (last!=_head) + { + if (last._timestamp <= task._timestamp) + break; + last=last._prev; + } + last.link(task); + } + } + + + /* ------------------------------------------------------------ */ + public void cancelAll() + { + synchronized (_lock) + { + _head._next=_head._prev=_head; + } + } + + /* ------------------------------------------------------------ */ + public boolean isEmpty() + { + synchronized (_lock) + { + return _head._next==_head; + } + } + + /* ------------------------------------------------------------ */ + public long getTimeToNext() + { + synchronized (_lock) + { + if (_head._next==_head) + return -1; + long to_next = _duration+_head._next._timestamp-_now; + return to_next<0?0:to_next; + } + } + + /* ------------------------------------------------------------ */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append(super.toString()); + + Task task = _head._next; + while (task!=_head) + { + buf.append("-->"); + buf.append(task); + task=task._next; + } + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** Task. + * The base class for scheduled timeouts. This class should be + * extended to implement the expire() method, which is called if the + * timeout expires. + * + * + * + */ + public static class Task + { + Task _next; + Task _prev; + Timeout _timeout; + long _delay; + long _timestamp=0; + boolean _expired=false; + + /* ------------------------------------------------------------ */ + public Task() + { + _next=_prev=this; + } + + /* ------------------------------------------------------------ */ + public long getTimestamp() + { + return _timestamp; + } + + /* ------------------------------------------------------------ */ + public long getAge() + { + Timeout t = _timeout; + if (t!=null && t._now!=0 && _timestamp!=0) + return t._now-_timestamp; + return 0; + } + + /* ------------------------------------------------------------ */ + private void unlink() + { + _next._prev=_prev; + _prev._next=_next; + _next=_prev=this; + _expired=false; + } + + /* ------------------------------------------------------------ */ + private void link(Task task) + { + Task next_next = _next; + _next._prev=task; + _next=task; + _next._next=next_next; + _next._prev=this; + } + + /* ------------------------------------------------------------ */ + /** Schedule the task on the given timeout. + * The task exiry will be called after the timeout duration. + * @param timer + */ + public void schedule(Timeout timer) + { + timer.schedule(this); + } + + /* ------------------------------------------------------------ */ + /** Schedule the task on the given timeout. + * The task exiry will be called after the timeout duration. + * @param timer + */ + public void schedule(Timeout timer, long delay) + { + timer.schedule(this,delay); + } + + /* ------------------------------------------------------------ */ + /** Reschedule the task on the current timeout. + * The task timeout is rescheduled as if it had been cancelled and + * scheduled on the current timeout. + */ + public void reschedule() + { + Timeout timeout = _timeout; + if (timeout!=null) + timeout.schedule(this,_delay); + } + + /* ------------------------------------------------------------ */ + /** Cancel the task. + * Remove the task from the timeout. + */ + public void cancel() + { + Timeout timeout = _timeout; + if (timeout!=null) + { + synchronized (timeout._lock) + { + unlink(); + _timestamp=0; + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isExpired() { return _expired; } + + /* ------------------------------------------------------------ */ + public boolean isScheduled() { return _next!=this; } + + /* ------------------------------------------------------------ */ + /** Expire task. + * This method is called when the timeout expires. It is called + * in the scope of the synchronize block (on this) that sets + * the {@link #isExpired()} state to true. + * @see #expired() For an unsynchronized callback. + */ + public void expire(){} + + /* ------------------------------------------------------------ */ + /** Expire task. + * This method is called when the timeout expires. It is called + * outside of any synchronization scope and may be delayed. + * + */ + public void expired(){} + + } + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ArrayQueueTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ArrayQueueTest.java new file mode 100644 index 00000000000..f79c6185646 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ArrayQueueTest.java @@ -0,0 +1,163 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import junit.framework.TestCase; + +public class ArrayQueueTest extends TestCase +{ + + public void testWrap() throws Exception + { + ArrayQueue queue = new ArrayQueue(3,3); + + assertEquals(0,queue.size()); + + for (int i=0;i<10;i++) + { + queue.offer("one"); + assertEquals(1,queue.size()); + + queue.offer("two"); + assertEquals(2,queue.size()); + + queue.offer("three"); + assertEquals(3,queue.size()); + + assertEquals("one",queue.get(0)); + assertEquals("two",queue.get(1)); + assertEquals("three",queue.get(2)); + + assertEquals("[one, two, three]",queue.toString()); + + assertEquals("two",queue.remove(1)); + assertEquals(2,queue.size()); + + assertEquals("one",queue.remove()); + assertEquals(1,queue.size()); + + assertEquals("three",queue.poll()); + assertEquals(0,queue.size()); + + assertEquals(null,queue.poll()); + + queue.offer("xxx"); + queue.offer("xxx"); + assertEquals(2,queue.size()); + assertEquals("xxx",queue.poll()); + assertEquals("xxx",queue.poll()); + assertEquals(0,queue.size()); + + } + + } + + public void testRemove() throws Exception + { + ArrayQueue queue = new ArrayQueue(3,3); + + queue.add("0"); + queue.add("x"); + + for (int i=1;i<100;i++) + { + queue.add(""+i); + queue.add("x"); + queue.remove(queue.size()-3); + queue.set(queue.size()-3,queue.get(queue.size()-3)+"!"); + } + + for (int i=0;i<99;i++) + assertEquals(i+"!",queue.get(i)); + } + + public void testGrow() throws Exception + { + ArrayQueue queue = new ArrayQueue(3,5); + assertEquals(3,queue.getCapacity()); + + queue.add("0"); + queue.add("a"); + queue.add("b"); + assertEquals(3,queue.getCapacity()); + queue.add("c"); + assertEquals(8,queue.getCapacity()); + + for (int i=0;i<4;i++) + queue.add(""+('d'+i)); + assertEquals(8,queue.getCapacity()); + for (int i=0;i<4;i++) + queue.poll(); + assertEquals(8,queue.getCapacity()); + for (int i=0;i<4;i++) + queue.add(""+('d'+i)); + assertEquals(8,queue.getCapacity()); + for (int i=0;i<4;i++) + queue.poll(); + assertEquals(8,queue.getCapacity()); + for (int i=0;i<4;i++) + queue.add(""+('d'+i)); + assertEquals(8,queue.getCapacity()); + + queue.add("z"); + assertEquals(13,queue.getCapacity()); + + queue.clear(); + assertEquals(13,queue.getCapacity()); + for (int i=0;i<12;i++) + queue.add(""+('a'+i)); + assertEquals(13,queue.getCapacity()); + queue.clear(); + assertEquals(13,queue.getCapacity()); + for (int i=0;i<12;i++) + queue.add(""+('a'+i)); + assertEquals(13,queue.getCapacity()); + + + } + + public void testFullEmpty() throws Exception + { + ArrayQueue queue = new ArrayQueue(2); + assertTrue(queue.offer("one")); + assertTrue(queue.offer("two")); + assertFalse(queue.offer("three")); + + try + { + queue.add("four"); + assertTrue(false); + } + catch(Exception e) + { + + } + + assertEquals("one",queue.peek()); + assertEquals("one",queue.remove()); + assertEquals("two",queue.remove()); + try + { + assertEquals("three",queue.remove()); + assertTrue(false); + } + catch(Exception e) + { + + } + + assertEquals(null,queue.poll()); + } + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java new file mode 100644 index 00000000000..da89c08e5b9 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java @@ -0,0 +1,311 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; + +import junit.framework.TestCase; + +public class BlockingArrayQueueTest extends TestCase +{ + + public void testWrap() throws Exception + { + BlockingArrayQueue queue = new BlockingArrayQueue(3); + + assertEquals(0,queue.size()); + + for (int i=0;i<3;i++) + { + queue.offer("one"); + assertEquals(1,queue.size()); + + queue.offer("two"); + assertEquals(2,queue.size()); + + queue.offer("three"); + assertEquals(3,queue.size()); + + assertEquals("one",queue.get(0)); + assertEquals("two",queue.get(1)); + assertEquals("three",queue.get(2)); + + assertEquals("[one, two, three]",queue.toString()); + + assertEquals("one",queue.poll()); + assertEquals(2,queue.size()); + + assertEquals("two",queue.poll()); + assertEquals(1,queue.size()); + + assertEquals("three",queue.poll()); + assertEquals(0,queue.size()); + + + queue.offer("xxx"); + assertEquals(1,queue.size()); + assertEquals("xxx",queue.poll()); + assertEquals(0,queue.size()); + + } + + } + + public void testRemove() throws Exception + { + BlockingArrayQueue queue = new BlockingArrayQueue(3,3); + + queue.add("0"); + queue.add("x"); + + for (int i=1;i<100;i++) + { + queue.add(""+i); + queue.add("x"); + queue.remove(queue.size()-3); + queue.set(queue.size()-3,queue.get(queue.size()-3)+"!"); + } + + for (int i=0;i<99;i++) + assertEquals(i+"!",queue.get(i)); + } + + public void testGrow() throws Exception + { + BlockingArrayQueue queue = new BlockingArrayQueue(3,2); + assertEquals(3,queue.getCapacity()); + + queue.add("a"); + queue.add("a"); + assertEquals(2,queue.size()); + assertEquals(3,queue.getCapacity()); + queue.add("a"); + queue.add("a"); + assertEquals(4,queue.size()); + assertEquals(5,queue.getCapacity()); + + int s=5; + int c=5; + queue.add("a"); + + for (int t=0;t<100;t++) + { + assertEquals(s,queue.size()); + assertEquals(c,queue.getCapacity()); + + for (int i=queue.size();i-->0;) + queue.poll(); + assertEquals(0,queue.size()); + assertEquals(c,queue.getCapacity()); + + for (int i=queue.getCapacity();i-->0;) + queue.add("a"); + queue.add("a"); + assertEquals(s+1,queue.size()); + assertEquals(c+2,queue.getCapacity()); + + queue.poll(); + queue.add("a"); + queue.add("a"); + assertEquals(s+2,queue.size()); + assertEquals(c+2,queue.getCapacity()); + + s+=2; + c+=2; + } + + + + } + + public void testTake() throws Exception + { + final String[] data=new String[4]; + + final BlockingArrayQueue queue = new BlockingArrayQueue(); + + Thread thread = new Thread() + { + public void run() + { + try + { + data[0]=queue.take(); + data[1]=queue.take(); + Thread.sleep(1000); + data[2]=queue.take(); + data[3]=queue.poll(100,TimeUnit.MILLISECONDS); + } + catch(Exception e) + { + assertTrue(false); + e.printStackTrace(); + } + } + }; + + thread.start(); + + Thread.sleep(1000); + + queue.offer("zero"); + queue.offer("one"); + queue.offer("two"); + thread.join(); + + assertEquals("zero",data[0]); + assertEquals("one",data[1]); + assertEquals("two",data[2]); + assertEquals(null,data[3]); + + } + + volatile boolean _running; + + public void testConcurrentAccess() throws Exception + { + final int THREADS=50; + final int LOOPS=1000; + + final BlockingArrayQueue queue = new BlockingArrayQueue(1+THREADS*LOOPS); + + final ConcurrentLinkedQueue produced=new ConcurrentLinkedQueue(); + final ConcurrentLinkedQueue consumed=new ConcurrentLinkedQueue(); + + + _running=true; + + // start consumers + final CyclicBarrier barrier0 = new CyclicBarrier(THREADS+1); + for (int i=0;i0 && size!=last) + { + last=size; + Thread.sleep(500); + size=queue.size(); + } + _running=false; + barrier0.await(); + + HashSet prodSet = new HashSet(produced); + HashSet consSet = new HashSet(consumed); + + assertEquals(prodSet,consSet); + + + + + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java new file mode 100644 index 00000000000..d0c7c18f8eb --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java @@ -0,0 +1,94 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.util.Locale; +import java.util.TimeZone; + +import junit.framework.TestSuite; + + +/* ------------------------------------------------------------ */ +/** Util meta Tests. + * + */ +public class DateCacheTest extends junit.framework.TestCase +{ + public DateCacheTest(String name) + { + super(name); + } + + public static junit.framework.Test suite() { + TestSuite suite = new TestSuite(DateCacheTest.class); + return suite; + } + + /* ------------------------------------------------------------ */ + /** main. + */ + public static void main(String[] args) + { + junit.textui.TestRunner.run(suite()); + } + + /* ------------------------------------------------------------ */ + public void testDateCache() throws Exception + { + //@WAS: Test t = new Test("org.eclipse.util.DateCache"); + // 012345678901234567890123456789 + DateCache dc = new DateCache("EEE, dd MMM yyyy HH:mm:ss zzz ZZZ", + Locale.US); + dc.setTimeZone(TimeZone.getTimeZone("GMT")); + String last=dc.format(System.currentTimeMillis()); + boolean change=false; + for (int i=0;i<15;i++) + { + Thread.sleep(100); + String date=dc.format(System.currentTimeMillis()); + + assertEquals( "Same Date", + last.substring(0,17), + date.substring(0,17)); + + if (!last.substring(17).equals(date.substring(17))) + change=true; + else + { + int lh=Integer.parseInt(last.substring(17,19)); + int dh=Integer.parseInt(date.substring(17,19)); + int lm=Integer.parseInt(last.substring(20,22)); + int dm=Integer.parseInt(date.substring(20,22)); + int ls=Integer.parseInt(last.substring(23,25)); + int ds=Integer.parseInt(date.substring(23,25)); + + // This won't work at midnight! + change|= ds!=ls || dm!=lm || dh!=lh; + } + last=date; + } + assertTrue("time changed", change); + + + // Test string is cached + dc = new DateCache(); + String s1=dc.format(System.currentTimeMillis()); + dc.format(1); + String s2=dc.format(System.currentTimeMillis()); + dc.format(System.currentTimeMillis()+10*60*60); + String s3=dc.format(System.currentTimeMillis()); + assertTrue(s1==s2 || s2==s3); + } + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java new file mode 100644 index 00000000000..4f1613e6e45 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java @@ -0,0 +1,355 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import junit.framework.TestCase; + +/** + * + * + */ +public class LazyListTest extends TestCase +{ + + /** + * Constructor for LazyListTest. + * @param arg0 + */ + public LazyListTest(String arg0) + { + super(arg0); + } + + /* + * Test for Object add(Object, Object) + */ + public void testAddObjectObject() + { + Object list=null; + assertEquals(0,LazyList.size(list)); + + list=LazyList.add(list, "a"); + assertEquals(1,LazyList.size(list)); + assertEquals("a",LazyList.get(list,0)); + + list=LazyList.add(list, "b"); + assertEquals(2,LazyList.size(list)); + assertEquals("a",LazyList.get(list,0)); + assertEquals("b",LazyList.get(list,1)); + + list=null; + list=LazyList.add(list, null); + assertEquals(1,LazyList.size(list)); + assertEquals(null,LazyList.get(list,0)); + + list="a"; + list=LazyList.add(list, null); + assertEquals(2,LazyList.size(list)); + assertEquals("a",LazyList.get(list,0)); + assertEquals(null,LazyList.get(list,1)); + + list=LazyList.add(list, null); + assertEquals(3,LazyList.size(list)); + assertEquals("a",LazyList.get(list,0)); + assertEquals(null,LazyList.get(list,1)); + assertEquals(null,LazyList.get(list,2)); + + list=LazyList.add(null,list); + assertEquals(1,LazyList.size(list)); + Object l = LazyList.get(list,0); + assertTrue(l instanceof List); + } + + /* + * Test for Object add(Object, int, Object) + */ + public void testAddObjectintObject() + { + Object list=null; + list=LazyList.add(list,0,"c"); + list=LazyList.add(list,0,"a"); + list=LazyList.add(list,1,"b"); + list=LazyList.add(list,3,"d"); + + assertEquals(4,LazyList.size(list)); + assertEquals("a",LazyList.get(list,0)); + assertEquals("b",LazyList.get(list,1)); + assertEquals("c",LazyList.get(list,2)); + assertEquals("d",LazyList.get(list,3)); + + list=LazyList.add(null, 0, null); + assertTrue(list instanceof List); + + list=LazyList.add(null, 0, new ArrayList()); + assertTrue(list instanceof List); + } + + + public void testAddCollection() + { + ArrayList l=new ArrayList(); + l.add("a"); + l.add("b"); + + Object list=null; + list=LazyList.addCollection(list,l); + list=LazyList.addCollection(list,l); + + assertEquals(4,LazyList.size(list)); + assertEquals("a",LazyList.get(list,0)); + assertEquals("b",LazyList.get(list,1)); + assertEquals("a",LazyList.get(list,2)); + assertEquals("b",LazyList.get(list,3)); + } + + public void testEnsureSize() + { + assertTrue(LazyList.ensureSize(null,10)!=null); + + assertTrue(LazyList.ensureSize("a",10) instanceof ArrayList); + + ArrayList l=new ArrayList(); + l.add("a"); + l.add("b"); + assertTrue(LazyList.ensureSize(l,10)!=l); + assertTrue(LazyList.ensureSize(l,1)==l); + } + + /* + * Test for Object remove(Object, Object) + */ + public void testRemoveObjectObject() + { + Object list=null; + + assertTrue(LazyList.remove(null,"a")==null); + + list=LazyList.add(list,"a"); + assertEquals("a",LazyList.remove(list,"z")); + assertTrue(LazyList.remove(list,"a")==null); + + list=LazyList.add(list,"b"); + list=LazyList.remove(list,"b"); + list=LazyList.add(list,"b"); + list=LazyList.add(list,"c"); + list=LazyList.add(list,"d"); + list=LazyList.add(list,"e"); + list=LazyList.remove(list,"a"); + list=LazyList.remove(list,"d"); + list=LazyList.remove(list,"e"); + + assertEquals(2,LazyList.size(list)); + assertEquals("b",LazyList.get(list,0)); + assertEquals("c",LazyList.get(list,1)); + + list=LazyList.remove(list,"b"); + list=LazyList.remove(list,"c"); + assertEquals(null,list); + } + + /* + * Test for Object remove(Object, int) + */ + public void testRemoveObjectint() + { + Object list=null; + assertTrue(LazyList.remove(list,0)==null); + + list=LazyList.add(list,"a"); + assertEquals("a",LazyList.remove(list,1)); + assertTrue(LazyList.remove(list,0)==null); + + list=LazyList.add(list,"b"); + list=LazyList.remove(list,1); + list=LazyList.add(list,"b"); + list=LazyList.add(list,"c"); + list=LazyList.add(list,"d"); + list=LazyList.add(list,"e"); + list=LazyList.remove(list,0); + list=LazyList.remove(list,2); + list=LazyList.remove(list,2); + + assertEquals(2,LazyList.size(list)); + assertEquals("b",LazyList.get(list,0)); + assertEquals("c",LazyList.get(list,1)); + + list=LazyList.remove(list,0); + list=LazyList.remove(list,0); + assertEquals(null,list); + } + + /* + * Test for List getList(Object) + */ + public void testGetListObject() + { + assertEquals(0,LazyList.getList(null).size()); + assertEquals(1,LazyList.getList("a").size()); + + ArrayList l=new ArrayList(); + l.add("a"); + l.add("b"); + assertEquals(2,LazyList.getList(l).size()); + } + + /* + * Test for List getList(Object, boolean) + */ + public void testGetListObjectboolean() + { + assertEquals(0,LazyList.getList(null,false).size()); + assertEquals(null,LazyList.getList(null,true)); + } + + public void testToStringArray() + { + assertEquals(0,LazyList.toStringArray(null).length); + + assertEquals(1,LazyList.toStringArray("a").length); + assertEquals("a",LazyList.toStringArray("a")[0]); + + ArrayList l=new ArrayList(); + l.add("a"); + l.add(null); + l.add(new Integer(2)); + String[] a=LazyList.toStringArray(l); + + assertEquals(3,a.length); + assertEquals("a",a[0]); + assertEquals(null,a[1]); + assertEquals("2",a[2]); + + } + + public void testSize() + { + ArrayList l=new ArrayList(); + l.add("a"); + l.add("b"); + + assertEquals(0,LazyList.size(null)); + assertEquals(0,LazyList.size(new ArrayList())); + assertEquals(1,LazyList.size("a")); + assertEquals(2,LazyList.size(l)); + } + + public void testGet() + { + testAddObjectObject(); + + assertEquals("a",LazyList.get("a",0)); + + try{ + LazyList.get(null,0); + assertTrue(false); + } + catch(IndexOutOfBoundsException e) + { + assertTrue(true); + } + + try{ + LazyList.get("a",1); + assertTrue(false); + } + catch(IndexOutOfBoundsException e) + { + assertTrue(true); + } + } + + public void testContains() + { + ArrayList l=new ArrayList(); + l.add("a"); + l.add("b"); + + assertFalse(LazyList.contains(null,"z")); + assertFalse(LazyList.contains("a","z")); + assertFalse(LazyList.contains(l,"z")); + + assertTrue(LazyList.contains("a","a")); + assertTrue(LazyList.contains(l,"b")); + + } + + + + public void testIterator() + { + ArrayList l=new ArrayList(); + l.add("a"); + l.add("b"); + + assertFalse(LazyList.iterator(null).hasNext()); + + Iterator i=LazyList.iterator("a"); + assertTrue(i.hasNext()); + assertEquals("a",i.next()); + assertFalse(i.hasNext()); + + i=LazyList.iterator(l); + assertTrue(i.hasNext()); + assertEquals("a",i.next()); + assertTrue(i.hasNext()); + assertEquals("b",i.next()); + assertFalse(i.hasNext()); + } + + public void testListIterator() + { + ArrayList l=new ArrayList(); + l.add("a"); + l.add("b"); + + assertFalse(LazyList.listIterator(null).hasNext()); + + ListIterator i=LazyList.listIterator("a"); + assertTrue(i.hasNext()); + assertFalse(i.hasPrevious()); + assertEquals("a",i.next()); + assertFalse(i.hasNext()); + assertTrue(i.hasPrevious()); + assertEquals("a",i.previous()); + + i=LazyList.listIterator(l); + assertTrue(i.hasNext()); + assertFalse(i.hasPrevious()); + assertEquals("a",i.next()); + assertTrue(i.hasNext()); + assertTrue(i.hasPrevious()); + assertEquals("b",i.next()); + assertFalse(i.hasNext()); + assertTrue(i.hasPrevious()); + assertEquals("b",i.previous()); + assertEquals("a",i.previous()); + } + + public void testCloneToString() + { + ArrayList l=new ArrayList(); + l.add("a"); + l.add("b"); + + assertEquals("[]",LazyList.toString(LazyList.clone(null))); + assertEquals("[a]",LazyList.toString(LazyList.clone("a"))); + assertEquals("[a, b]",LazyList.toString(LazyList.clone(l))); + } + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java new file mode 100644 index 00000000000..8c2253163a6 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java @@ -0,0 +1,170 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import junit.framework.TestCase; + +/** + * + * + */ +public class QuotedStringTokenizerTest extends TestCase +{ + + /** + * Constructor for QuotedStringTokenizerTest. + * @param arg0 + */ + public QuotedStringTokenizerTest(String arg0) + { + super(arg0); + } + + /* + * Test for String nextToken() + */ + public void testTokenizer0() + { + QuotedStringTokenizer tok = + new QuotedStringTokenizer("abc\n\"d\\\"'\"\n'p\\',y'\nz"); + checkTok(tok,false,false); + } + + /* + * Test for String nextToken() + */ + public void testTokenizer1() + { + QuotedStringTokenizer tok = + new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", + " ,"); + checkTok(tok,false,false); + } + + /* + * Test for String nextToken() + */ + public void testTokenizer2() + { + QuotedStringTokenizer tok = + new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", + false); + checkTok(tok,false,false); + + tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", + true); + checkTok(tok,true,false); + } + + /* + * Test for String nextToken() + */ + public void testTokenizer3() + { + QuotedStringTokenizer tok; + + tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", + false,false); + checkTok(tok,false,false); + + tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", + false,true); + checkTok(tok,false,true); + + tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", + true,false); + checkTok(tok,true,false); + + tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", + true,true); + checkTok(tok,true,true); + } + + public void testQuote() + { + StringBuffer buf = new StringBuffer(); + + buf.setLength(0); + QuotedStringTokenizer.quote(buf,"abc \n efg"); + assertEquals("\"abc \\n efg\"",buf.toString()); + + buf.setLength(0); + QuotedStringTokenizer.quote(buf,"abcefg"); + assertEquals("\"abcefg\"",buf.toString()); + + buf.setLength(0); + QuotedStringTokenizer.quote(buf,"abcefg\""); + assertEquals("\"abcefg\\\"\"",buf.toString()); + + buf.setLength(0); + QuotedStringTokenizer.quoteIfNeeded(buf,"abc \n efg"); + assertEquals("\"abc \\n efg\"",buf.toString()); + + buf.setLength(0); + QuotedStringTokenizer.quoteIfNeeded(buf,"abcefg"); + assertEquals("abcefg",buf.toString()); + + } + + /* + * Test for String nextToken() + */ + public void testTokenizer4() + { + QuotedStringTokenizer tok = new QuotedStringTokenizer("abc'def,ghi'jkl",","); + tok.setSingle(false); + assertEquals("abc'def",tok.nextToken()); + assertEquals("ghi'jkl",tok.nextToken()); + tok = new QuotedStringTokenizer("abc'def,ghi'jkl",","); + tok.setSingle(true); + assertEquals("abcdef,ghijkl",tok.nextToken()); + } + + private void checkTok(QuotedStringTokenizer tok,boolean delim,boolean quotes) + { + assertTrue(tok.hasMoreElements()); + assertTrue(tok.hasMoreTokens()); + assertEquals("abc",tok.nextToken()); + if (delim)assertEquals(",",tok.nextToken()); + if (delim)assertEquals(" ",tok.nextToken()); + + assertEquals(quotes?"\"d\\\"'\"":"d\"'",tok.nextElement()); + if (delim)assertEquals(",",tok.nextToken()); + assertEquals(quotes?"'p\\',y'":"p',y",tok.nextToken()); + if (delim)assertEquals(" ",tok.nextToken()); + assertEquals("z",tok.nextToken()); + assertFalse(tok.hasMoreTokens()); + } + + /* + * Test for String quote(String, String) + */ + public void testQuoteString() + { + assertEquals("abc",QuotedStringTokenizer.quote("abc", " ,")); + assertEquals("\"a c\"",QuotedStringTokenizer.quote("a c", " ,")); + assertEquals("\"a'c\"",QuotedStringTokenizer.quote("a'c", " ,")); + assertEquals("\"a\\n\\r\\t\"",QuotedStringTokenizer.quote("a\n\r\t")); + } + + + public void testUnquote() + { + assertEquals("abc",QuotedStringTokenizer.unquote("abc")); + assertEquals("a\"c",QuotedStringTokenizer.unquote("\"a\\\"c\"")); + assertEquals("a'c",QuotedStringTokenizer.unquote("\"a'c\"")); + assertEquals("a\n\r\t",QuotedStringTokenizer.unquote("\"a\\n\\r\\t\"")); + } + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java new file mode 100644 index 00000000000..087bc6b49c2 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java @@ -0,0 +1,296 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Map; +import java.util.Set; + +import junit.framework.TestCase; + + +/** + * + * + */ +public class StringMapTest extends TestCase +{ + StringMap m0; + StringMap m1; + StringMap m5; + StringMap m5i; + + /** + * Constructor for StringMapTest. + * @param arg0 + */ + public StringMapTest(String arg0) + { + super(arg0); + } + + /* + * @see TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + + m0=new StringMap(); + m1=new StringMap(false); + m1.put("abc", "0"); + + m5=new StringMap(false); + m5.put("a", "0"); + m5.put("ab", "1"); + m5.put("abc", "2"); + m5.put("abb", "3"); + m5.put("bbb", "4"); + + m5i=new StringMap(true); + m5i.put(null, "0"); + m5i.put("ab", "1"); + m5i.put("abc", "2"); + m5i.put("abb", "3"); + m5i.put("bbb", null); + } + + /* + * @see TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + } + + public void testSize() + { + assertEquals(0, m0.size()); + assertEquals(1, m1.size()); + assertEquals(5, m5.size()); + assertEquals(5, m5i.size()); + + m1.remove("abc"); + m5.remove("abc"); + m5.put("bbb","x"); + m5i.put("ABC", "x"); + assertEquals(0, m0.size()); + assertEquals(0, m1.size()); + assertEquals(4, m5.size()); + assertEquals(5, m5i.size()); + } + + public void testIsEmpty() + { + assertTrue(m0.isEmpty()); + assertFalse(m1.isEmpty()); + assertFalse(m5.isEmpty()); + assertFalse(m5i.isEmpty()); + } + + public void testClear() + { + m0.clear(); + m1.clear(); + m5.clear(); + m5i.clear(); + assertTrue(m0.isEmpty()); + assertTrue(m1.isEmpty()); + assertTrue(m5.isEmpty()); + assertTrue(m5i.isEmpty()); + assertEquals(null,m1.get("abc")); + assertEquals(null,m5.get("abc")); + assertEquals(null,m5i.get("abc")); + } + + + /* + * Test for Object put(Object, Object) + */ + public void testPutGet() + { + assertEquals("2",m5.get("abc")); + assertEquals(null,m5.get("aBc")); + assertEquals("2",m5i.get("abc")); + assertEquals("2",m5i.get("aBc")); + + m5.put(null,"x"); + m5.put("aBc", "x"); + m5i.put("AbC", "x"); + + StringBuilder buffer=new StringBuilder(); + buffer.append("aBc"); + assertEquals("2",m5.get("abc")); + assertEquals("x",m5.get(buffer)); + assertEquals("x",m5i.get((Object)"abc")); + assertEquals("x",m5i.get("aBc")); + + assertEquals("x",m5.get(null)); + assertEquals("0",m5i.get(null)); + + } + + + + /* + * Test for Map.Entry getEntry(String, int, int) + */ + public void testGetEntryStringintint() + { + Map.Entry entry; + + entry=m5.getEntry("xabcyz",1,3); + assertTrue(entry!=null); + assertEquals("abc",entry.getKey()); + assertEquals("2",entry.getValue()); + + entry=m5.getBestEntry("xabcyz".getBytes(),1,5); + assertTrue(entry!=null); + assertEquals("abc",entry.getKey()); + assertEquals("2",entry.getValue()); + + entry=m5.getEntry("xaBcyz",1,3); + assertTrue(entry==null); + + entry=m5i.getEntry("xaBcyz",1,3); + assertTrue(entry!=null); + assertEquals("abc",entry.getKey()); + assertEquals("2",entry.getValue()); + entry.setValue("x"); + assertEquals("{[c:abc=x]}",entry.toString()); + + entry=m5i.getEntry((String)null,0,0); + assertTrue(entry!=null); + assertEquals(null,entry.getKey()); + assertEquals("0",entry.getValue()); + entry.setValue("x"); + assertEquals("[:null=x]",entry.toString()); + + } + + /* + * Test for Map.Entry getEntry(char[], int, int) + */ + public void testGetEntrycharArrayintint() + { + char[] xabcyz = {'x','a','b','c','y','z'}; + char[] xaBcyz = {'x','a','B','c','y','z'}; + Map.Entry entry; + + entry=m5.getEntry(xabcyz,1,3); + assertTrue(entry!=null); + assertEquals("abc",entry.getKey()); + assertEquals("2",entry.getValue()); + + entry=m5.getEntry(xaBcyz,1,3); + assertTrue(entry==null); + + entry=m5i.getEntry(xaBcyz,1,3); + assertTrue(entry!=null); + assertEquals("abc",entry.getKey()); + assertEquals("2",entry.getValue()); + } + + /* + * Test for Object remove(Object) + */ + public void testRemove() + { + m0.remove("abc"); + m1.remove("abc"); + m5.remove("aBc"); + m5.remove("bbb"); + m5i.remove("aBc"); + m5i.remove(null); + + assertEquals(0, m0.size()); + assertEquals(0, m1.size()); + assertEquals(4, m5.size()); + assertEquals(3, m5i.size()); + + assertEquals("2",m5.get("abc")); + assertEquals(null,m5.get("bbb")); + assertEquals(null,m5i.get("AbC")); + assertEquals(null,m5i.get(null)); + } + + + /* + * Test for Set entrySet() + */ + public void testEntrySet() + { + Set es0=m0.entrySet(); + Set es1=m1.entrySet(); + Set es5=m5.entrySet(); + assertEquals(0, es0.size()); + assertEquals(1, es1.size()); + assertEquals(5, es5.size()); + } + + /* + * Test for boolean containsKey(Object) + */ + public void testContainsKey() + { + assertTrue(m5.containsKey("abc")); + assertTrue(!m5.containsKey("aBc")); + assertTrue(m5.containsKey("bbb")); + assertTrue(!m5.containsKey("xyz")); + + assertTrue(m5i.containsKey(null)); + assertTrue(m5i.containsKey("abc")); + assertTrue(m5i.containsKey("aBc")); + assertTrue(m5i.containsKey("ABC")); + } + + public void testWriteExternal() + throws Exception + { + ByteArrayOutputStream bout= new ByteArrayOutputStream(); + ObjectOutputStream oo=new ObjectOutputStream(bout); + ObjectInputStream oi; + + oo.writeObject(m0); + oo.writeObject(m1); + oo.writeObject(m5); + oo.writeObject(m5i); + + oi=new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray())); + m0=(StringMap)oi.readObject(); + m1=(StringMap)oi.readObject(); + m5=(StringMap)oi.readObject(); + m5i=(StringMap)oi.readObject(); + testSize(); + + oi=new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray())); + m0=(StringMap)oi.readObject(); + m1=(StringMap)oi.readObject(); + m5=(StringMap)oi.readObject(); + m5i=(StringMap)oi.readObject(); + testPutGet(); + + } + + public void testToString() + { + assertEquals("{}",m0.toString()); + assertEquals("{abc=0}",m1.toString()); + assertTrue(m5.toString().indexOf("abc=2")>0); + } + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java new file mode 100644 index 00000000000..7a26695bec4 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java @@ -0,0 +1,191 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + + +import junit.framework.TestCase; + +/** + * + * + */ +public class StringUtilTest extends TestCase +{ + + /** + * Constructor for StringUtilTest. + * @param arg0 + */ + public StringUtilTest(String arg0) + { + super(arg0); + } + + /* + * @see TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + } + + /* + * @see TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + } + + public void testAsciiToLowerCase() + { + String lc="\u0690bc def 1\u06903"; + assertEquals(StringUtil.asciiToLowerCase("\u0690Bc DeF 1\u06903"), lc); + assertTrue(StringUtil.asciiToLowerCase(lc)==lc); + } + + public void testStartsWithIgnoreCase() + { + + assertTrue(StringUtil.startsWithIgnoreCase("\u0690b\u0690defg", "\u0690b\u0690")); + assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "\u0690bc")); + assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "\u0690Bc")); + assertTrue(StringUtil.startsWithIgnoreCase("\u0690Bcdefg", "\u0690bc")); + assertTrue(StringUtil.startsWithIgnoreCase("\u0690Bcdefg", "\u0690Bc")); + assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "")); + assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", null)); + assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "\u0690bcdefg")); + + assertFalse(StringUtil.startsWithIgnoreCase(null, "xyz")); + assertFalse(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "xyz")); + assertFalse(StringUtil.startsWithIgnoreCase("\u0690", "xyz")); + } + + public void testEndsWithIgnoreCase() + { + assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcd\u0690f\u0690", "\u0690f\u0690")); + assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdefg", "efg")); + assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdefg", "eFg")); + assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdeFg", "efg")); + assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdeFg", "eFg")); + assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdefg", "")); + assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdefg", null)); + assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdefg", "\u0690bcdefg")); + + assertFalse(StringUtil.endsWithIgnoreCase(null, "xyz")); + assertFalse(StringUtil.endsWithIgnoreCase("\u0690bcdefg", "xyz")); + assertFalse(StringUtil.endsWithIgnoreCase("\u0690", "xyz")); + } + + public void testIndexFrom() + { + assertEquals(StringUtil.indexFrom("\u0690bcd", "xyz"),-1); + assertEquals(StringUtil.indexFrom("\u0690bcd", "\u0690bcz"),0); + assertEquals(StringUtil.indexFrom("\u0690bcd", "bcz"),1); + assertEquals(StringUtil.indexFrom("\u0690bcd", "dxy"),3); + } + + public void testReplace() + { + String s="\u0690bc \u0690bc \u0690bc"; + assertEquals(StringUtil.replace(s, "\u0690bc", "xyz"),"xyz xyz xyz"); + assertTrue(StringUtil.replace(s,"xyz","pqy")==s); + + s=" \u0690bc "; + assertEquals(StringUtil.replace(s, "\u0690bc", "xyz")," xyz "); + + } + + public void testUnquote() + { + String uq =" not quoted "; + assertTrue(StringUtil.unquote(uq)==uq); + assertEquals(StringUtil.unquote("' quoted string '")," quoted string "); + assertEquals(StringUtil.unquote("\" quoted string \"")," quoted string "); + assertEquals(StringUtil.unquote("' quoted\"string '")," quoted\"string "); + assertEquals(StringUtil.unquote("\" quoted'string \"")," quoted'string "); + } + + + public void testNonNull() + { + String nn=""; + assertTrue(nn==StringUtil.nonNull(nn)); + assertEquals("",StringUtil.nonNull(null)); + } + + /* + * Test for boolean equals(String, char[], int, int) + */ + public void testEqualsStringcharArrayintint() + { + assertTrue(StringUtil.equals("\u0690bc", new char[] {'x','\u0690','b','c','z'},1,3)); + assertFalse(StringUtil.equals("axc", new char[] {'x','a','b','c','z'},1,3)); + } + + public void testAppend() + { + StringBuilder buf = new StringBuilder(); + buf.append('a'); + StringUtil.append(buf, "abc", 1, 1); + StringUtil.append(buf, (byte)12, 16); + StringUtil.append(buf, (byte)16, 16); + StringUtil.append(buf, (byte)-1, 16); + StringUtil.append(buf, (byte)-16, 16); + assertEquals("ab0c10fff0",buf.toString()); + + } + + public static void main(String[] arg) throws Exception + { + String string = "Now \u0690xxxxxxxx"; + System.err.println(string); + byte[] bytes=string.getBytes("UTF-8"); + System.err.println(new String(bytes)); + System.err.println(bytes.length); + long calc=0; + Utf8StringBuffer strbuf = new Utf8StringBuffer(bytes.length); + for (int i=0;i<10;i++) + { + long s1=System.currentTimeMillis(); + for (int j=1000000; j-->0;) + { + calc+=new String(bytes,0,bytes.length,"UTF-8").hashCode(); + } + long s2=System.currentTimeMillis(); + for (int j=1000000; j-->0;) + { + calc+=StringUtil.toUTF8String(bytes,0,bytes.length).hashCode(); + } + long s3=System.currentTimeMillis(); + for (int j=1000000; j-->0;) + { + Utf8StringBuffer buffer = new Utf8StringBuffer(bytes.length); + buffer.append(bytes,0,bytes.length); + calc+=buffer.toString().hashCode(); + } + long s4=System.currentTimeMillis(); + for (int j=1000000; j-->0;) + { + strbuf.reset(); + strbuf.append(bytes,0,bytes.length); + calc+=strbuf.toString().hashCode(); + } + long s5=System.currentTimeMillis(); + + System.err.println((s2-s1)+", "+(s3-s2)+", "+(s4-s3)+", "+(s5-s4)); + } + System.err.println(calc); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java new file mode 100644 index 00000000000..7048a1b92eb --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java @@ -0,0 +1,209 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import junit.framework.TestCase; + +/** + * TestInjection + * + * + */ +public class TestIntrospectionUtil extends TestCase +{ + public final Class[] __INTEGER_ARG = new Class[] {Integer.class}; + Field privateAField; + Field protectedAField; + Field publicAField; + Field defaultAField; + Field privateBField; + Field protectedBField; + Field publicBField; + Field defaultBField; + Method privateCMethod; + Method protectedCMethod; + Method publicCMethod; + Method defaultCMethod; + Method privateDMethod; + Method protectedDMethod; + Method publicDMethod; + Method defaultDMethod; + + public class ServletA + { + private Integer privateA; + protected Integer protectedA; + Integer defaultA; + public Integer publicA; + } + + public class ServletB extends ServletA + { + private String privateB; + protected String protectedB; + public String publicB; + String defaultB; + } + + public class ServletC + { + private void setPrivateC (Integer c) {} + protected void setProtectedC (Integer c) {} + public void setPublicC(Integer c) {} + void setDefaultC(Integer c) {} + } + + public class ServletD extends ServletC + { + private void setPrivateD(Integer d) {} + protected void setProtectedD(Integer d) {} + public void setPublicD(Integer d) {} + void setDefaultD(Integer d) {} + } + + public void setUp() + throws Exception + { + privateAField = ServletA.class.getDeclaredField("privateA"); + protectedAField = ServletA.class.getDeclaredField("protectedA"); + publicAField = ServletA.class.getDeclaredField("publicA"); + defaultAField = ServletA.class.getDeclaredField("defaultA"); + privateBField = ServletB.class.getDeclaredField("privateB"); + protectedBField = ServletB.class.getDeclaredField("protectedB"); + publicBField = ServletB.class.getDeclaredField("publicB"); + defaultBField = ServletB.class.getDeclaredField("defaultB"); + privateCMethod = ServletC.class.getDeclaredMethod("setPrivateC", __INTEGER_ARG); + protectedCMethod = ServletC.class.getDeclaredMethod("setProtectedC", __INTEGER_ARG); + publicCMethod = ServletC.class.getDeclaredMethod("setPublicC", __INTEGER_ARG); + defaultCMethod = ServletC.class.getDeclaredMethod("setDefaultC", __INTEGER_ARG); + privateDMethod = ServletD.class.getDeclaredMethod("setPrivateD", __INTEGER_ARG); + protectedDMethod = ServletD.class.getDeclaredMethod("setProtectedD", __INTEGER_ARG); + publicDMethod = ServletD.class.getDeclaredMethod("setPublicD", __INTEGER_ARG); + defaultDMethod = ServletD.class.getDeclaredMethod("setDefaultD", __INTEGER_ARG); + } + + + public void testFieldPrivate () + throws Exception + { + //direct + Field f = IntrospectionUtil.findField(ServletA.class, "privateA", Integer.class, true, false); + assertEquals(privateAField,f); + + //inheritance + try + { + IntrospectionUtil.findField(ServletB.class, "privateA", Integer.class, true, false); + fail("Private fields should not be inherited"); + } + catch (NoSuchFieldException e) + { + //expected + } + } + + public void testFieldProtected() + throws Exception + { + //direct + Field f = IntrospectionUtil.findField(ServletA.class, "protectedA", Integer.class, true, false); + assertEquals(f, protectedAField); + + //inheritance + f = IntrospectionUtil.findField(ServletB.class, "protectedA", Integer.class, true, false); + assertEquals(f, protectedAField); + } + + public void testFieldPublic() + throws Exception + { + //direct + Field f = IntrospectionUtil.findField(ServletA.class, "publicA", Integer.class, true, false); + assertEquals(f, publicAField); + + //inheritance + f = IntrospectionUtil.findField(ServletB.class, "publicA", Integer.class, true, false); + assertEquals(f, publicAField); + } + + public void testFieldDefault() + throws Exception + { + //direct + Field f = IntrospectionUtil.findField(ServletA.class, "defaultA", Integer.class, true, false); + assertEquals(f, defaultAField); + + //inheritance + f = IntrospectionUtil.findField(ServletB.class, "defaultA", Integer.class, true, false); + assertEquals(f, defaultAField); + } + + public void testMethodPrivate () + throws Exception + { + //direct + Method m = IntrospectionUtil.findMethod(ServletC.class, "setPrivateC", __INTEGER_ARG, true, false); + assertEquals(m, privateCMethod); + + //inheritance + try + { + IntrospectionUtil.findMethod(ServletD.class, "setPrivateC", __INTEGER_ARG, true, false); + fail(); + } + catch (NoSuchMethodException e) + { + //expected + } + } + + public void testMethodProtected () + throws Exception + { + // direct + Method m = IntrospectionUtil.findMethod(ServletC.class, "setProtectedC", __INTEGER_ARG, true, false); + assertEquals(m, protectedCMethod); + + //inherited + m = IntrospectionUtil.findMethod(ServletD.class, "setProtectedC", __INTEGER_ARG, true, false); + assertEquals(m, protectedCMethod); + } + + public void testMethodPublic () + throws Exception + { + // direct + Method m = IntrospectionUtil.findMethod(ServletC.class, "setPublicC", __INTEGER_ARG, true, false); + assertEquals(m, publicCMethod); + + //inherited + m = IntrospectionUtil.findMethod(ServletD.class, "setPublicC", __INTEGER_ARG, true, false); + assertEquals(m, publicCMethod); + } + + public void testMethodDefault () + throws Exception + { + // direct + Method m = IntrospectionUtil.findMethod(ServletC.class, "setDefaultC", __INTEGER_ARG, true, false); + assertEquals(m, defaultCMethod); + + //inherited + m = IntrospectionUtil.findMethod(ServletD.class, "setDefaultC", __INTEGER_ARG, true, false); + assertEquals(m, defaultCMethod); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java new file mode 100644 index 00000000000..037d2456cd2 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java @@ -0,0 +1,246 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util; + +import junit.framework.TestSuite; + + +/* ------------------------------------------------------------ */ +/** Util meta Tests. + * + */ +public class URITest extends junit.framework.TestCase +{ + public URITest(String name) + { + super(name); + } + + public static junit.framework.Test suite() { + TestSuite suite = new TestSuite(URITest.class); + return suite; + } + + /* ------------------------------------------------------------ */ + /** main. + */ + public static void main(String[] args) + { + junit.textui.TestRunner.run(suite()); + } + + /* ------------------------------------------------------------ */ + public void testEncodePath() + { + // test basic encode/decode + StringBuilder buf = new StringBuilder(); + + + buf.setLength(0); + URIUtil.encodePath(buf,"/foo%23+;,:=/b a r/?info "); + assertEquals("/foo%2523+%3B,:=/b%20a%20r/%3Finfo%20",buf.toString()); + + assertEquals("/foo%2523+%3B,:=/b%20a%20r/%3Finfo%20",URIUtil.encodePath("/foo%23+;,:=/b a r/?info ")); + + buf.setLength(0); + URIUtil.encodeString(buf,"foo%23;,:=b a r",";,= "); + assertEquals("foo%2523%3b%2c:%3db%20a%20r",buf.toString()); + + } + + /* ------------------------------------------------------------ */ + public void testDecodePath() + { + assertEquals("foo%23;,:=b a r",URIUtil.decodePath("foo%2523%3b%2c:%3db%20a%20r")); + assertEquals("foo%23;,:=b a r=",URIUtil.decodePath("xxxfoo%2523%3b%2c:%3db%20a%20r%3Dxxx".getBytes(),3,30)); + assertEquals("fää%23;,:=b a r=",URIUtil.decodePath("fää%2523%3b%2c:%3db%20a%20r%3D")); + assertEquals("f\u0629\u0629%23;,:=b a r",URIUtil.decodePath("f%d8%a9%d8%a9%2523%3b%2c:%3db%20a%20r")); + } + + /* ------------------------------------------------------------ */ + public void testAddPaths() + { + assertEquals("null+null", URIUtil.addPaths(null,null),null); + assertEquals("null+", URIUtil.addPaths(null,""),""); + assertEquals("null+bbb", URIUtil.addPaths(null,"bbb"),"bbb"); + assertEquals("null+/", URIUtil.addPaths(null,"/"),"/"); + assertEquals("null+/bbb", URIUtil.addPaths(null,"/bbb"),"/bbb"); + + assertEquals("+null", URIUtil.addPaths("",null),""); + assertEquals("+", URIUtil.addPaths("",""),""); + assertEquals("+bbb", URIUtil.addPaths("","bbb"),"bbb"); + assertEquals("+/", URIUtil.addPaths("","/"),"/"); + assertEquals("+/bbb", URIUtil.addPaths("","/bbb"),"/bbb"); + + assertEquals("aaa+null", URIUtil.addPaths("aaa",null),"aaa"); + assertEquals("aaa+", URIUtil.addPaths("aaa",""),"aaa"); + assertEquals("aaa+bbb", URIUtil.addPaths("aaa","bbb"),"aaa/bbb"); + assertEquals("aaa+/", URIUtil.addPaths("aaa","/"),"aaa/"); + assertEquals("aaa+/bbb", URIUtil.addPaths("aaa","/bbb"),"aaa/bbb"); + + assertEquals("/+null", URIUtil.addPaths("/",null),"/"); + assertEquals("/+", URIUtil.addPaths("/",""),"/"); + assertEquals("/+bbb", URIUtil.addPaths("/","bbb"),"/bbb"); + assertEquals("/+/", URIUtil.addPaths("/","/"),"/"); + assertEquals("/+/bbb", URIUtil.addPaths("/","/bbb"),"/bbb"); + + assertEquals("aaa/+null", URIUtil.addPaths("aaa/",null),"aaa/"); + assertEquals("aaa/+", URIUtil.addPaths("aaa/",""),"aaa/"); + assertEquals("aaa/+bbb", URIUtil.addPaths("aaa/","bbb"),"aaa/bbb"); + assertEquals("aaa/+/", URIUtil.addPaths("aaa/","/"),"aaa/"); + assertEquals("aaa/+/bbb", URIUtil.addPaths("aaa/","/bbb"),"aaa/bbb"); + + assertEquals(";JS+null", URIUtil.addPaths(";JS",null),";JS"); + assertEquals(";JS+", URIUtil.addPaths(";JS",""),";JS"); + assertEquals(";JS+bbb", URIUtil.addPaths(";JS","bbb"),"bbb;JS"); + assertEquals(";JS+/", URIUtil.addPaths(";JS","/"),"/;JS"); + assertEquals(";JS+/bbb", URIUtil.addPaths(";JS","/bbb"),"/bbb;JS"); + + assertEquals("aaa;JS+null", URIUtil.addPaths("aaa;JS",null),"aaa;JS"); + assertEquals("aaa;JS+", URIUtil.addPaths("aaa;JS",""),"aaa;JS"); + assertEquals("aaa;JS+bbb", URIUtil.addPaths("aaa;JS","bbb"),"aaa/bbb;JS"); + assertEquals("aaa;JS+/", URIUtil.addPaths("aaa;JS","/"),"aaa/;JS"); + assertEquals("aaa;JS+/bbb", URIUtil.addPaths("aaa;JS","/bbb"),"aaa/bbb;JS"); + + assertEquals("aaa;JS+null", URIUtil.addPaths("aaa/;JS",null),"aaa/;JS"); + assertEquals("aaa;JS+", URIUtil.addPaths("aaa/;JS",""),"aaa/;JS"); + assertEquals("aaa;JS+bbb", URIUtil.addPaths("aaa/;JS","bbb"),"aaa/bbb;JS"); + assertEquals("aaa;JS+/", URIUtil.addPaths("aaa/;JS","/"),"aaa/;JS"); + assertEquals("aaa;JS+/bbb", URIUtil.addPaths("aaa/;JS","/bbb"),"aaa/bbb;JS"); + + assertEquals("?A=1+null", URIUtil.addPaths("?A=1",null),"?A=1"); + assertEquals("?A=1+", URIUtil.addPaths("?A=1",""),"?A=1"); + assertEquals("?A=1+bbb", URIUtil.addPaths("?A=1","bbb"),"bbb?A=1"); + assertEquals("?A=1+/", URIUtil.addPaths("?A=1","/"),"/?A=1"); + assertEquals("?A=1+/bbb", URIUtil.addPaths("?A=1","/bbb"),"/bbb?A=1"); + + assertEquals("aaa?A=1+null", URIUtil.addPaths("aaa?A=1",null),"aaa?A=1"); + assertEquals("aaa?A=1+", URIUtil.addPaths("aaa?A=1",""),"aaa?A=1"); + assertEquals("aaa?A=1+bbb", URIUtil.addPaths("aaa?A=1","bbb"),"aaa/bbb?A=1"); + assertEquals("aaa?A=1+/", URIUtil.addPaths("aaa?A=1","/"),"aaa/?A=1"); + assertEquals("aaa?A=1+/bbb", URIUtil.addPaths("aaa?A=1","/bbb"),"aaa/bbb?A=1"); + + assertEquals("aaa?A=1+null", URIUtil.addPaths("aaa/?A=1",null),"aaa/?A=1"); + assertEquals("aaa?A=1+", URIUtil.addPaths("aaa/?A=1",""),"aaa/?A=1"); + assertEquals("aaa?A=1+bbb", URIUtil.addPaths("aaa/?A=1","bbb"),"aaa/bbb?A=1"); + assertEquals("aaa?A=1+/", URIUtil.addPaths("aaa/?A=1","/"),"aaa/?A=1"); + assertEquals("aaa?A=1+/bbb", URIUtil.addPaths("aaa/?A=1","/bbb"),"aaa/bbb?A=1"); + + assertEquals(";JS?A=1+null", URIUtil.addPaths(";JS?A=1",null),";JS?A=1"); + assertEquals(";JS?A=1+", URIUtil.addPaths(";JS?A=1",""),";JS?A=1"); + assertEquals(";JS?A=1+bbb", URIUtil.addPaths(";JS?A=1","bbb"),"bbb;JS?A=1"); + assertEquals(";JS?A=1+/", URIUtil.addPaths(";JS?A=1","/"),"/;JS?A=1"); + assertEquals(";JS?A=1+/bbb", URIUtil.addPaths(";JS?A=1","/bbb"),"/bbb;JS?A=1"); + + assertEquals("aaa;JS?A=1+null", URIUtil.addPaths("aaa;JS?A=1",null),"aaa;JS?A=1"); + assertEquals("aaa;JS?A=1+", URIUtil.addPaths("aaa;JS?A=1",""),"aaa;JS?A=1"); + assertEquals("aaa;JS?A=1+bbb", URIUtil.addPaths("aaa;JS?A=1","bbb"),"aaa/bbb;JS?A=1"); + assertEquals("aaa;JS?A=1+/", URIUtil.addPaths("aaa;JS?A=1","/"),"aaa/;JS?A=1"); + assertEquals("aaa;JS?A=1+/bbb", URIUtil.addPaths("aaa;JS?A=1","/bbb"),"aaa/bbb;JS?A=1"); + + assertEquals("aaa;JS?A=1+null", URIUtil.addPaths("aaa/;JS?A=1",null),"aaa/;JS?A=1"); + assertEquals("aaa;JS?A=1+", URIUtil.addPaths("aaa/;JS?A=1",""),"aaa/;JS?A=1"); + assertEquals("aaa;JS?A=1+bbb", URIUtil.addPaths("aaa/;JS?A=1","bbb"),"aaa/bbb;JS?A=1"); + assertEquals("aaa;JS?A=1+/", URIUtil.addPaths("aaa/;JS?A=1","/"),"aaa/;JS?A=1"); + assertEquals("aaa;JS?A=1+/bbb", URIUtil.addPaths("aaa/;JS?A=1","/bbb"),"aaa/bbb;JS?A=1"); + + } + + /* ------------------------------------------------------------ */ + public void testCompactPath() + { + assertEquals("/foo/bar", URIUtil.compactPath("/foo/bar")); + assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("/foo/bar?a=b//c")); + + assertEquals("/foo/bar", URIUtil.compactPath("//foo//bar")); + assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("//foo//bar?a=b//c")); + + assertEquals("/foo/bar", URIUtil.compactPath("/foo///bar")); + assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("/foo///bar?a=b//c")); + } + + /* ------------------------------------------------------------ */ + public void testParentPath() + { + assertEquals("parent /aaa/bbb/","/aaa/", URIUtil.parentPath("/aaa/bbb/")); + assertEquals("parent /aaa/bbb","/aaa/", URIUtil.parentPath("/aaa/bbb")); + assertEquals("parent /aaa/","/", URIUtil.parentPath("/aaa/")); + assertEquals("parent /aaa","/", URIUtil.parentPath("/aaa")); + assertEquals("parent /",null, URIUtil.parentPath("/")); + assertEquals("parent null",null, URIUtil.parentPath(null)); + + } + + /* ------------------------------------------------------------ */ + public void testCanonicalPath() + { + String[][] canonical = + { + {"/aaa/bbb/","/aaa/bbb/"}, + {"/aaa//bbb/","/aaa//bbb/"}, + {"/aaa///bbb/","/aaa///bbb/"}, + {"/aaa/./bbb/","/aaa/bbb/"}, + {"/aaa/../bbb/","/bbb/"}, + {"/aaa/./../bbb/","/bbb/"}, + {"/aaa/bbb/ccc/../../ddd/","/aaa/ddd/"}, + {"./bbb/","bbb/"}, + {"./aaa/../bbb/","bbb/"}, + {"./",""}, + {".//",".//"}, + {".///",".///"}, + {"/.","/"}, + {"//.","//"}, + {"///.","///"}, + {"/","/"}, + {"aaa/bbb","aaa/bbb"}, + {"aaa/","aaa/"}, + {"aaa","aaa"}, + {"/aaa/bbb","/aaa/bbb"}, + {"/aaa//bbb","/aaa//bbb"}, + {"/aaa/./bbb","/aaa/bbb"}, + {"/aaa/../bbb","/bbb"}, + {"/aaa/./../bbb","/bbb"}, + {"./bbb","bbb"}, + {"./aaa/../bbb","bbb"}, + {"aaa/bbb/..","aaa/"}, + {"aaa/bbb/../","aaa/"}, + {"/aaa//../bbb","/aaa/bbb"}, + {"/aaa/./../bbb","/bbb"}, + {"./",""}, + {".",""}, + {"",""}, + {"..",null}, + {"./..",null}, + {"aaa/../..",null}, + {"/foo/bar/../../..",null}, + {"/../foo",null}, + {"/foo/.","/foo/"}, + {"a","a"}, + {"a/","a/"}, + {"a/.","a/"}, + {"a/..",""}, + {"a/../..",null}, + {"/foo/../bar//","/bar//"}, + }; + + for (int t=0;t=0); + assertTrue(s.indexOf("\"n2\":2")>=0); + assertTrue(s.indexOf("\"n3\":-3.0E-11")>=0); + assertTrue(s.indexOf("\"n4\":\"4\\n")>=0); + assertTrue(s.indexOf("\"n5\":[\"a\",\"b\",")>=0); + assertTrue(s.indexOf("\"n6\":{}")>=0); + assertTrue(s.indexOf("\"n7\":{\"x\":\"value\"}")>=0); + assertTrue(s.indexOf("\"n8\":[1,2,3,4]")>=0); + assertTrue(s.indexOf("\"n9\":[{}, [], {}]")>=0); + assertTrue(s.indexOf("\"w0\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle0\",\"nested\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle1\",\"nested\":null,\"number\":-101},\"number\":100}")>=0); + + Gadget gadget = new Gadget(); + gadget.setShields(42); + gadget.setWoggles(new Woggle[]{w0,w1}); + + s = JSON.toString(new Gadget[]{gadget}); + assertTrue(s.startsWith("[")); + assertTrue(s.indexOf("\"modulated\":false")>=0); + assertTrue(s.indexOf("\"shields\":42")>=0); + assertTrue(s.indexOf("\"name\":\"woggle0\"")>=0); + assertTrue(s.indexOf("\"name\":\"woggle1\"")>=0); + + } + + + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + JSON.registerConvertor(Gadget.class,new JSONObjectConvertor(false)); + } + + + + /* ------------------------------------------------------------ */ + public void testParse() + { + Map map = (Map)JSON.parse(test); + assertEquals(new Long(100),map.get("onehundred")); + assertEquals("fred",map.get("name")); + assertEquals(-0.2,map.get("small")); + assertTrue(map.get("array").getClass().isArray()); + assertTrue(map.get("w0") instanceof Woggle); + assertTrue(((Woggle)map.get("w0")).nested instanceof Woggle); + assertEquals(-101,((Woggle)((Woggle)map.get("w0")).nested).number); + + + test="{\"data\":{\"source\":\"15831407eqdaawf7\",\"widgetId\":\"Magnet_8\"},\"channel\":\"/magnets/moveStart\",\"connectionId\":null,\"clientId\":\"15831407eqdaawf7\"}"; + map = (Map)JSON.parse(test); + + + } + + /* ------------------------------------------------------------ */ + public void testParseReader() throws Exception + { + Map map = (Map)JSON.parse(new StringReader(test)); + + assertEquals(new Long(100),map.get("onehundred")); + assertEquals("fred",map.get("name")); + assertTrue(map.get("array").getClass().isArray()); + assertTrue(map.get("w0") instanceof Woggle); + assertTrue(((Woggle)map.get("w0")).nested instanceof Woggle); + + test="{\"data\":{\"source\":\"15831407eqdaawf7\",\"widgetId\":\"Magnet_8\"},\"channel\":\"/magnets/moveStart\",\"connectionId\":null,\"clientId\":\"15831407eqdaawf7\"}"; + map = (Map)JSON.parse(test); + } + + /* ------------------------------------------------------------ */ + public void testStripComment() + { + String test="\n\n\n\t\t "+ + "// ignore this ,a [ \" \n"+ + "/* "+ + "{ "+ + "\"onehundred\" : 100 ,"+ + "\"name\" : \"fred\" ," + + "\"empty\" : {} ," + + "\"map\" : {\"a\":-1.0e2} ," + + "\"array\" : [\"a\",-1.0e2,[],null,true,false] ," + + "} */"; + + Object o = JSON.parse(test,false); + assertTrue(o==null); + o = JSON.parse(test,true); + assertTrue(o instanceof Map); + assertEquals("fred",((Map)o).get("name")); + + } + + /* ------------------------------------------------------------ */ + public void testQuote() + { + String test="\"abc123|\\\"|\\\\|\\/|\\b|\\f|\\n|\\r|\\t|\\uaaaa|\""; + + String result = (String)JSON.parse(test,false); + assertEquals("abc123|\"|\\|/|\b|\f|\n|\r|\t|\uaaaa|",result); + } + + /* ------------------------------------------------------------ */ + public void testBigDecimal() + { + Object obj = JSON.parse("1.0E7"); + assertTrue(obj instanceof Double); + BigDecimal bd = BigDecimal.valueOf(10000000d); + String string = JSON.toString(new Object[]{bd}); + obj = Array.get(JSON.parse(string),0); + assertTrue(obj instanceof Double); + } + + /* ------------------------------------------------------------ */ + public static class Gadget + { + private boolean modulated; + private long shields; + private Woggle[] woggles; + /* ------------------------------------------------------------ */ + /** + * @return the modulated + */ + public boolean isModulated() + { + return modulated; + } + /* ------------------------------------------------------------ */ + /** + * @param modulated the modulated to set + */ + public void setModulated(boolean modulated) + { + this.modulated=modulated; + } + /* ------------------------------------------------------------ */ + /** + * @return the shields + */ + public long getShields() + { + return shields; + } + /* ------------------------------------------------------------ */ + /** + * @param shields the shields to set + */ + public void setShields(long shields) + { + this.shields=shields; + } + /* ------------------------------------------------------------ */ + /** + * @return the woggles + */ + public Woggle[] getWoggles() + { + return woggles; + } + /* ------------------------------------------------------------ */ + /** + * @param woggles the woggles to set + */ + public void setWoggles(Woggle[] woggles) + { + this.woggles=woggles; + } + } + + /* ------------------------------------------------------------ */ + public void testConvertor() + { + // test case#1 - force timezone to GMT + JSON json = new JSON(); + json.addConvertor(Date.class, new JSONDateConvertor("MM/dd/yyyy HH:mm:ss zzz", TimeZone.getTimeZone("GMT"),false)); + json.addConvertor(Object.class,new JSONObjectConvertor()); + + Woggle w0 = new Woggle(); + Gizmo g0 = new Gizmo(); + + w0.name="woggle0"; + w0.nested=g0; + w0.number=100; + g0.name="woggle1"; + g0.nested=null; + g0.number=-101; + g0.tested=true; + + HashMap map = new HashMap(); + Date dummyDate = new Date(1); + map.put("date", dummyDate); + map.put("w0",w0); + + StringBuffer buf = new StringBuffer(); + json.append(buf,map); + String js=buf.toString(); + + System.err.println(js); + assertTrue(js.indexOf("\"date\":\"01/01/1970 00:00:00 GMT\"")>=0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0); + assertTrue(js.indexOf("\"tested\":true")>=0); + + // test case#3 + TimeZone tzone = TimeZone.getTimeZone("JST"); + String tzone3Letter = tzone.getDisplayName(false, TimeZone.SHORT); + String format = "EEE MMMMM dd HH:mm:ss zzz yyyy"; + + Locale l = new Locale("ja", "JP"); + if (l!=null) + { + json.addConvertor(Date.class, new JSONDateConvertor(format, tzone, false, l)); + buf = new StringBuffer(); + json.append(buf,map); + js=buf.toString(); + + assertTrue(js.indexOf("\"date\":\"\u6728 1\u6708 01 09:00:00 JST 1970\"")>=0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0); + assertTrue(js.indexOf("\"tested\":true")>=0); + } + + // test case#4 + json.addConvertor(Date.class,new JSONDateConvertor(true)); + w0.nested=null; + buf = new StringBuffer(); + json.append(buf,map); + js=buf.toString(); + System.err.println(js); + assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")<0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0); + + map=(HashMap)json.parse(new JSON.StringSource(js)); + + assertTrue(map.get("date") instanceof Date); + assertTrue(map.get("w0") instanceof Woggle); + } + + enum Color { Red, Green, Blue }; + + public void testEnumConvertor() + { + JSON json = new JSON(); + Locale l = new Locale("en", "US"); + json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),false,l)); + json.addConvertor(Enum.class,new JSONEnumConvertor(false)); + json.addConvertor(Object.class,new JSONObjectConvertor()); + + Woggle w0 = new Woggle(); + Gizmo g0 = new Gizmo(); + + w0.name="woggle0"; + w0.nested=g0; + w0.number=100; + w0.other=Color.Blue; + g0.name="woggle1"; + g0.nested=null; + g0.number=-101; + g0.tested=true; + g0.other=Color.Green; + + HashMap map = new HashMap(); + map.put("date",new Date(1)); + map.put("w0",w0); + map.put("g0",g0); + + StringBuffer buf = new StringBuffer(); + json.append(buf,map); + String js=buf.toString(); + + System.err.println(js); + assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")>=0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0); + assertTrue(js.indexOf("\"tested\":true")>=0); + assertTrue(js.indexOf("\"Green\"")>=0); + assertTrue(js.indexOf("\"Blue\"")<0); + + json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),true,l)); + json.addConvertor(Enum.class,new JSONEnumConvertor(false)); + w0.nested=null; + buf = new StringBuffer(); + json.append(buf,map); + js=buf.toString(); + System.err.println(js); + assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")<0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0); + assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0); + + Map map2=(HashMap)json.parse(new JSON.StringSource(js)); + + assertTrue(map2.get("date") instanceof Date); + assertTrue(map2.get("w0") instanceof Woggle); + assertEquals(null, ((Woggle)map2.get("w0")).getOther() ); + assertEquals(Color.Green.toString(), ((Map)map2.get("g0")).get("other")); + + + + json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),true,l)); + json.addConvertor(Enum.class,new JSONEnumConvertor(true)); + buf = new StringBuffer(); + json.append(buf,map); + js=buf.toString(); + System.err.println(js); + map2=(HashMap)json.parse(new JSON.StringSource(js)); + + assertTrue(map2.get("date") instanceof Date); + assertTrue(map2.get("w0") instanceof Woggle); + assertEquals(null, ((Woggle)map2.get("w0")).getOther() ); + Object o=((Map)map2.get("g0")).get("other"); + assertEquals(Color.Green, o); + + } + + /* ------------------------------------------------------------ */ + public static class Gizmo + { + String name; + Gizmo nested; + long number; + boolean tested; + Object other; + + public String getName() + { + return name; + } + public Gizmo getNested() + { + return nested; + } + public long getNumber() + { + return number; + } + public boolean isTested() + { + return tested; + } + public Object getOther() + { + return other; + } + } + + /* ------------------------------------------------------------ */ + public static class Woggle extends Gizmo implements JSON.Convertible + { + + public Woggle() + { + } + + public void fromJSON(Map object) + { + name=(String)object.get("name"); + nested=(Gizmo)object.get("nested"); + number=((Number)object.get("number")).intValue(); + } + + public void toJSON(Output out) + { + out.addClass(Woggle.class); + out.add("name",name); + out.add("nested",nested); + out.add("number",number); + } + + public String toString() + { + return name+"<<"+nested+">>"+number; + } + + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java new file mode 100644 index 00000000000..7740255e498 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java @@ -0,0 +1,230 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.component; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import junit.textui.TestRunner; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.StdErrLog; + +public class LifeCycleListenerTest extends TestCase +{ + static Exception cause = new Exception("expected test exception"); + + public LifeCycleListenerTest(String name) + { + super(name); + } + + public static void main(String[] args) + { + TestRunner.run(suite()); + } + + public static Test suite() + { + TestSuite suite = new TestSuite(LifeCycleListenerTest.class); + return suite; + } + + public void testStart() throws Exception + { + TestLifeCycle lifecycle = new TestLifeCycle(); + TestListener listener = new TestListener(); + lifecycle.addLifeCycleListener(listener); + ((StdErrLog)Log.getLog()).setHideStacks(true); + lifecycle.setCause(cause); + + try + { + lifecycle.start(); + assertTrue(false); + } + catch(Exception e) + { + assertEquals(cause,e); + assertEquals(cause,listener.getCause()); + } + lifecycle.setCause(null); + ((StdErrLog)Log.getLog()).setHideStacks(false); + + + lifecycle.start(); + + // check that the starting event has been thrown + assertTrue("The staring event didn't occur",listener.starting); + + // check that the started event has been thrown + assertTrue("The started event didn't occur",listener.started); + + // check that the starting event occurs before the started event + assertTrue("The starting event must occur before the started event",listener.startingTime <= listener.startedTime); + + // check that the lifecycle's state is started + assertTrue("The lifecycle state is not started",lifecycle.isStarted()); + } + + public void testStop() throws Exception + { + TestLifeCycle lifecycle = new TestLifeCycle(); + TestListener listener = new TestListener(); + lifecycle.addLifeCycleListener(listener); + + + // need to set the state to something other than stopped or stopping or + // else + // stop() will return without doing anything + + lifecycle.start(); + + ((StdErrLog)Log.getLog()).setHideStacks(true); + lifecycle.setCause(cause); + + try + { + lifecycle.stop(); + assertTrue(false); + } + catch(Exception e) + { + assertEquals(cause,e); + assertEquals(cause,listener.getCause()); + } + + + lifecycle.setCause(null); + ((StdErrLog)Log.getLog()).setHideStacks(false); + + lifecycle.stop(); + + // check that the stopping event has been thrown + assertTrue("The stopping event didn't occur",listener.stopping); + + // check that the stopped event has been thrown + assertTrue("The stopped event didn't occur",listener.stopped); + + // check that the stopping event occurs before the stopped event + assertTrue("The stopping event must occur before the stopped event",listener.stoppingTime <= listener.stoppedTime); + // System.out.println("STOPING TIME : " + listener.stoppingTime + " : " + listener.stoppedTime); + + // check that the lifecycle's state is stopped + assertTrue("The lifecycle state is not stooped",lifecycle.isStopped()); + } + + private class TestLifeCycle extends AbstractLifeCycle + { + Exception cause; + + private TestLifeCycle() + { + } + + protected void doStart() throws Exception + { + if (cause!=null) + throw cause; + super.doStart(); + } + + protected void doStop() throws Exception + { + if (cause!=null) + throw cause; + super.doStop(); + } + + public void setCause(Exception e) + { + cause=e; + } + } + + private class TestListener implements LifeCycle.Listener + { + + private boolean failure = false; + private boolean started = false; + private boolean starting = false; + private boolean stopped = false; + private boolean stopping = false; + + private long startedTime; + private long startingTime; + private long stoppedTime; + private long stoppingTime; + + private Throwable cause = null; + + public void lifeCycleFailure(LifeCycle event, Throwable cause) + { + failure = true; + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } + + public void lifeCycleStarted(LifeCycle event) + { + started = true; + startedTime = System.currentTimeMillis(); + } + + public void lifeCycleStarting(LifeCycle event) + { + starting = true; + startingTime = System.currentTimeMillis(); + + // need to sleep to make sure the starting and started times are not + // the same + try + { + Thread.sleep(1); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void lifeCycleStopped(LifeCycle event) + { + stopped = true; + stoppedTime = System.currentTimeMillis(); + } + + public void lifeCycleStopping(LifeCycle event) + { + stopping = true; + stoppingTime = System.currentTimeMillis(); + + // need to sleep to make sure the stopping and stopped times are not + // the same + try + { + Thread.sleep(1); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java new file mode 100644 index 00000000000..f7948f8e082 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java @@ -0,0 +1,33 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.log; + +import junit.framework.TestCase; + +public class LogTest extends TestCase +{ + public void testLoggerLog() + { + Logger log=new LoggerLog(Log.getLogger("test")); + log.setDebugEnabled(true); + log.debug("testing {} {}","LoggerLog","debug"); + log.info("testing {} {}","LoggerLog","info"); + log.warn("testing {} {}","LoggerLog","warn"); + log.setDebugEnabled(false); + log.debug("YOU SHOULD NOT SEE THIS!",null,null); + + log=log.getLogger("next"); + log.info("testing {} {}","LoggerLog","info"); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java new file mode 100644 index 00000000000..be6fb1c32bb --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java @@ -0,0 +1,76 @@ +// ======================================================================== +// Copyright (c) 2007-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.resource; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +import junit.framework.TestCase; + +public class ResourceCollectionTest extends TestCase +{ + + public void testMutlipleSources1() throws Exception + { + ResourceCollection rc1 = new ResourceCollection(new String[]{ + "src/test/resources/org/eclipse/jetty/util/resource/one/", + "src/test/resources/org/eclipse/jetty/util/resource/two/", + "src/test/resources/org/eclipse/jetty/util/resource/three/" + }); + assertEquals("1 - one", getContent(rc1, "1.txt")); + assertEquals("2 - two", getContent(rc1, "2.txt")); + assertEquals("3 - three", getContent(rc1, "3.txt")); + + + ResourceCollection rc2 = new ResourceCollection( + "src/test/resources/org/eclipse/jetty/util/resource/one/," + + "src/test/resources/org/eclipse/jetty/util/resource/two/," + + "src/test/resources/org/eclipse/jetty/util/resource/three/" + ); + assertEquals("1 - one", getContent(rc2, "1.txt")); + assertEquals("2 - two", getContent(rc2, "2.txt")); + assertEquals("3 - three", getContent(rc2, "3.txt")); + + for(String s : rc1.list()) + System.err.println(s); + } + + public void testMergedDir() throws Exception + { + ResourceCollection rc = new ResourceCollection(new String[]{ + "src/test/resources/org/eclipse/jetty/util/resource/one/", + "src/test/resources/org/eclipse/jetty/util/resource/two/", + "src/test/resources/org/eclipse/jetty/util/resource/three/" + }); + + Resource r = rc.addPath("dir"); + assertTrue(r instanceof ResourceCollection); + rc=(ResourceCollection)r; + assertEquals("1 - one", getContent(rc, "1.txt")); + assertEquals("2 - two", getContent(rc, "2.txt")); + assertEquals("3 - three", getContent(rc, "3.txt")); + } + + static String getContent(ResourceCollection rc, String path) throws Exception + { + StringBuilder buffer = new StringBuilder(); + String line = null; + BufferedReader br = new BufferedReader(new InputStreamReader(rc.addPath(path).getURL().openStream())); + while((line=br.readLine())!=null) + buffer.append(line); + br.close(); + return buffer.toString(); + } + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java new file mode 100644 index 00000000000..81087ba4e18 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java @@ -0,0 +1,347 @@ +// ======================================================================== +// Copyright (c) 1997-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.util.resource; + + +import java.io.File; +import java.io.FilePermission; +import java.io.InputStream; +import java.net.URL; +import java.util.jar.JarInputStream; + +import junit.framework.TestSuite; + +import org.eclipse.jetty.util.IO; + +public class ResourceTest extends junit.framework.TestCase +{ + + public static String __userDir = System.getProperty("basedir", "."); + public static URL __userURL=null; + private static String __relDir=""; + private static File tmpFile; + + private static final boolean DIR=true; + private static final boolean EXISTS=true; + + class Data + { + Resource resource; + String test; + boolean exists; + boolean dir; + String content; + + Data(Data data,String path,boolean exists, boolean dir) + throws Exception + { + this.test=data.resource+"+"+path; + resource=data.resource.addPath(path); + this.exists=exists; + this.dir=dir; + } + + Data(Data data,String path,boolean exists, boolean dir, String content) + throws Exception + { + this.test=data.resource+"+"+path; + resource=data.resource.addPath(path); + this.exists=exists; + this.dir=dir; + this.content=content; + } + + Data(URL url,boolean exists, boolean dir) + throws Exception + { + this.test=url.toString(); + this.exists=exists; + this.dir=dir; + resource=Resource.newResource(url); + } + + Data(String url,boolean exists, boolean dir) + throws Exception + { + this.test=url; + this.exists=exists; + this.dir=dir; + resource=Resource.newResource(url); + } + + Data(String url,boolean exists, boolean dir, String content) + throws Exception + { + this.test=url; + this.exists=exists; + this.dir=dir; + this.content=content; + resource=Resource.newResource(url); + } + } + + public static Data[] data; + + public ResourceTest(String name) + { + super(name); + } + + /* ------------------------------------------------------------ */ + public static void main(String[] args) + { + junit.textui.TestRunner.run(suite()); + } + + /* ------------------------------------------------------------ */ + public static junit.framework.Test suite() + { + return new TestSuite(ResourceTest.class); + } + + /* ------------------------------------------------------------ */ + protected void setUp() + throws Exception + { + if (data!=null) + return; + + File file = new File(__userDir); + file=new File(file.getCanonicalPath()); + __userURL=file.toURL(); + + __userURL = new URL(__userURL.toString() + "src/test/java/org/eclipse/jetty/util/resource/"); + FilePermission perm = (FilePermission) __userURL.openConnection().getPermission(); + __userDir = new File(perm.getName()).getCanonicalPath() + File.separatorChar; + __relDir = "src/test/java/org/eclipse/jetty/util/resource/".replace('/', File.separatorChar); + + System.err.println("User Dir="+__userDir); + System.err.println("Rel Dir="+__relDir); + System.err.println("User URL="+__userURL); + + tmpFile=File.createTempFile("test",null).getCanonicalFile(); + tmpFile.deleteOnExit(); + + data = new Data[50]; + int i=0; + + data[i++]=new Data(tmpFile.toString(),EXISTS,!DIR); + + int rt=i; + data[i++]=new Data(__userURL,EXISTS,DIR); + data[i++]=new Data(__userDir,EXISTS,DIR); + data[i++]=new Data(__relDir,EXISTS,DIR); + data[i++]=new Data(__userURL+"ResourceTest.java",EXISTS,!DIR); + data[i++]=new Data(__userDir+"ResourceTest.java",EXISTS,!DIR); + data[i++]=new Data(__relDir+"ResourceTest.java",EXISTS,!DIR); + data[i++]=new Data(__userURL+"NoName.txt",!EXISTS,!DIR); + data[i++]=new Data(__userDir+"NoName.txt",!EXISTS,!DIR); + data[i++]=new Data(__relDir+"NoName.txt",!EXISTS,!DIR); + + data[i++]=new Data(data[rt],"ResourceTest.java",EXISTS,!DIR); + data[i++]=new Data(data[rt],"/ResourceTest.java",EXISTS,!DIR); + data[i++]=new Data(data[rt],"NoName.txt",!EXISTS,!DIR); + data[i++]=new Data(data[rt],"/NoName.txt",!EXISTS,!DIR); + + int td=i; + data[i++]=new Data(data[rt],"TestData",EXISTS,DIR); + data[i++]=new Data(data[rt],"TestData/",EXISTS,DIR); + data[i++]=new Data(data[td],"alphabet.txt",EXISTS,!DIR,"ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + + data[i++]=new Data("jar:file:/somejar.jar!/content/",!EXISTS,DIR); + data[i++]=new Data("jar:file:/somejar.jar!/",!EXISTS,DIR); + + int tj=i; + data[i++]=new Data("jar:"+__userURL+"TestData/test.zip!/",EXISTS,DIR); + data[i++]=new Data(data[tj],"Unkown",!EXISTS,!DIR); + data[i++]=new Data(data[tj],"/Unkown/",!EXISTS,DIR); + + data[i++]=new Data(data[tj],"subdir",EXISTS,DIR); + data[i++]=new Data(data[tj],"/subdir/",EXISTS,DIR); + data[i++]=new Data(data[tj],"alphabet",EXISTS,!DIR, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + data[i++]=new Data(data[tj],"/subdir/alphabet",EXISTS,!DIR, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + + Resource base = Resource.newResource(__userDir); + Resource dir0 = base.addPath("TestData"); + assertTrue(dir0.isDirectory()); + assertTrue(dir0.toString().endsWith("/")); + assertTrue(dir0.getAlias()==null); + Resource dir1 = base.addPath("TestData/"); + assertTrue(dir1.isDirectory()); + assertTrue(dir1.toString().endsWith("/")); + assertTrue(dir1.getAlias()==null); + + + } + + + /* ------------------------------------------------------------ */ + protected void tearDown() + throws Exception + { + } + + + /* ------------------------------------------------------------ */ + public void testResourceExists() + { + for (int i=0;i0); + assertTrue(r.getFile().toString().indexOf("a file with,spe#ials")>0); + } + + /* ------------------------------------------------------------ */ + public void testJarFile() + throws Exception + { + + String s = "jar:"+__userURL+"TestData/test.zip!/subdir/"; + Resource r = Resource.newResource(s); + InputStream is = r.getInputStream(); + JarInputStream jin = new JarInputStream(is); + assertNotNull(is); + assertNotNull(jin); + + } + + /** + * Test a class path resource for existence. + */ + public void testClassPathResourceClassRelative() + { + final String classPathName="Resource.class"; + + Resource resource=Resource.newClassPathResource(classPathName); + + assertTrue(resource!=null); + + // A class path cannot be a directory + assertFalse("Class path cannot be a directory.",resource.isDirectory()); + + // A class path must exist + assertTrue("Class path resource does not exist.",resource.exists()); + } + + /** + * Test a class path resource for existence. + */ + public void testClassPathResourceClassAbsolute() + { + final String classPathName="/org/eclipse/jetty/util/resource/Resource.class"; + + Resource resource=Resource.newClassPathResource(classPathName); + + assertTrue(resource!=null); + + // A class path cannot be a directory + assertFalse("Class path cannot be a directory.",resource.isDirectory()); + + // A class path must exist + assertTrue("Class path resource does not exist.",resource.exists()); + } + + /** + * Test a class path resource for directories. + */ + public void testClassPathResourceDirectory() throws Exception + { + final String classPathName="/"; + + Resource resource=Resource.newClassPathResource(classPathName); + + + assertTrue(resource!=null); + + // A class path must be a directory + assertTrue("Class path must be a directory.",resource.isDirectory()); + + assertTrue("Class path returned file must be a directory.",resource.getFile().isDirectory()); + + // A class path must exist + assertTrue("Class path resource does not exist.",resource.exists()); + } + + /** + * Test a class path resource for a file. + */ + public void testClassPathResourceFile() throws Exception + { + final String fileName="resource.txt"; + final String classPathName="/"+fileName; + + // Will locate a resource in the class path + Resource resource=Resource.newClassPathResource(classPathName); + + assertTrue(resource!=null); + + // A class path cannot be a directory + assertFalse("Class path must be a directory.",resource.isDirectory()); + + assertTrue(resource!=null); + + File file=resource.getFile(); + + assertTrue("File returned from class path should not be null.",file!=null); + assertEquals("File name from class path is not equal.",fileName,file.getName()); + assertTrue("File returned from class path should be a file.",file.isFile()); + + // A class path must exist + assertTrue("Class path resource does not exist.",resource.exists()); + } + + + + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/alphabet.txt b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/alphabet.txt new file mode 100644 index 00000000000..a6860d918df --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/alphabet.txt @@ -0,0 +1 @@ +ABCDEFGHIJKLMNOPQRSTUVWXYZ \ No newline at end of file diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/alt.zip b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/alt.zip new file mode 100644 index 0000000000000000000000000000000000000000..d0967698e8337a96dba220b742d31c8dbdc8ebaf GIT binary patch literal 840 zcmWIWW@Zs#-~htNbyZpnNPv@pg~8V~#8KDN&rSc|DFy~+h5&DN4v-2asImZ@nni#r z;F^6M{XE@VgG2Ou-9G!CIql=Et9OytTUYDcne&^246YbIcv__A<*VcAd$DvC3#Y_u z$s^Jdni(w9pK1O`QxktG9xgV0$ulwSif5{?Gd~r5EMf#Zgu`lQlsnKG5Jot}C%*{A z7wkY;*SwOV%+%t`{^!nnpZ3w$^}4$H#Mz7HSBx+5>zxh$a9Te&z#!DXV2hFA1|y?z z10w^23)i1t()R>9bM+}51}&3|-_M>8{@||{XkZd-uwe_+DpP}IjVovUE?4_%@(&U=Hp4T;?%dVb3tFPthwdM-H?#VM3 z`GJ-fdg^#Sff&zL6!1tLs96=S@hl+Y^Ycr*^}T%c&!0JY?u>Ssho1Je(;$nkXrBZ6 z+|T#4zL)>i)84upFR^=xK7Bs@Mf1{~U@xqxtEx8wnqUgHgON#u0X1E~A{~@2Pysxm zL8&Cb8&xZEG=WkD0@wnXaIHvb1la^obRvf>C^`|~7?25#Ty#yyAqEO|1PEqCjs$dl e$f1Dh&rKkGz|alwW@Q7J#tMWoKru0JzyJVF#Kd&~ literal 0 HcmV?d00001 diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/test.zip b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/TestData/test.zip new file mode 100644 index 0000000000000000000000000000000000000000..93d14cc0a3e700d50855143455798394a5cd68fc GIT binary patch literal 1406 zcmWIWW@Zs#-~d7Q08IuYz{$YE;OiRVsO#zHrvL900|PTdfHyk_NCgvASpZJWB0v>z z&AyI)o^GzeA$q=UpMB4q_VL!$yU6RUt99L@=Q#-;+g8}%uhuhix|PKShe_WjtkHZ5JtFSLXa&E-5N2EG_>2;mfCQAHRO)=iuSuO^nP8O^vs2FnItm;S}%0N4!9JZm0=dK$?MJDVn1|CKQ(@rDPWABV0ZU zO%Xpr5rrN=H<=G%GHL!;iRMH>gh4>>VE71Kxg<8_6#5O_4hd{_5a&xqCJ_eId;m-2 zpnQM|;0YU)D+0VxwIZiXP%c0KTObpz6)8_3n*d6|$O#dg-QY$X2C`s5hOP@a@qv;U z0*EoflPpLR7X$8eiO>`Q)C7-HgbzWf3E6$1bcz7A2u;|W3DS)0Oi)rpfa?g&&?rN8 zEqYXgQXc{+03!?PTI95da5qRhvb#a45dr+MX~*URkTJ+U03|pC*o@5>xL;V=Kq^6@ Mp9ECL#th;C0Kdf + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-webapp + Jetty :: Webapp Application Support + Jetty web application support + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + config.xml + + + + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + J2SE-1.5 + org.eclipse.jetty.webapp;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + org.eclipse.jetty + jetty-xml + ${project.version} + + + junit + junit + test + + + org.eclipse.jetty + jetty-servlet + ${project.version} + + + diff --git a/jetty-webapp/src/main/config/etc/webdefault.xml b/jetty-webapp/src/main/config/etc/webdefault.xml new file mode 100644 index 00000000000..7c45fff9e85 --- /dev/null +++ b/jetty-webapp/src/main/config/etc/webdefault.xml @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + Default web.xml file. + This file is applied to a Web application before it's own WEB_INF/web.xml file + + + + + + + + + + + org.eclipse.jetty.server.webapp.ContainerIncludeTLDJarPattern + jsp-api-.*\.jar|jsp-.*\.jar + + + + + + + + + + + org.eclipse.jetty.server.webapp.ContainerIncludeAnnotationJarPattern + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default + org.eclipse.jetty.servlet.DefaultServlet + + acceptRanges + true + + + dirAllowed + true + + + redirectWelcome + false + + + maxCacheSize + 256000000 + + + maxCachedFileSize + 10000000 + + + maxCachedFiles + 1000 + + + cacheType + both + + + gzip + true + + + useFileMappedBuffer + true + + + 0 + + + default / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jsp + org.apache.jasper.servlet.JspServlet + + logVerbosityLevel + DEBUG + + + fork + false + + + xpoweredBy + false + + + 0 + + + + jsp + *.jsp + *.jspf + *.jspx + *.xsp + *.JSP + *.JSPF + *.JSPX + *.XSP + + + + + + + + + + + + + + + + + + + + + + + + + + + + 30 + + + + + + + + + + + + + index.html + index.htm + index.jsp + + + + + arISO-8859-6 + beISO-8859-5 + bgISO-8859-5 + caISO-8859-1 + csISO-8859-2 + daISO-8859-1 + deISO-8859-1 + elISO-8859-7 + enISO-8859-1 + esISO-8859-1 + etISO-8859-1 + fiISO-8859-1 + frISO-8859-1 + hrISO-8859-2 + huISO-8859-2 + isISO-8859-1 + itISO-8859-1 + iwISO-8859-8 + jaShift_JIS + koEUC-KR + ltISO-8859-2 + lvISO-8859-2 + mkISO-8859-5 + nlISO-8859-1 + noISO-8859-1 + plISO-8859-2 + ptISO-8859-1 + roISO-8859-2 + ruISO-8859-5 + shISO-8859-5 + skISO-8859-2 + slISO-8859-2 + sqISO-8859-2 + srISO-8859-5 + svISO-8859-1 + trISO-8859-9 + ukISO-8859-5 + zhGB2312 + zh_TWBig5 + + + + + Disable TRACE + / + TRACE + + + + + + diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java new file mode 100644 index 00000000000..76c7b42e0fa --- /dev/null +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java @@ -0,0 +1,77 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.webapp; + + +/* ------------------------------------------------------------------------------- */ +/** Base Class for WebApplicationContext Configuration. + * This class can be extended to customize or extend the configuration + * of the WebApplicationContext. If WebApplicationContext.setConfiguration is not + * called, then an XMLConfiguration instance is created. + * + * + */ +public interface Configuration +{ + /* ------------------------------------------------------------------------------- */ + /** Set up a context on which to perform the configuration. + * @param context + */ + public void setWebAppContext (WebAppContext context); + + /* ------------------------------------------------------------------------------- */ + /** Get the context on which the configuration is performed. + */ + public WebAppContext getWebAppContext(); + + /* ------------------------------------------------------------------------------- */ + /** Configure ClassPath. + * This method is called to configure the context ClassLoader. It is called just + * after a new WebAppClassLoader is constructed and before it has been used. + * Class paths may be added, options changed or the loader totally replaced. + * @throws Exception + */ + public void configureClassLoader() + throws Exception; + + /* ------------------------------------------------------------------------------- */ + /** Configure Defaults. + * This method is called to intialize the context to the containers default configuration. + * Typically this would mean application of the webdefault.xml file. + * @throws Exception + */ + public void configureDefaults() + throws Exception; + + + /* ------------------------------------------------------------------------------- */ + /** Configure WebApp. + * This method is called to apply the standard and vendor deployment descriptors. + * Typically this is web.xml and jetty-web.xml. + * @throws Exception + */ + public void configureWebApp() + throws Exception; + + /* ------------------------------------------------------------------------------- */ + /** DeConfigure WebApp. + * This method is called to undo all configuration done to this webapphandler. This is + * called to allow the context to work correctly over a stop/start cycle + * @throws Exception + */ + public void deconfigureWebApp() + throws Exception; + + +} diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java new file mode 100644 index 00000000000..4bb7a446a8e --- /dev/null +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java @@ -0,0 +1,167 @@ +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.webapp; + +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.regex.Pattern; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; + +/** + * JarScannerConfiguration + * + * Abstract base class for configurations that want to scan jars in + * WEB-INF/lib and the classloader hierarchy. + * + * Jar name matching based on regexp patterns is provided. + * + * Subclasses should implement the processEntry(URL jarUrl, JarEntry entry) + * method to handle entries in jar files whose names match the supplied + * pattern. + */ +public abstract class JarScanner +{ + + public abstract void processEntry (URL jarUrl, JarEntry entry); + + + /** + * Find jar names from the classloader matching a pattern. + * + * If the pattern is null and isNullInclusive is true, then + * all jar names in the classloader will match. + * + * A pattern is a set of acceptable jar names. Each acceptable + * jar name is a regex. Each regex can be separated by either a + * "," or a "|". If you use a "|" this or's together the jar + * name patterns. This means that ordering of the matches is + * unimportant to you. If instead, you want to match particular + * jar names, and you want to match them in order, you should + * separate the regexs with "," instead. + * + * Eg "aaa-.*\\.jar|bbb-.*\\.jar" + * Will iterate over the jar names in the classloader and match + * in any order. + * + * Eg "aaa-*\\.jar,bbb-.*\\.jar" + * Will iterate over the jar names in the classloader, matching + * all those starting with "aaa-" first, then "bbb-". + * + * If visitParent is true, then the pattern is applied to the + * parent loader hierarchy. If false, it is only applied to the + * classloader passed in. + * + * @param pattern + * @param loader + * @param isNullInclusive + * @param visitParent + * @throws Exception + */ + public void scan (Pattern pattern, ClassLoader loader, boolean isNullInclusive, boolean visitParent) + throws Exception + { + String[] patterns = (pattern==null?null:pattern.pattern().split(",")); + + List subPatterns = new ArrayList(); + for (int i=0; patterns!=null && i _extensions; + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public WebAppClassLoader(WebAppContext context) + throws IOException + { + this(null,context); + } + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public WebAppClassLoader(ClassLoader parent, WebAppContext context) + throws IOException + { + super(new URL[]{},parent!=null?parent + :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader() + :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader() + :ClassLoader.getSystemClassLoader()))); + _parent=getParent(); + _context=context; + if (_parent==null) + throw new IllegalArgumentException("no parent classloader!"); + + _extensions = new HashSet(); + _extensions.add(".jar"); + _extensions.add(".zip"); + + String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions"); + if(extensions!=null) + { + StringTokenizer tokenizer = new StringTokenizer(extensions, ",;"); + while(tokenizer.hasMoreTokens()) + _extensions.add(tokenizer.nextToken().trim()); + } + + if (context.getExtraClasspath()!=null) + addClassPath(context.getExtraClasspath()); + } + + /* ------------------------------------------------------------ */ + /** + * @return the name of the classloader + */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** + * @param name the name of the classloader + */ + public void setName(String name) + { + _name=name; + } + + + /* ------------------------------------------------------------ */ + public ContextHandler getContext() + { + return _context; + } + + /* ------------------------------------------------------------ */ + /** + * @param classPath Comma or semicolon separated path of filenames or URLs + * pointing to directories or jar files. Directories should end + * with '/'. + */ + public void addClassPath(String classPath) + throws IOException + { + if (classPath == null) + return; + + StringTokenizer tokenizer= new StringTokenizer(classPath, ",;"); + while (tokenizer.hasMoreTokens()) + { + Resource resource= _context.newResource(tokenizer.nextToken()); + if (Log.isDebugEnabled()) + Log.debug("Path resource=" + resource); + + // Resolve file path if possible + File file= resource.getFile(); + if (file != null) + { + URL url= resource.getURL(); + addURL(url); + } + else + { + // Add resource or expand jar/ + if (!resource.isDirectory() && file == null) + { + InputStream in= resource.getInputStream(); + File tmp_dir=_context.getTempDirectory(); + if (tmp_dir==null) + { + tmp_dir = File.createTempFile("jetty.cl.lib",null); + tmp_dir.mkdir(); + tmp_dir.deleteOnExit(); + } + File lib= new File(tmp_dir, "lib"); + if (!lib.exists()) + { + lib.mkdir(); + lib.deleteOnExit(); + } + File jar= File.createTempFile("Jetty-", ".jar", lib); + + jar.deleteOnExit(); + if (Log.isDebugEnabled()) + Log.debug("Extract " + resource + " to " + jar); + FileOutputStream out = null; + try + { + out= new FileOutputStream(jar); + IO.copy(in, out); + } + finally + { + IO.close(out); + } + + URL url= jar.toURL(); + addURL(url); + } + else + { + URL url= resource.getURL(); + addURL(url); + } + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @param file Checks if this file type can be added to the classpath. + */ + private boolean isFileSupported(String file) + { + int dot = file.lastIndexOf('.'); + return dot!=-1 && _extensions.contains(file.substring(dot)); + } + + /* ------------------------------------------------------------ */ + /** Add elements to the class path for the context from the jar and zip files found + * in the specified resource. + * @param lib the resource that contains the jar and/or zip files. + * @param append true if the classpath entries are to be appended to any + * existing classpath, or false if they replace the existing classpath. + * @see #setClassPath(String) + */ + public void addJars(Resource lib) + { + if (lib.exists() && lib.isDirectory()) + { + String[] files=lib.list(); + for (int f=0;files!=null && f0;) + _configurations[i].deconfigureWebApp(); + _configurations=null; + + // restore security handler + if (_securityHandler.getHandler()==null) + { + _sessionHandler.setHandler(_securityHandler); + _securityHandler.setHandler(_servletHandler); + } + + // delete temp directory if we had to create it or if it isn't called work + if (_tmpDir!=null && !_isExistingTmpDir && !isTempWorkDirectory()) //_tmpDir!=null && !"work".equals(_tmpDir.getName())) + { + IO.delete(_tmpDir); + _tmpDir=null; + } + } + finally + { + if (_ownClassLoader) + setClassLoader(null); + + _unavailable = false; + _unavailableException=null; + } + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the configurations. + */ + public String[] getConfigurationClasses() + { + return _configurationClasses; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the configurations. + */ + public Configuration[] getConfigurations() + { + return _configurations; + } + + /* ------------------------------------------------------------ */ + /** + * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml + * @return Returns the defaultsDescriptor. + */ + public String getDefaultsDescriptor() + { + return _defaultsDescriptor; + } + + /* ------------------------------------------------------------ */ + /** + * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml + * @return Returns the Override Descriptor. + */ + public String getOverrideDescriptor() + { + return _overrideDescriptor; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the permissions. + */ + public PermissionCollection getPermissions() + { + return _permissions; + } + + + /* ------------------------------------------------------------ */ + /** + * @see #setServerClasses(String[]) + * @return Returns the serverClasses. + */ + public String[] getServerClasses() + { + return _serverClasses; + } + + + /* ------------------------------------------------------------ */ + /** + * @see #setSystemClasses(String[]) + * @return Returns the systemClasses. + */ + public String[] getSystemClasses() + { + return _systemClasses; + } + + + + /* ------------------------------------------------------------ */ + public boolean isServerClass(String name) + { + name=name.replace('/','.'); + while(name.startsWith(".")) + name=name.substring(1); + + String[] server_classes = getServerClasses(); + if (server_classes!=null) + { + for (int i=0;iA. Try to use an explicit directory specifically for this webapp:

    + *
      + *
    1. + * Iff an explicit directory is set for this webapp, use it. Do NOT set + * delete on exit. + *
    2. + *
    3. + * Iff javax.servlet.context.tempdir context attribute is set for + * this webapp && exists && writeable, then use it. Do NOT set delete on exit. + *
    4. + *
    + * + *

    B. Create a directory based on global settings. The new directory + * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost + * Work out where to create this directory: + *

      + *
    1. + * Iff $(jetty.home)/work exists create the directory there. Do NOT + * set delete on exit. Do NOT delete contents if dir already exists. + *
    2. + *
    3. + * Iff WEB-INF/work exists create the directory there. Do NOT set + * delete on exit. Do NOT delete contents if dir already exists. + *
    4. + *
    5. + * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete + * contents if dir already exists. + *
    6. + *
    + * + * @return + */ + public File getTempDirectory() + { + if (_tmpDir!=null && _tmpDir.isDirectory() && _tmpDir.canWrite()) + return _tmpDir; + + // Initialize temporary directory + // + // I'm afraid that this is very much black magic. + // but if you can think of better.... + Object t = getAttribute(ServletContext.TEMPDIR); + + if (t!=null && (t instanceof File)) + { + _tmpDir=(File)t; + if (_tmpDir.isDirectory() && _tmpDir.canWrite()) + return _tmpDir; + } + + if (t!=null && (t instanceof String)) + { + try + { + _tmpDir=new File((String)t); + + if (_tmpDir.isDirectory() && _tmpDir.canWrite()) + { + if(Log.isDebugEnabled())Log.debug("Converted to File "+_tmpDir+" for "+this); + setAttribute(ServletContext.TEMPDIR,_tmpDir); + return _tmpDir; + } + } + catch(Exception e) + { + Log.warn(Log.EXCEPTION,e); + } + } + + // No tempdir so look for a work directory to use as tempDir base + File work=null; + try + { + File w=new File(System.getProperty("jetty.home"),"work"); + if (w.exists() && w.canWrite() && w.isDirectory()) + work=w; + else if (getBaseResource()!=null) + { + Resource web_inf = getWebInf(); + if (web_inf !=null && web_inf.exists()) + { + w=new File(web_inf.getFile(),"work"); + if (w.exists() && w.canWrite() && w.isDirectory()) + work=w; + } + } + } + catch(Exception e) + { + Log.ignore(e); + } + + // No tempdir set so make one! + try + { + + String temp = getCanonicalNameForWebAppTmpDir(); + + if (work!=null) + _tmpDir=new File(work,temp); + else + { + _tmpDir=new File(System.getProperty("java.io.tmpdir"),temp); + + if (_tmpDir.exists()) + { + if(Log.isDebugEnabled())Log.debug("Delete existing temp dir "+_tmpDir+" for "+this); + if (!IO.delete(_tmpDir)) + { + if(Log.isDebugEnabled())Log.debug("Failed to delete temp dir "+_tmpDir); + } + + if (_tmpDir.exists()) + { + String old=_tmpDir.toString(); + _tmpDir=File.createTempFile(temp+"_",""); + if (_tmpDir.exists()) + _tmpDir.delete(); + Log.warn("Can't reuse "+old+", using "+_tmpDir); + } + } + } + + if (!_tmpDir.exists()) + _tmpDir.mkdir(); + + //if not in a dir called "work" then we want to delete it on jvm exit + if (!isTempWorkDirectory()) + _tmpDir.deleteOnExit(); + if(Log.isDebugEnabled())Log.debug("Created temp dir "+_tmpDir+" for "+this); + } + catch(Exception e) + { + _tmpDir=null; + Log.ignore(e); + } + + if (_tmpDir==null) + { + try{ + // that didn't work, so try something simpler (ish) + _tmpDir=File.createTempFile("JettyContext",""); + if (_tmpDir.exists()) + _tmpDir.delete(); + _tmpDir.mkdir(); + _tmpDir.deleteOnExit(); + if(Log.isDebugEnabled())Log.debug("Created temp dir "+_tmpDir+" for "+this); + } + catch(IOException e) + { + Log.warn("tmpdir",e); System.exit(1); + } + } + + setAttribute(ServletContext.TEMPDIR,_tmpDir); + return _tmpDir; + } + + /** + * Check if the _tmpDir itself is called "work", or if the _tmpDir + * is in a directory called "work". + * @return + */ + public boolean isTempWorkDirectory () + { + if (_tmpDir == null) + return false; + if (_tmpDir.getName().equalsIgnoreCase("work")) + return true; + File t = _tmpDir.getParentFile(); + if (t == null) + return false; + return (t.getName().equalsIgnoreCase("work")); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the war as a file or URL string (Resource) + */ + public String getWar() + { + if (_war==null) + _war=getResourceBase(); + return _war; + } + + /* ------------------------------------------------------------ */ + public Resource getWebInf() throws IOException + { + resolveWebApp(); + + // Iw there a WEB-INF directory? + Resource web_inf= super.getBaseResource().addPath("WEB-INF/"); + if (!web_inf.exists() || !web_inf.isDirectory()) + return null; + + return web_inf; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the distributable. + */ + public boolean isDistributable() + { + return _distributable; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the extractWAR. + */ + public boolean isExtractWAR() + { + return _extractWAR; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if the webdir is copied (to allow hot replacement of jars) + */ + public boolean isCopyWebDir() + { + return _copyDir; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the java2compliant. + */ + public boolean isParentLoaderPriority() + { + return _parentLoaderPriority; + } + + /* ------------------------------------------------------------ */ + protected void loadConfigurations() + throws Exception + { + if (_configurations!=null) + return; + if (_configurationClasses==null) + _configurationClasses=__dftConfigurationClasses; + + _configurations = new Configuration[_configurationClasses.length]; + for (int i=0;i<_configurations.length;i++) + { + _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance(); + } + } + + /* ------------------------------------------------------------ */ + protected boolean isProtectedTarget(String target) + { + while (target.startsWith("//")) + target=URIUtil.compactPath(target); + + return StringUtil.startsWithIgnoreCase(target, "/web-inf") || StringUtil.startsWithIgnoreCase(target, "/meta-inf"); + } + + + /* ------------------------------------------------------------ */ + public String toString() + { + return this.getClass().getName()+"@"+Integer.toHexString(hashCode())+"{"+getContextPath()+","+(_war==null?getResourceBase():_war)+"}"; + } + + /* ------------------------------------------------------------ */ + /** Resolve Web App directory + * If the BaseResource has not been set, use the war resource to + * derive a webapp resource (expanding WAR if required). + */ + protected void resolveWebApp() throws IOException + { + Resource web_app = super.getBaseResource(); + if (web_app == null) + { + if (_war==null || _war.length()==0) + _war=getResourceBase(); + + // Set dir or WAR + web_app= newResource(_war); + + // Accept aliases for WAR files + if (web_app.getAlias() != null) + { + Log.debug(web_app + " anti-aliased to " + web_app.getAlias()); + web_app= newResource(web_app.getAlias()); + } + + if (Log.isDebugEnabled()) + Log.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory()); + + // Is the WAR usable directly? + if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:")) + { + // No - then lets see if it can be turned into a jar URL. + Resource jarWebApp= newResource("jar:" + web_app + "!/"); + if (jarWebApp.exists() && jarWebApp.isDirectory()) + { + web_app= jarWebApp; + } + } + + // If we should extract or the URL is still not usable + if (web_app.exists() && ( + (_copyDir && web_app.getFile()!= null && web_app.getFile().isDirectory()) + || + (_extractWAR && web_app.getFile()!= null && !web_app.getFile().isDirectory()) + || + (_extractWAR && web_app.getFile() == null) + || + !web_app.isDirectory() + )) + { + // Then extract it if necessary. + File extractedWebAppDir= new File(getTempDirectory(), "webapp"); + + if (web_app.getFile()!=null && web_app.getFile().isDirectory()) + { + // Copy directory + Log.info("Copy " + web_app.getFile() + " to " + extractedWebAppDir); + IO.copyDir(web_app.getFile(),extractedWebAppDir); + } + else + { + if (!extractedWebAppDir.exists()) + { + //it hasn't been extracted before so extract it + extractedWebAppDir.mkdir(); + Log.info("Extract " + _war + " to " + extractedWebAppDir); + JarResource.extract(web_app, extractedWebAppDir, false); + } + else + { + //only extract if the war file is newer + if (web_app.lastModified() > extractedWebAppDir.lastModified()) + { + extractedWebAppDir.delete(); + extractedWebAppDir.mkdir(); + Log.info("Extract " + _war + " to " + extractedWebAppDir); + JarResource.extract(web_app, extractedWebAppDir, false); + } + } + } + + web_app= Resource.newResource(extractedWebAppDir.getCanonicalPath()); + + } + + // Now do we have something usable? + if (!web_app.exists() || !web_app.isDirectory()) + { + Log.warn("Web application not found " + _war); + throw new java.io.FileNotFoundException(_war); + } + + if (Log.isDebugEnabled()) + Log.debug("webapp=" + web_app); + + // ResourcePath + super.setBaseResource(web_app); + } + } + + + /* ------------------------------------------------------------ */ + /** + * @param configurations The configuration class names. If setConfigurations is not called + * these classes are used to create a configurations array. + */ + public void setConfigurationClasses(String[] configurations) + { + _configurationClasses = configurations==null?null:(String[])configurations.clone(); + } + + /* ------------------------------------------------------------ */ + /** + * @param configurations The configurations to set. + */ + public void setConfigurations(Configuration[] configurations) + { + _configurations = configurations==null?null:(Configuration[])configurations.clone(); + } + + /* ------------------------------------------------------------ */ + /** + * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml + * @param defaultsDescriptor The defaultsDescriptor to set. + */ + public void setDefaultsDescriptor(String defaultsDescriptor) + { + _defaultsDescriptor = defaultsDescriptor; + } + + /* ------------------------------------------------------------ */ + /** + * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml + * @param overrideDescriptor The overrideDescritpor to set. + */ + public void setOverrideDescriptor(String overrideDescriptor) + { + _overrideDescriptor = overrideDescriptor; + } + + /* ------------------------------------------------------------ */ + /** + * @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists. + */ + public String getDescriptor() + { + return _descriptor; + } + + /* ------------------------------------------------------------ */ + /** + * @param descriptor the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists. + */ + public void setDescriptor(String descriptor) + { + _descriptor=descriptor; + } + + /* ------------------------------------------------------------ */ + /** + * @param distributable The distributable to set. + */ + public void setDistributable(boolean distributable) + { + this._distributable = distributable; + } + + /* ------------------------------------------------------------ */ + public void setEventListeners(EventListener[] eventListeners) + { + if (_sessionHandler!=null) + _sessionHandler.clearEventListeners(); + + super.setEventListeners(eventListeners); + + for (int i=0; eventListeners!=null && i + * Server classes/packages are classes used to implement the server and are hidden + * from the context. If the context needs to load these classes, it must have its + * own copy of them in WEB-INF/lib or WEB-INF/classes. + * A class pattern is a string of one of the forms:
    + *
    org.package.Classname
    Match a specific class
    + *
    org.package.
    Match a specific package hierarchy
    + *
    -org.package.Classname
    Exclude a specific class
    + *
    -org.package.
    Exclude a specific package hierarchy
    + *
    + * @param serverClasses The serverClasses to set. + */ + public void setServerClasses(String[] serverClasses) + { + _serverClasses = serverClasses==null?null:(String[])serverClasses.clone(); + } + + /* ------------------------------------------------------------ */ + /** + * Set the system classes patterns. + *

    + * System classes/packages are classes provided by the JVM and that + * cannot be replaced by classes of the same name from WEB-INF, + * regardless of the value of {@link #setParentLoaderPriority(boolean)}. + * A class pattern is a string of one of the forms:

    + *
    org.package.Classname
    Match a specific class
    + *
    org.package.
    Match a specific package hierarchy
    + *
    -org.package.Classname
    Exclude a specific class
    + *
    -org.package.
    Exclude a specific package hierarchy
    + *
    + * @param systemClasses The systemClasses to set. + */ + public void setSystemClasses(String[] systemClasses) + { + _systemClasses = systemClasses==null?null:(String[])systemClasses.clone(); + } + + + /* ------------------------------------------------------------ */ + /** Set temporary directory for context. + * The javax.servlet.context.tempdir attribute is also set. + * @param dir Writable temporary directory. + */ + public void setTempDirectory(File dir) + { + if (isStarted()) + throw new IllegalStateException("Started"); + + if (dir!=null) + { + try{dir=new File(dir.getCanonicalPath());} + catch (IOException e){Log.warn(Log.EXCEPTION,e);} + } + + if (dir!=null && !dir.exists()) + { + dir.mkdir(); + dir.deleteOnExit(); + } + else if (dir != null) + _isExistingTmpDir = true; + + if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite())) + throw new IllegalArgumentException("Bad temp directory: "+dir); + + _tmpDir=dir; + setAttribute(ServletContext.TEMPDIR,_tmpDir); + } + + /* ------------------------------------------------------------ */ + /** + * @param war The war to set as a file name or URL + */ + public void setWar(String war) + { + _war = war; + } + + + /* ------------------------------------------------------------ */ + /** + * @return Comma or semicolon separated path of filenames or URLs + * pointing to directories or jar files. Directories should end + * with '/'. + */ + public String getExtraClasspath() + { + return _extraClasspath; + } + + /* ------------------------------------------------------------ */ + /** + * @param extraClasspath Comma or semicolon separated path of filenames or URLs + * pointing to directories or jar files. Directories should end + * with '/'. + */ + public void setExtraClasspath(String extraClasspath) + { + _extraClasspath=extraClasspath; + } + + /* ------------------------------------------------------------ */ + public boolean isLogUrlOnStart() + { + return _logUrlOnStart; + } + + /* ------------------------------------------------------------ */ + /** + * Sets whether or not the web app name and URL is logged on startup + * + * @param logOnStart whether or not the log message is created + */ + public void setLogUrlOnStart(boolean logOnStart) + { + this._logUrlOnStart = logOnStart; + } + + /* ------------------------------------------------------------ */ + protected void startContext() + throws Exception + { + // Configure defaults + for (int i=0;i<_configurations.length;i++) + _configurations[i].configureDefaults(); + + // Is there a WEB-INF work directory + Resource web_inf=getWebInf(); + if (web_inf!=null) + { + Resource work= web_inf.addPath("work"); + if (work.exists() + && work.isDirectory() + && work.getFile() != null + && work.getFile().canWrite() + && getAttribute(ServletContext.TEMPDIR) == null) + setAttribute(ServletContext.TEMPDIR, work.getFile()); + } + + // Configure webapp + for (int i=0;i<_configurations.length;i++) + _configurations[i].configureWebApp(); + + + super.startContext(); + } + + /** + * Create a canonical name for a webapp tmp directory. + * The form of the name is: + * "Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36 hashcode of whole string + * + * host and port uniquely identify the server + * context and virtual host uniquely identify the webapp + * @return + */ + private String getCanonicalNameForWebAppTmpDir () + { + StringBuffer canonicalName = new StringBuffer(); + canonicalName.append("Jetty"); + + //get the host and the port from the first connector + Connector[] connectors = getServer().getConnectors(); + + + //Get the host + canonicalName.append("_"); + String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost()); + if (host == null) + host = "0.0.0.0"; + canonicalName.append(host.replace('.', '_')); + + //Get the port + canonicalName.append("_"); + //try getting the real port being listened on + int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort()); + //if not available (eg no connectors or connector not started), + //try getting one that was configured. + if (port < 0) + port = connectors[0].getPort(); + canonicalName.append(port); + + + //Resource base + canonicalName.append("_"); + try + { + Resource resource = super.getBaseResource(); + if (resource == null) + { + if (_war==null || _war.length()==0) + resource=newResource(getResourceBase()); + + // Set dir or WAR + resource= newResource(_war); + } + + String tmp = URIUtil.decodePath(resource.getURL().getPath()); + if (tmp.endsWith("/")) + tmp = tmp.substring(0, tmp.length()-1); + if (tmp.endsWith("!")) + tmp = tmp.substring(0, tmp.length() -1); + //get just the last part which is the filename + int i = tmp.lastIndexOf("/"); + canonicalName.append(tmp.substring(i+1, tmp.length())); + } + catch (Exception e) + { + Log.warn("Can't generate resourceBase as part of webapp tmp dir name", e); + } + + //Context name + canonicalName.append("_"); + String contextPath = getContextPath(); + contextPath=contextPath.replace('/','_'); + contextPath=contextPath.replace('\\','_'); + canonicalName.append(contextPath); + + //Virtual host (if there is one) + canonicalName.append("_"); + String[] vhosts = getVirtualHosts(); + canonicalName.append((vhosts==null||vhosts[0]==null?"":vhosts[0])); + + //base36 hash of the whole string for uniqueness + String hash = Integer.toString(canonicalName.toString().hashCode(),36); + canonicalName.append("_"); + canonicalName.append(hash); + + // sanitize + for (int i=0;i _roles = new HashSet(); + protected Object _listeners; + protected Map _errorPages; + protected boolean _hasJSP; + protected String _jspServletName; + protected String _jspServletClass; + protected boolean _defaultWelcomeFileList; + protected SecurityHandler _securityHandler; + protected ServletHandler _servletHandler; + protected int _version; + protected boolean _metaDataComplete = false; + protected URL _webxml; + + + + public WebXmlConfiguration() throws ClassNotFoundException + { + // Get parser + _xmlParser = webXmlParser(); + } + + public static XmlParser webXmlParser() throws ClassNotFoundException + { + XmlParser xmlParser=new XmlParser(); + //set up cache of DTDs and schemas locally + URL dtd22=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_2.dtd",true); + URL dtd23=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_3.dtd",true); + URL j2ee14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_1_4.xsd",true); + URL webapp24xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_4.xsd",true); + URL webapp25xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_5.xsd",true); + URL webapp30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_0.xsd",true); + URL webcommon30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_0.xsd",true); + URL webfragment30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_0.xsd",true); + URL schemadtd=Loader.getResource(Servlet.class,"javax/servlet/resources/XMLSchema.dtd",true); + URL xmlxsd=Loader.getResource(Servlet.class,"javax/servlet/resources/xml.xsd",true); + URL webservice11xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_web_services_client_1_1.xsd",true); + URL webservice12xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_2.xsd",true); + URL datatypesdtd=Loader.getResource(Servlet.class,"javax/servlet/resources/datatypes.dtd",true); + + URL jsp20xsd = null; + URL jsp21xsd = null; + + try + { + Class jsp_page = Loader.loadClass(WebXmlConfiguration.class, "javax.servlet.jsp.JspPage"); + jsp20xsd = jsp_page.getResource("/javax/servlet/resources/jsp_2_0.xsd"); + jsp21xsd = jsp_page.getResource("/javax/servlet/resources/jsp_2_1.xsd"); + } + catch (Exception e) + { + Log.ignore(e); + } + finally + { + if (jsp20xsd == null) jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_0.xsd", true); + if (jsp21xsd == null) jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_1.xsd", true); + } + + redirect(xmlParser,"web-app_2_2.dtd",dtd22); + redirect(xmlParser,"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",dtd22); + redirect(xmlParser,"web.dtd",dtd23); + redirect(xmlParser,"web-app_2_3.dtd",dtd23); + redirect(xmlParser,"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",dtd23); + redirect(xmlParser,"XMLSchema.dtd",schemadtd); + redirect(xmlParser,"http://www.w3.org/2001/XMLSchema.dtd",schemadtd); + redirect(xmlParser,"-//W3C//DTD XMLSCHEMA 200102//EN",schemadtd); + redirect(xmlParser,"jsp_2_0.xsd",jsp20xsd); + redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/jsp_2_0.xsd",jsp20xsd); + redirect(xmlParser,"jsp_2_1.xsd",jsp21xsd); + redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/jsp_2_1.xsd",jsp21xsd); + redirect(xmlParser,"j2ee_1_4.xsd",j2ee14xsd); + redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd",j2ee14xsd); + redirect(xmlParser,"web-app_2_4.xsd",webapp24xsd); + redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd",webapp24xsd); + redirect(xmlParser,"web-app_2_5.xsd",webapp25xsd); + redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd",webapp25xsd); + redirect(xmlParser,"web-app_3_0.xsd",webapp30xsd); + redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd",webapp30xsd); + redirect(xmlParser,"web-common_3_0.xsd",webcommon30xsd); + redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-common_3_0.xsd",webcommon30xsd); + redirect(xmlParser,"web-fragment_3_0.xsd",webfragment30xsd); + redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd",webfragment30xsd); + redirect(xmlParser,"xml.xsd",xmlxsd); + redirect(xmlParser,"http://www.w3.org/2001/xml.xsd",xmlxsd); + redirect(xmlParser,"datatypes.dtd",datatypesdtd); + redirect(xmlParser,"http://www.w3.org/2001/datatypes.dtd",datatypesdtd); + redirect(xmlParser,"j2ee_web_services_client_1_1.xsd",webservice11xsd); + redirect(xmlParser,"http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",webservice11xsd); + redirect(xmlParser,"javaee_web_services_client_1_2.xsd",webservice12xsd); + redirect(xmlParser,"http://www.ibm.com/webservices/xsd/javaee_web_services_client_1_2.xsd",webservice12xsd); + + return xmlParser; + } + + /* ------------------------------------------------------------------------------- */ + private static void redirect(XmlParser parser, String resource, URL source) + { + if (source != null) parser.redirectEntity(resource, source); + } + + /* ------------------------------------------------------------------------------- */ + public void setWebAppContext(WebAppContext context) + { + _context = context; + } + + /* ------------------------------------------------------------------------------- */ + public WebAppContext getWebAppContext() + { + return _context; + } + + /* ------------------------------------------------------------------------------- */ + /** + * Configure ClassPath. + */ + public void configureClassLoader() throws Exception + { + } + + /* ------------------------------------------------------------------------------- */ + /** + * Process webdefaults.xml + * + * @see org.eclipse.jetty.server.server.webapp.Configuration#configureDefaults() + */ + public void configureDefaults() throws Exception + { + // cannot configure if the context is already started + if (_context.isStarted()) + { + if (Log.isDebugEnabled()) + { + Log.debug("Cannot configure webapp after it is started"); + } + return; + } + String defaultsDescriptor = getWebAppContext().getDefaultsDescriptor(); + if (defaultsDescriptor != null && defaultsDescriptor.length() > 0) + { + Resource dftResource = Resource.newSystemResource(defaultsDescriptor); + if (dftResource == null) dftResource = _context.newResource(defaultsDescriptor); + + // don't initialize the version and therefore the metadata from + // webdefault.xml + XmlParser.Node config = null; + config = _xmlParser.parse(dftResource.getURL().toString()); + initialize(config); + _defaultWelcomeFileList = _welcomeFiles != null; + } + } + + /* ------------------------------------------------------------------------------- */ + /** + * Process web.xml + * + * @see org.eclipse.jetty.server.server.webapp.Configuration#configureWebApp() + */ + public void configureWebApp() throws Exception + { + // cannot configure if the context is already started + if (_context.isStarted()) + { + if (Log.isDebugEnabled()) Log.debug("Cannot configure webapp after it is started"); + return; + } + + _webxml = findWebXml(); + if (_webxml != null) configure(_webxml.toString()); + + String overrideDescriptor = getWebAppContext().getOverrideDescriptor(); + if (overrideDescriptor != null && overrideDescriptor.length() > 0) + { + Resource orideResource = Resource.newSystemResource(overrideDescriptor); + if (orideResource == null) orideResource = _context.newResource(overrideDescriptor); + _xmlParser.setValidating(false); + configure(orideResource.getURL().toString()); + } + + // TODO is this before or after the overrides? + configureWebFragments(); + } + + /* ------------------------------------------------------------------------------- */ + /** + * Look for any web.xml fragments in META-INF of jars in WEB-INF/lib + * + * @throws Exception + */ + public void configureWebFragments() throws Exception + { + Log.debug("metadata-complete " + _metaDataComplete); + + // if metadata-complete is true in web.xml, do not search for fragments + if (_metaDataComplete) return; + + // either there is no web.xml, or it set metadata-complete to false, so + // we need to look for fragments in WEB-INF/lib + // Check to see if a specific search pattern has been set. + String tmp = (String) _context.getInitParameter("org.eclipse.jetty.webapp.WebXmlFragmentPattern"); + Pattern webFragPattern = (tmp == null ? null : Pattern.compile(tmp)); + + JarScanner fragScanner = new JarScanner() + { + public void processEntry(URL jarUrl, JarEntry entry) + { + try + { + String name = entry.getName(); + if (name.toLowerCase().equals("meta-inf/web.xml")) + { + Resource webXmlFrag = _context.newResource("jar:" + jarUrl + "!/" + name); + Log.debug("web.xml fragment found {}", webXmlFrag); + // Process web.xml + // web-fragment + // servlet + // servlet-mapping + // filter + // filter-mapping + // listener + XmlParser.Node config = null; + config = _xmlParser.parse(webXmlFrag.toString()); + initialize(config); + } + } + catch (Exception e) + { + Log.warn("Problem processing jar entry " + entry, e); + } + } + }; + fragScanner.scan(webFragPattern, Thread.currentThread().getContextClassLoader(), true, false); + } + + /* ------------------------------------------------------------------------------- */ + protected URL findWebXml() throws IOException, MalformedURLException + { + String descriptor = getWebAppContext().getDescriptor(); + if (descriptor != null) + { + Resource web = _context.newResource(descriptor); + if (web.exists() && !web.isDirectory()) return web.getURL(); + } + + Resource web_inf = getWebAppContext().getWebInf(); + if (web_inf != null && web_inf.isDirectory()) + { + // do web.xml file + Resource web = web_inf.addPath("web.xml"); + if (web.exists()) return web.getURL(); + Log.debug("No WEB-INF/web.xml in " + getWebAppContext().getWar() + ". Serving files and default/dynamic servlets only"); + } + return null; + } + + /* ------------------------------------------------------------------------------- */ + public void configure(String webXml) throws Exception + { + XmlParser.Node config = null; + config = _xmlParser.parse(webXml); + initializeVersion(config); + initialize(config); + } + + /* ------------------------------------------------------------------------------- */ + public void deconfigureWebApp() throws Exception + { + // TODO preserve any configuration that pre-existed. + + _servletHandler = getWebAppContext().getServletHandler(); + _securityHandler = (SecurityHandler)getWebAppContext().getSecurityHandler(); + + _servletHandler.setFilters(null); + _servletHandler.setFilterMappings(null); + _servletHandler.setServlets(null); + _servletHandler.setServletMappings(null); + + getWebAppContext().setEventListeners(null); + getWebAppContext().setWelcomeFiles(null); + if (_securityHandler instanceof ConstraintAware) ((ConstraintAware) _securityHandler).setConstraintMappings(null, _roles); + + if (getWebAppContext().getErrorHandler() instanceof ErrorPageErrorHandler) + ((ErrorPageErrorHandler) getWebAppContext().getErrorHandler()).setErrorPages(null); + + + // TODO remove classpaths from classloader + } + + /* ------------------------------------------------------------ */ + protected void initializeVersion(XmlParser.Node config) + { + String version = config.getAttribute("version", "DTD"); + if ("2.5".equals(version)) + _version = 25; + else if ("2.4".equals(version)) + _version = 24; + else if ("3.0".equals(version)) + _version = 30; + else if ("DTD".equals(version)) + { + _version = 23; + String dtd = _xmlParser.getDTD(); + if (dtd != null && dtd.indexOf("web-app_2_2") >= 0) _version = 22; + } + + if (_version < 25) + _metaDataComplete = true; // does not apply before 2.5 + else + _metaDataComplete = Boolean.valueOf((String) config.getAttribute("metadata-complete", "false")).booleanValue(); + + Log.debug("Calculated metadatacomplete = " + _metaDataComplete + " with version=" + version); + + _context.setAttribute("metadata-complete", String.valueOf(_metaDataComplete)); + } + + /* ------------------------------------------------------------ */ + protected void initialize(XmlParser.Node config) throws ClassNotFoundException, UnavailableException + { + _servletHandler = getWebAppContext().getServletHandler(); + _securityHandler = (SecurityHandler)getWebAppContext().getSecurityHandler(); + + // Get any existing servlets and mappings. + _filters = LazyList.array2List(_servletHandler.getFilters()); + _filterMappings = LazyList.array2List(_servletHandler.getFilterMappings()); + _servlets = LazyList.array2List(_servletHandler.getServlets()); + _servletMappings = LazyList.array2List(_servletHandler.getServletMappings()); + + _listeners = LazyList.array2List(getWebAppContext().getEventListeners()); + _welcomeFiles = LazyList.array2List(getWebAppContext().getWelcomeFiles()); + + if (_securityHandler instanceof ConstraintAware) + { + _constraintMappings = LazyList.array2List(((ConstraintAware) _securityHandler).getConstraintMappings()); + + if (((ConstraintAware) _securityHandler).getRoles() != null) + { + _roles.addAll(((ConstraintAware) _securityHandler).getRoles()); + } + } + + _errorPages = getWebAppContext().getErrorHandler() instanceof ErrorPageErrorHandler ? ((ErrorPageErrorHandler) getWebAppContext().getErrorHandler()) + .getErrorPages() : null; + + Iterator iter = config.iterator(); + XmlParser.Node node = null; + while (iter.hasNext()) + { + try + { + Object o = iter.next(); + if (!(o instanceof XmlParser.Node)) continue; + node = (XmlParser.Node) o; + String name = node.getTag(); + initWebXmlElement(name, node); + } + catch (ClassNotFoundException e) + { + throw e; + } + catch (Exception e) + { + Log.warn("Configuration problem at " + node, e); + throw new UnavailableException("Configuration problem"); + } + } + + _servletHandler.setFilters((FilterHolder[]) LazyList.toArray(_filters, FilterHolder.class)); + _servletHandler.setFilterMappings((FilterMapping[]) LazyList.toArray(_filterMappings, FilterMapping.class)); + _servletHandler.setServlets((ServletHolder[]) LazyList.toArray(_servlets, ServletHolder.class)); + _servletHandler.setServletMappings((ServletMapping[]) LazyList.toArray(_servletMappings, ServletMapping.class)); + + getWebAppContext().setEventListeners((EventListener[]) LazyList.toArray(_listeners, EventListener.class)); + getWebAppContext().setWelcomeFiles((String[]) LazyList.toArray(_welcomeFiles, String.class)); + // TODO jaspi check this + if (_securityHandler instanceof ConstraintAware) + { + ((ConstraintAware) _securityHandler).setConstraintMappings((ConstraintMapping[]) LazyList.toArray(_constraintMappings, + ConstraintMapping.class), + _roles); + } + + if (_errorPages != null && getWebAppContext().getErrorHandler() instanceof ErrorPageErrorHandler) + ((ErrorPageErrorHandler) getWebAppContext().getErrorHandler()).setErrorPages(_errorPages); + + } + + /* ------------------------------------------------------------ */ + /** + * Handle web.xml element. This method is called for each top level element + * within the web.xml file. It may be specialized by derived WebAppHandlers + * to provide additional configuration and handling. + * + * @param element The element name + * @param node The node containing the element. + */ + protected void initWebXmlElement(String element, XmlParser.Node node) throws Exception + { + if ("display-name".equals(element)) + initDisplayName(node); + else if ("description".equals(element)) + { + } + else if ("context-param".equals(element)) + initContextParam(node); + else if ("servlet".equals(element)) + initServlet(node); + else if ("servlet-mapping".equals(element)) + initServletMapping(node); + else if ("session-config".equals(element)) + initSessionConfig(node); + else if ("mime-mapping".equals(element)) + initMimeConfig(node); + else if ("welcome-file-list".equals(element)) + initWelcomeFileList(node); + else if ("locale-encoding-mapping-list".equals(element)) + initLocaleEncodingList(node); + else if ("error-page".equals(element)) + initErrorPage(node); + else if ("taglib".equals(element)) + initTagLib(node); + else if ("jsp-config".equals(element)) + initJspConfig(node); + else if ("resource-ref".equals(element)) + { + if (Log.isDebugEnabled()) Log.debug("No implementation: " + node); + } + else if ("security-constraint".equals(element)) + initSecurityConstraint(node); + else if ("login-config".equals(element)) + initLoginConfig(node); + else if ("security-role".equals(element)) + initSecurityRole(node); + else if ("filter".equals(element)) + initFilter(node); + else if ("filter-mapping".equals(element)) + initFilterMapping(node); + else if ("listener".equals(element)) + initListener(node); + else if ("distributable".equals(element)) + initDistributable(node); + else if ("web-fragment".equals(element)) + { + } + else + { + if (Log.isDebugEnabled()) + { + Log.debug("Element {} not handled in {}", element, this); + Log.debug(node.toString()); + } + } + } + + /* ------------------------------------------------------------ */ + protected void initDisplayName(XmlParser.Node node) + { + getWebAppContext().setDisplayName(node.toString(false, true)); + } + + /* ------------------------------------------------------------ */ + protected void initContextParam(XmlParser.Node node) + { + String name = node.getString("param-name", false, true); + String value = node.getString("param-value", false, true); + if (Log.isDebugEnabled()) Log.debug("ContextParam: " + name + "=" + value); + getWebAppContext().getInitParams().put(name, value); + } + + /* ------------------------------------------------------------ */ + protected void initFilter(XmlParser.Node node) + { + String name = node.getString("filter-name", false, true); + FilterHolder holder = _servletHandler.getFilter(name); + if (holder == null) + { + holder = _servletHandler.newFilterHolder(); + holder.setName(name); + _filters = LazyList.add(_filters, holder); + } + + String filter_class = node.getString("filter-class", false, true); + if (filter_class != null) holder.setClassName(filter_class); + + Iterator iter = node.iterator("init-param"); + while (iter.hasNext()) + { + XmlParser.Node paramNode = (XmlParser.Node) iter.next(); + String pname = paramNode.getString("param-name", false, true); + String pvalue = paramNode.getString("param-value", false, true); + holder.setInitParameter(pname, pvalue); + } + + String async=node.getString("async-support",false,true); + if (async!=null) + holder.setAsyncSupported(Boolean.valueOf(async)); + } + + /* ------------------------------------------------------------ */ + protected void initFilterMapping(XmlParser.Node node) + { + String filter_name = node.getString("filter-name", false, true); + + FilterMapping mapping = new FilterMapping(); + + mapping.setFilterName(filter_name); + + ArrayList paths = new ArrayList(); + Iterator iter = node.iterator("url-pattern"); + while (iter.hasNext()) + { + String p = ((XmlParser.Node) iter.next()).toString(false, true); + p = normalizePattern(p); + paths.add(p); + } + mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()])); + + ArrayList names = new ArrayList(); + iter = node.iterator("servlet-name"); + while (iter.hasNext()) + { + String n = ((XmlParser.Node) iter.next()).toString(false, true); + names.add(n); + } + mapping.setServletNames((String[]) names.toArray(new String[names.size()])); + + int dispatcher=FilterMapping.DEFAULT; + iter=node.iterator("dispatcher"); + while(iter.hasNext()) + { + String d=((XmlParser.Node)iter.next()).toString(false,true); + dispatcher|=FilterMapping.dispatch(d); + } + mapping.setDispatches(dispatcher); + + _filterMappings = LazyList.add(_filterMappings, mapping); + } + + /* ------------------------------------------------------------ */ + protected String normalizePattern(String p) + { + if (p != null && p.length() > 0 && !p.startsWith("/") && !p.startsWith("*")) return "/" + p; + return p; + } + + /* ------------------------------------------------------------ */ + protected void initServlet(XmlParser.Node node) + { + String id = node.getAttribute("id"); + + // initialize holder + String servlet_name = node.getString("servlet-name", false, true); + ServletHolder holder = _servletHandler.getServlet(servlet_name); + if (holder == null) + { + holder = _servletHandler.newServletHolder(); + holder.setName(servlet_name); + _servlets = LazyList.add(_servlets, holder); + } + + // init params + Iterator iParamsIter = node.iterator("init-param"); + while (iParamsIter.hasNext()) + { + XmlParser.Node paramNode = (XmlParser.Node) iParamsIter.next(); + String pname = paramNode.getString("param-name", false, true); + String pvalue = paramNode.getString("param-value", false, true); + holder.setInitParameter(pname, pvalue); + } + + String servlet_class = node.getString("servlet-class", false, true); + + // Handle JSP + if (id != null && id.equals("jsp")) + { + _jspServletName = servlet_name; + _jspServletClass = servlet_class; + try + { + Loader.loadClass(this.getClass(), servlet_class); + _hasJSP = true; + } + catch (ClassNotFoundException e) + { + Log.info("NO JSP Support for {}, did not find {}", _context.getContextPath(), servlet_class); + _hasJSP = false; + _jspServletClass = servlet_class = "org.eclipse.jetty.servlet.NoJspServlet"; + } + if (holder.getInitParameter("scratchdir") == null) + { + File tmp = getWebAppContext().getTempDirectory(); + File scratch = new File(tmp, "jsp"); + if (!scratch.exists()) scratch.mkdir(); + holder.setInitParameter("scratchdir", scratch.getAbsolutePath()); + + if ("?".equals(holder.getInitParameter("classpath"))) + { + String classpath = getWebAppContext().getClassPath(); + Log.debug("classpath=" + classpath); + if (classpath != null) holder.setInitParameter("classpath", classpath); + } + } + } + if (servlet_class != null) holder.setClassName(servlet_class); + + // Handler JSP file + String jsp_file = node.getString("jsp-file", false, true); + if (jsp_file != null) + { + holder.setForcedPath(jsp_file); + holder.setClassName(_jspServletClass); + } + + // handle startup + XmlParser.Node startup = node.get("load-on-startup"); + if (startup != null) + { + String s = startup.toString(false, true).toLowerCase(); + if (s.startsWith("t")) + { + Log.warn("Deprecated boolean load-on-startup. Please use integer"); + holder.setInitOrder(1); + } + else + { + int order = 0; + try + { + if (s != null && s.trim().length() > 0) order = Integer.parseInt(s); + } + catch (Exception e) + { + Log.warn("Cannot parse load-on-startup " + s + ". Please use integer"); + Log.ignore(e); + } + holder.setInitOrder(order); + } + } + + Iterator sRefsIter = node.iterator("security-role-ref"); + while (sRefsIter.hasNext()) + { + XmlParser.Node securityRef = (XmlParser.Node) sRefsIter.next(); + String roleName = securityRef.getString("role-name", false, true); + String roleLink = securityRef.getString("role-link", false, true); + if (roleName != null && roleName.length() > 0 && roleLink != null && roleLink.length() > 0) + { + if (Log.isDebugEnabled()) Log.debug("link role " + roleName + " to " + roleLink + " for " + this); + holder.setUserRoleLink(roleName, roleLink); + } + else + { + Log.warn("Ignored invalid security-role-ref element: " + "servlet-name=" + holder.getName() + ", " + securityRef); + } + } + + XmlParser.Node run_as = node.get("run-as"); + if (run_as != null) + { + String roleName = run_as.getString("role-name", false, true); + if (roleName != null) + holder.setRunAsRole(roleName); + } + + String async=node.getString("async-support",false,true); + if (async!=null) + holder.setAsyncSupported(Boolean.valueOf(async)); + } + + /* ------------------------------------------------------------ */ + protected void initServletMapping(XmlParser.Node node) + { + String servlet_name = node.getString("servlet-name", false, true); + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(servlet_name); + + ArrayList paths = new ArrayList(); + Iterator iter = node.iterator("url-pattern"); + while (iter.hasNext()) + { + String p = ((XmlParser.Node) iter.next()).toString(false, true); + p = normalizePattern(p); + paths.add(p); + } + mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()])); + + _servletMappings = LazyList.add(_servletMappings, mapping); + } + + /* ------------------------------------------------------------ */ + protected void initListener(XmlParser.Node node) + { + String className = node.getString("listener-class", false, true); + Object listener = null; + try + { + Class listenerClass = getWebAppContext().loadClass(className); + listener = newListenerInstance(listenerClass); + if (!(listener instanceof EventListener)) + { + Log.warn("Not an EventListener: " + listener); + return; + } + _listeners = LazyList.add(_listeners, listener); + } + catch (Exception e) + { + Log.warn("Could not instantiate listener " + className, e); + return; + } + } + + /* ------------------------------------------------------------ */ + protected Object newListenerInstance(Class clazz) throws InstantiationException, IllegalAccessException + { + return clazz.newInstance(); + } + + /* ------------------------------------------------------------ */ + protected void initDistributable(XmlParser.Node node) + { + // the element has no content, so its simple presence + // indicates that the webapp is distributable... + WebAppContext wac = getWebAppContext(); + if (!wac.isDistributable()) wac.setDistributable(true); + } + + /* ------------------------------------------------------------ */ + protected void initSessionConfig(XmlParser.Node node) + { + XmlParser.Node tNode = node.get("session-timeout"); + if (tNode != null) + { + int timeout = Integer.parseInt(tNode.toString(false, true)); + getWebAppContext().getSessionHandler().getSessionManager().setMaxInactiveInterval(timeout * 60); + } + } + + /* ------------------------------------------------------------ */ + protected void initMimeConfig(XmlParser.Node node) + { + String extension = node.getString("extension", false, true); + if (extension != null && extension.startsWith(".")) extension = extension.substring(1); + String mimeType = node.getString("mime-type", false, true); + getWebAppContext().getMimeTypes().addMimeMapping(extension, mimeType); + } + + /* ------------------------------------------------------------ */ + protected void initWelcomeFileList(XmlParser.Node node) + { + if (_defaultWelcomeFileList) _welcomeFiles = null; // erase welcome + // files from + // default web.xml + + _defaultWelcomeFileList = false; + Iterator iter = node.iterator("welcome-file"); + while (iter.hasNext()) + { + XmlParser.Node indexNode = (XmlParser.Node) iter.next(); + String welcome = indexNode.toString(false, true); + _welcomeFiles = LazyList.add(_welcomeFiles, welcome); + } + } + + /* ------------------------------------------------------------ */ + protected void initLocaleEncodingList(XmlParser.Node node) + { + Iterator iter = node.iterator("locale-encoding-mapping"); + while (iter.hasNext()) + { + XmlParser.Node mapping = (XmlParser.Node) iter.next(); + String locale = mapping.getString("locale", false, true); + String encoding = mapping.getString("encoding", false, true); + getWebAppContext().addLocaleEncoding(locale, encoding); + } + } + + /* ------------------------------------------------------------ */ + protected void initErrorPage(XmlParser.Node node) + { + String error = node.getString("error-code", false, true); + if (error == null || error.length() == 0) error = node.getString("exception-type", false, true); + String location = node.getString("location", false, true); + + if (_errorPages == null) _errorPages = new HashMap(); + _errorPages.put(error, location); + } + + /* ------------------------------------------------------------ */ + protected void initTagLib(XmlParser.Node node) + { + String uri = node.getString("taglib-uri", false, true); + String location = node.getString("taglib-location", false, true); + + getWebAppContext().setResourceAlias(uri, location); + } + + /* ------------------------------------------------------------ */ + protected void initJspConfig(XmlParser.Node node) + { + for (int i = 0; i < node.size(); i++) + { + Object o = node.get(i); + if (o instanceof XmlParser.Node && "taglib".equals(((XmlParser.Node) o).getTag())) initTagLib((XmlParser.Node) o); + } + + // Map URLs from jsp property groups to JSP servlet. + // this is more JSP stupidness creaping into the servlet spec + Iterator iter = node.iterator("jsp-property-group"); + Object paths = null; + while (iter.hasNext()) + { + XmlParser.Node group = (XmlParser.Node) iter.next(); + Iterator iter2 = group.iterator("url-pattern"); + while (iter2.hasNext()) + { + String url = ((XmlParser.Node) iter2.next()).toString(false, true); + url = normalizePattern(url); + paths = LazyList.add(paths, url); + } + } + + if (LazyList.size(paths) > 0) + { + String jspName = getJSPServletName(); + if (jspName != null) + { + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(jspName); + mapping.setPathSpecs(LazyList.toStringArray(paths)); + _servletMappings = LazyList.add(_servletMappings, mapping); + } + } + } + + /* ------------------------------------------------------------ */ + protected void initSecurityConstraint(XmlParser.Node node) + { + Constraint scBase = new Constraint(); + + try + { + XmlParser.Node auths = node.get("auth-constraint"); + + if (auths != null) + { + scBase.setAuthenticate(true); + // auth-constraint + Iterator iter = auths.iterator("role-name"); + Object roles = null; + while (iter.hasNext()) + { + String role = ((XmlParser.Node) iter.next()).toString(false, true); + roles = LazyList.add(roles, role); + } + scBase.setRoles(LazyList.toStringArray(roles)); + } + + XmlParser.Node data = node.get("user-data-constraint"); + if (data != null) + { + data = data.get("transport-guarantee"); + String guarantee = data.toString(false, true).toUpperCase(); + if (guarantee == null || guarantee.length() == 0 || "NONE".equals(guarantee)) + scBase.setDataConstraint(Constraint.DC_NONE); + else if ("INTEGRAL".equals(guarantee)) + scBase.setDataConstraint(Constraint.DC_INTEGRAL); + else if ("CONFIDENTIAL".equals(guarantee)) + scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL); + else + { + Log.warn("Unknown user-data-constraint:" + guarantee); + scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL); + } + } + Iterator iter = node.iterator("web-resource-collection"); + while (iter.hasNext()) + { + XmlParser.Node collection = (XmlParser.Node) iter.next(); + String name = collection.getString("web-resource-name", false, true); + Constraint sc = (Constraint) scBase.clone(); + sc.setName(name); + + Iterator iter2 = collection.iterator("url-pattern"); + while (iter2.hasNext()) + { + String url = ((XmlParser.Node) iter2.next()).toString(false, true); + url = normalizePattern(url); + + Iterator iter3 = collection.iterator("http-method"); + if (iter3.hasNext()) + { + while (iter3.hasNext()) + { + String method = ((XmlParser.Node) iter3.next()).toString(false, true); + ConstraintMapping mapping = new ConstraintMapping(); + mapping.setMethod(method); + mapping.setPathSpec(url); + mapping.setConstraint(sc); + _constraintMappings = LazyList.add(_constraintMappings, mapping); + } + } + else + { + ConstraintMapping mapping = new ConstraintMapping(); + mapping.setPathSpec(url); + mapping.setConstraint(sc); + _constraintMappings = LazyList.add(_constraintMappings, mapping); + } + } + } + } + catch (CloneNotSupportedException e) + { + Log.warn(e); + } + + } + + /* ------------------------------------------------------------ */ + protected void initLoginConfig(XmlParser.Node node) throws Exception + { + XmlParser.Node method = node.get("auth-method"); + if (method != null) + { + XmlParser.Node name = node.get("realm-name"); + _securityHandler.setRealmName(name == null ? "default" : name.toString(false, true)); + _securityHandler.setAuthMethod(method.toString(false, true)); + + + if (Constraint.__FORM_AUTH.equals(_securityHandler.getAuthMethod())) + { + XmlParser.Node formConfig = node.get("form-login-config"); + if (formConfig != null) + { + String loginPageName = null; + XmlParser.Node loginPage = formConfig.get("form-login-page"); + if (loginPage != null) + loginPageName = loginPage.toString(false, true); + String errorPageName = null; + XmlParser.Node errorPage = formConfig.get("form-error-page"); + if (errorPage != null) + errorPageName = errorPage.toString(false, true); + _securityHandler.setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE,loginPageName); + _securityHandler.setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE,errorPageName); + } + else + { + throw new IllegalArgumentException("!form-login-config"); + } + } + } + } + + /* ------------------------------------------------------------ */ + protected void initSecurityRole(XmlParser.Node node) + { + XmlParser.Node roleNode = node.get("role-name"); + String role = roleNode.toString(false, true); + _roles.add(role); + } + + /* ------------------------------------------------------------ */ + protected String getJSPServletName() + { + if (_jspServletName == null) + { + Map.Entry entry = _context.getServletHandler().getHolderEntry("test.jsp"); + if (entry != null) + { + ServletHolder holder = (ServletHolder) entry.getValue(); + _jspServletName = holder.getName(); + } + } + return _jspServletName; + } +} diff --git a/jetty-xml/pom.xml b/jetty-xml/pom.xml new file mode 100644 index 00000000000..bd53d7762ee --- /dev/null +++ b/jetty-xml/pom.xml @@ -0,0 +1,90 @@ + + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + + 4.0.0 + jetty-xml + Jetty :: XML utilities + The jetty xml utilities. + + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + manifest + + + + org.eclipse.jetty.xml + J2SE-1.5 + org.eclipse.jetty.xml;version=${project.version} + http://jetty.eclipse.org + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + site-component.xml + + + + + + + + + + org.eclipse.jetty + jetty-util + ${project.version} + + + junit + junit + test + + + diff --git a/jetty-xml/src/main/assembly/site-component.xml b/jetty-xml/src/main/assembly/site-component.xml new file mode 100644 index 00000000000..7c48a5beccd --- /dev/null +++ b/jetty-xml/src/main/assembly/site-component.xml @@ -0,0 +1,15 @@ + + site-component + + jar + + + + ${basedir} + jetty-xml + + src/main/resources/org/eclipse/** + + + + diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java new file mode 100644 index 00000000000..e38aac0b909 --- /dev/null +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java @@ -0,0 +1,993 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/* ------------------------------------------------------------ */ +/** + * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and + * uses it to configure and object by calling set, put or other methods on the object. + * + * + */ +public class XmlConfiguration +{ + + private static Class[] __primitives = { Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, + Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE}; + + private static Class[] __primitiveHolders = { Boolean.class, Character.class, Byte.class, + Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class}; + private static final Integer ZERO=new Integer(0); + + /* ------------------------------------------------------------ */ + private static XmlParser __parser; + private XmlParser.Node _config; + private Map _idMap = new HashMap(); + private Map _propertyMap = new HashMap(); + + /* ------------------------------------------------------------ */ + private synchronized static void initParser() throws IOException + { + if (__parser != null) return; + + __parser = new XmlParser(); + try + { + URL configURL = Loader.getResource(XmlConfiguration.class, "org/eclipse/jetty/xml/configure_6_0.dtd", true); + __parser.redirectEntity("configure.dtd", configURL); + __parser.redirectEntity("configure_1_3.dtd", configURL); + __parser.redirectEntity("http://jetty.eclipse.org/configure.dtd", configURL); + __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN", configURL); + __parser.redirectEntity("http://jetty.eclipse.org/configure_1_3.dtd", configURL); + __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.3//EN", configURL); + __parser.redirectEntity("configure_1_2.dtd", configURL); + __parser.redirectEntity("http://jetty.eclipse.org/configure_1_2.dtd", configURL); + __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.2//EN", configURL); + __parser.redirectEntity("configure_1_1.dtd", configURL); + __parser.redirectEntity("http://jetty.eclipse.org/configure_1_1.dtd", configURL); + __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.1//EN", configURL); + __parser.redirectEntity("configure_1_0.dtd", configURL); + __parser.redirectEntity("http://jetty.eclipse.org/configure_1_0.dtd", configURL); + __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.0//EN", configURL); + } + catch (ClassNotFoundException e) + { + Log.warn(e.toString()); + Log.debug(e); + } + } + + /* ------------------------------------------------------------ */ + /** + * Constructor. Reads the XML configuration file. + * + * @param configuration + */ + public XmlConfiguration(URL configuration) throws SAXException, IOException + { + initParser(); + synchronized (__parser) + { + _config = __parser.parse(configuration.toString()); + } + } + + /* ------------------------------------------------------------ */ + /** + * Constructor. + * + * @param configuration String of XML configuration commands excluding the normal XML preamble. + * The String should start with a " \n" + + configuration; + InputSource source = new InputSource(new StringReader(configuration)); + synchronized (__parser) + { + _config = __parser.parse(source); + } + } + + /* ------------------------------------------------------------ */ + /** + * Constructor. + * + * @param configuration An input stream containing a complete e.g. configuration file + * @exception SAXException + * @exception IOException + */ + public XmlConfiguration(InputStream configuration) throws SAXException, IOException + { + initParser(); + InputSource source = new InputSource(configuration); + synchronized (__parser) + { + _config = __parser.parse(source); + } + } + + /* ------------------------------------------------------------ */ + public Map getIdMap() + { + return _idMap; + } + + /* ------------------------------------------------------------ */ + public void setIdMap(Map map) + { + _idMap=map; + } + + /* ------------------------------------------------------------ */ + public void setProperties (Map map) + { + _propertyMap = map; + } + + /* ------------------------------------------------------------ */ + public Map getProperties () + { + return _propertyMap; + } + + /* ------------------------------------------------------------ */ + /** + * Configure an object. If the object is of the approprate class, the XML configuration script + * is applied to the object. + * + * @param obj The object to be configured. + * @exception Exception + */ + public void configure(Object obj) throws Exception + { + //Check the class of the object + Class oClass = nodeClass(_config); + if (!oClass.isInstance(obj)) + throw new IllegalArgumentException("Object is not of type " + oClass); + configure(obj, _config, 0); + } + + /* ------------------------------------------------------------ */ + /** + * Configure an object. If the configuration has an ID, an object is looked up + * by ID and it's type check. Otherwise a new object is created. + * + * @return The newly created configured object. + * @exception Exception + */ + public Object configure() throws Exception + { + Class oClass = nodeClass(_config); + + String id = _config.getAttribute("id"); + Object obj = id==null?null:_idMap.get(id); + + if (obj==null && oClass !=null) + obj = oClass.newInstance(); + + if (oClass!=null && !oClass.isInstance(obj)) + throw new ClassCastException(oClass.toString()); + + configure(obj, _config, 0); + return obj; + } + + /* ------------------------------------------------------------ */ + private Class nodeClass(XmlParser.Node node) throws ClassNotFoundException + { + String className = node.getAttribute("class"); + if (className == null) return null; + + return Loader.loadClass(XmlConfiguration.class, className,true); + } + + /* ------------------------------------------------------------ */ + /* + * Recursive configuration step. This method applies the remaining Set, Put and Call elements to + * the current object. @param obj @param cfg @param i @exception Exception + */ + private void configure(Object obj, XmlParser.Node cfg, int i) throws Exception + { + String id = cfg.getAttribute("id"); + if (id!=null) + _idMap.put(id,obj); + + for (; i < cfg.size(); i++) + { + Object o = cfg.get(i); + if (o instanceof String) continue; + XmlParser.Node node = (XmlParser.Node) o; + + try + { + String tag = node.getTag(); + if ("Set".equals(tag)) + set(obj, node); + else if ("Put".equals(tag)) + put(obj, node); + else if ("Call".equals(tag)) + call(obj, node); + else if ("Get".equals(tag)) + get(obj, node); + else if ("New".equals(tag)) + newObj(obj, node); + else if ("Array".equals(tag)) + newArray(obj, node); + else if ("Ref".equals(tag)) + refObj(obj, node); + else if ("Property".equals(tag)) + propertyObj(obj, node); + else + throw new IllegalStateException("Unknown tag: " + tag); + } + catch (Exception e) + { + Log.warn("Config error at " + node, e.toString()); + throw e; + } + } + } + + /* ------------------------------------------------------------ */ + /* + * Call a set method. This method makes a best effort to find a matching set method. The type of + * the value is used to find a suitable set method by 1. Trying for a trivial type match. 2. + * Looking for a native type match. 3. Trying all correctly named methods for an auto + * conversion. 4. Attempting to construct a suitable value from original value. @param obj + * @param node + */ + private void set(Object obj, XmlParser.Node node) throws Exception + { + String attr = node.getAttribute("name"); + String name = "set" + attr.substring(0, 1).toUpperCase() + attr.substring(1); + Object value = value(obj, node); + Object[] arg = { value}; + + Class oClass = nodeClass(node); + if (oClass != null) + obj = null; + else + oClass = obj.getClass(); + + Class[] vClass = { Object.class}; + if (value != null) vClass[0] = value.getClass(); + + if (Log.isDebugEnabled()) + Log.debug("XML "+(obj!=null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")"); + + // Try for trivial match + try + { + Method set = oClass.getMethod(name, vClass); + set.invoke(obj, arg); + return; + } + catch (IllegalArgumentException e) + { + Log.ignore(e); + } + catch (IllegalAccessException e) + { + Log.ignore(e); + } + catch (NoSuchMethodException e) + { + Log.ignore(e); + } + + // Try for native match + try + { + Field type = vClass[0].getField("TYPE"); + vClass[0] = (Class) type.get(null); + Method set = oClass.getMethod(name, vClass); + set.invoke(obj, arg); + return; + } + catch (NoSuchFieldException e) + { + Log.ignore(e); + } + catch (IllegalArgumentException e) + { + Log.ignore(e); + } + catch (IllegalAccessException e) + { + Log.ignore(e); + } + catch (NoSuchMethodException e) + { + Log.ignore(e); + } + + // Try a field + try + { + Field field = oClass.getField(attr); + if (Modifier.isPublic(field.getModifiers())) + { + field.set(obj, value); + return; + } + } + catch (NoSuchFieldException e) + { + Log.ignore(e); + } + + // Search for a match by trying all the set methods + Method[] sets = oClass.getMethods(); + Method set = null; + for (int s = 0; sets != null && s < sets.length; s++) + { + if (name.equals(sets[s].getName()) && sets[s].getParameterTypes().length == 1) + { + // lets try it + try + { + set = sets[s]; + sets[s].invoke(obj, arg); + return; + } + catch (IllegalArgumentException e) + { + Log.ignore(e); + } + catch (IllegalAccessException e) + { + Log.ignore(e); + } + } + } + + // Try converting the arg to the last set found. + if (set != null) + { + try + { + Class sClass = set.getParameterTypes()[0]; + if (sClass.isPrimitive()) + { + for (int t = 0; t < __primitives.length; t++) + { + if (sClass.equals(__primitives[t])) + { + sClass = __primitiveHolders[t]; + break; + } + } + } + Constructor cons = sClass.getConstructor(vClass); + arg[0] = cons.newInstance(arg); + set.invoke(obj, arg); + return; + } + catch (NoSuchMethodException e) + { + Log.ignore(e); + } + catch (IllegalAccessException e) + { + Log.ignore(e); + } + catch (InstantiationException e) + { + Log.ignore(e); + } + } + + // No Joy + throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")"); + } + + /* ------------------------------------------------------------ */ + /* + * Call a put method. + * + * @param obj @param node + */ + private void put(Object obj, XmlParser.Node node) throws Exception + { + if (!(obj instanceof Map)) + throw new IllegalArgumentException("Object for put is not a Map: " + obj); + Map map = (Map) obj; + + String name = node.getAttribute("name"); + Object value = value(obj, node); + map.put(name, value); + if (Log.isDebugEnabled()) Log.debug("XML "+obj + ".put(" + name + "," + value + ")"); + } + + /* ------------------------------------------------------------ */ + /* + * Call a get method. Any object returned from the call is passed to the configure method to + * consume the remaining elements. @param obj @param node @return @exception Exception + */ + private Object get(Object obj, XmlParser.Node node) throws Exception + { + Class oClass = nodeClass(node); + if (oClass != null) + obj = null; + else + oClass = obj.getClass(); + + String name = node.getAttribute("name"); + String id = node.getAttribute("id"); + if (Log.isDebugEnabled()) Log.debug("XML get " + name); + + try + { + // try calling a getXxx method. + Method method = oClass.getMethod("get" + name.substring(0, 1).toUpperCase() + + name.substring(1), (java.lang.Class[]) null); + obj = method.invoke(obj, (java.lang.Object[]) null); + configure(obj, node, 0); + } + catch (NoSuchMethodException nsme) + { + try + { + Field field = oClass.getField(name); + obj = field.get(obj); + configure(obj, node, 0); + } + catch (NoSuchFieldException nsfe) + { + throw nsme; + } + } + if (id != null) _idMap.put(id, obj); + return obj; + } + + /* ------------------------------------------------------------ */ + /* + * Call a method. A method is selected by trying all methods with matching names and number of + * arguments. Any object returned from the call is passed to the configure method to consume the + * remaining elements. Note that if this is a static call we consider only methods declared + * directly in the given class. i.e. we ignore any static methods in superclasses. @param obj + * @param node @return @exception Exception + */ + private Object call(Object obj, XmlParser.Node node) throws Exception + { + String id = node.getAttribute("id"); + Class oClass = nodeClass(node); + if (oClass != null) + obj = null; + else if (obj != null) oClass = obj.getClass(); + if (oClass == null) throw new IllegalArgumentException(node.toString()); + + int size = 0; + int argi = node.size(); + for (int i = 0; i < node.size(); i++) + { + Object o = node.get(i); + if (o instanceof String) continue; + if (!((XmlParser.Node) o).getTag().equals("Arg")) + { + argi = i; + break; + } + size++; + } + + Object[] arg = new Object[size]; + for (int i = 0, j = 0; j < size; i++) + { + Object o = node.get(i); + if (o instanceof String) continue; + arg[j++] = value(obj, (XmlParser.Node) o); + } + + String method = node.getAttribute("name"); + if (Log.isDebugEnabled()) Log.debug("XML call " + method); + + // Lets just try all methods for now + Method[] methods = oClass.getMethods(); + for (int c = 0; methods != null && c < methods.length; c++) + { + if (!methods[c].getName().equals(method)) continue; + if (methods[c].getParameterTypes().length != size) continue; + if (Modifier.isStatic(methods[c].getModifiers()) != (obj == null)) continue; + if ((obj == null) && methods[c].getDeclaringClass() != oClass) continue; + + Object n = null; + boolean called = false; + try + { + n = methods[c].invoke(obj, arg); + called = true; + } + catch (IllegalAccessException e) + { + Log.ignore(e); + } + catch (IllegalArgumentException e) + { + Log.ignore(e); + } + if (called) + { + if (id != null) _idMap.put(id, n); + configure(n, node, argi); + return n; + } + } + + throw new IllegalStateException("No Method: " + node + " on " + oClass); + } + + /* ------------------------------------------------------------ */ + /* + * Create a new value object. + * + * @param obj @param node @return @exception Exception + */ + private Object newObj(Object obj, XmlParser.Node node) throws Exception + { + Class oClass = nodeClass(node); + String id = node.getAttribute("id"); + int size = 0; + int argi = node.size(); + for (int i = 0; i < node.size(); i++) + { + Object o = node.get(i); + if (o instanceof String) continue; + if (!((XmlParser.Node) o).getTag().equals("Arg")) + { + argi = i; + break; + } + size++; + } + + Object[] arg = new Object[size]; + for (int i = 0, j = 0; j < size; i++) + { + Object o = node.get(i); + if (o instanceof String) continue; + arg[j++] = value(obj, (XmlParser.Node) o); + } + + if (Log.isDebugEnabled()) Log.debug("XML new " + oClass); + + // Lets just try all constructors for now + Constructor[] constructors = oClass.getConstructors(); + for (int c = 0; constructors != null && c < constructors.length; c++) + { + if (constructors[c].getParameterTypes().length != size) continue; + + Object n = null; + boolean called = false; + try + { + n = constructors[c].newInstance(arg); + called = true; + } + catch (IllegalAccessException e) + { + Log.ignore(e); + } + catch (InstantiationException e) + { + Log.ignore(e); + } + catch (IllegalArgumentException e) + { + Log.ignore(e); + } + if (called) + { + if (id != null) _idMap.put(id, n); + configure(n, node, argi); + return n; + } + } + + throw new IllegalStateException("No Constructor: " + node + " on " + obj); + } + + /* ------------------------------------------------------------ */ + /* + * Reference an id value object. + * + * @param obj @param node @return @exception NoSuchMethodException @exception + * ClassNotFoundException @exception InvocationTargetException + */ + private Object refObj(Object obj, XmlParser.Node node) throws Exception + { + String id = node.getAttribute("id"); + obj = _idMap.get(id); + if (obj == null) throw new IllegalStateException("No object for id=" + id); + configure(obj, node, 0); + return obj; + } + + + /* ------------------------------------------------------------ */ + /* + * Create a new array object. + * + */ + private Object newArray(Object obj, XmlParser.Node node) throws Exception + { + + // Get the type + Class aClass = java.lang.Object.class; + String type = node.getAttribute("type"); + final String id = node.getAttribute("id"); + if (type != null) + { + aClass = TypeUtil.fromName(type); + if (aClass == null) + { + if ("String".equals(type)) + aClass = java.lang.String.class; + else if ("URL".equals(type)) + aClass = java.net.URL.class; + else if ("InetAddress".equals(type)) + aClass = java.net.InetAddress.class; + else + aClass = Loader.loadClass(XmlConfiguration.class, type,true); + } + } + + Object al=null; + + Iterator iter = node.iterator("Item"); + while(iter.hasNext()) + { + XmlParser.Node item= (XmlParser.Node)iter.next(); + String nid = item.getAttribute("id"); + Object v = value(obj, item); + al=LazyList.add(al,(v==null&&aClass.isPrimitive())?ZERO:v); + if (nid != null) + _idMap.put(nid, v); + } + + Object array = LazyList.toArray(al,aClass); + if (id != null) + _idMap.put(id, array); + return array; + } + + /* ------------------------------------------------------------ */ + /* + * Create a new map object. + * + */ + private Object newMap(Object obj, XmlParser.Node node) throws Exception + { + String id = node.getAttribute("id"); + + Map map = new HashMap(); + if (id != null) _idMap.put(id, map); + + for (int i = 0; i < node.size(); i++) + { + Object o = node.get(i); + if (o instanceof String) continue; + XmlParser.Node entry = (XmlParser.Node) o; + if (!entry.getTag().equals("Entry")) throw new IllegalStateException("Not an Entry"); + + + XmlParser.Node key=null; + XmlParser.Node value=null; + + for (int j = 0; j < entry.size(); j++) + { + o = entry.get(j); + if (o instanceof String) continue; + XmlParser.Node item = (XmlParser.Node) o; + if (!item.getTag().equals("Item")) throw new IllegalStateException("Not an Item"); + if (key==null) + key=item; + else + value=item; + } + + if (key==null || value==null) + throw new IllegalStateException("Missing Item in Entry"); + String kid = key.getAttribute("id"); + String vid = value.getAttribute("id"); + + Object k = value(obj, key); + Object v = value(obj, value); + map.put(k,v); + + if (kid != null) _idMap.put(kid, k); + if (vid != null) _idMap.put(vid, v); + } + + return map; + } + + /* ------------------------------------------------------------ */ + /* + * Create a new value object. + * + * @param obj @param node @return @exception Exception + */ + private Object propertyObj(Object obj, XmlParser.Node node) throws Exception + { + String id = node.getAttribute("id"); + String name = node.getAttribute("name"); + Object defval = node.getAttribute("default"); + Object prop=null; + if (_propertyMap!=null && _propertyMap.containsKey(name)) + { + prop=_propertyMap.get(name); + } + else if (defval != null) + prop=defval; + + if (id != null) + _idMap.put(id, prop); + if (prop!=null) + configure(prop, node, 0); + return prop; + } + + /* ------------------------------------------------------------ */ + /* + * Get the value of an element. If no value type is specified, then white space is trimmed out + * of the value. If it contains multiple value elements they are added as strings before being + * converted to any specified type. @param node + */ + private Object value(Object obj, XmlParser.Node node) throws Exception + { + Object value = null; + + // Get the type + String type = node.getAttribute("type"); + + // Try a ref lookup + String ref = node.getAttribute("ref"); + if (ref != null) + { + value = _idMap.get(ref); + } + else + { + // handle trivial case + if (node.size() == 0) + { + if ("String".equals(type)) return ""; + return null; + } + + // Trim values + int first = 0; + int last = node.size() - 1; + + // Handle default trim type + if (type == null || !"String".equals(type)) + { + // Skip leading white + Object item = null; + while (first <= last) + { + item = node.get(first); + if (!(item instanceof String)) break; + item = ((String) item).trim(); + if (((String) item).length() > 0) break; + first++; + } + + // Skip trailing white + while (first < last) + { + item = node.get(last); + if (!(item instanceof String)) break; + item = ((String) item).trim(); + if (((String) item).length() > 0) break; + last--; + } + + // All white, so return null + if (first > last) return null; + } + + if (first == last) + // Single Item value + value = itemValue(obj, node.get(first)); + else + { + // Get the multiple items as a single string + StringBuilder buf = new StringBuilder(); + for (int i = first; i <= last; i++) + { + Object item = node.get(i); + buf.append(itemValue(obj, item)); + } + value = buf.toString(); + } + } + + // Untyped or unknown + if (value == null) + { + if ("String".equals(type)) return ""; + return null; + } + + // Try to type the object + if (type == null) + { + if (value != null && value instanceof String) return ((String) value).trim(); + return value; + } + + if ("String".equals(type) || "java.lang.String".equals(type)) return value.toString(); + + Class pClass = TypeUtil.fromName(type); + if (pClass != null) return TypeUtil.valueOf(pClass, value.toString()); + + if ("URL".equals(type) || "java.net.URL".equals(type)) + { + if (value instanceof URL) return value; + try + { + return new URL(value.toString()); + } + catch (MalformedURLException e) + { + throw new InvocationTargetException(e); + } + } + + if ("InetAddress".equals(type) || "java.net.InetAddress".equals(type)) + { + if (value instanceof InetAddress) return value; + try + { + return InetAddress.getByName(value.toString()); + } + catch (UnknownHostException e) + { + throw new InvocationTargetException(e); + } + } + + throw new IllegalStateException("Unknown type " + type); + } + + /* ------------------------------------------------------------ */ + /* + * Get the value of a single element. @param obj @param item @return @exception Exception + */ + private Object itemValue(Object obj, Object item) throws Exception + { + // String value + if (item instanceof String) return item; + + XmlParser.Node node = (XmlParser.Node) item; + String tag = node.getTag(); + if ("Call".equals(tag)) return call(obj, node); + if ("Get".equals(tag)) return get(obj, node); + if ("New".equals(tag)) return newObj(obj, node); + if ("Ref".equals(tag)) return refObj(obj, node); + if ("Array".equals(tag)) return newArray(obj, node); + if ("Map".equals(tag)) return newMap(obj, node); + if ("Property".equals(tag)) return propertyObj(obj,node); + + if ("SystemProperty".equals(tag)) + { + String name = node.getAttribute("name"); + String defaultValue = node.getAttribute("default"); + return System.getProperty(name, defaultValue); + } + + Log.warn("Unknown value tag: " + node, new Throwable()); + return null; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * Run the XML configurations as a main application. + * The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration + * files. + *

    + * Any property file on the command line is added to a combined Property instance that is passed to + * each configuration file via {@link XmlConfiguration#setProperties(Map)}. + *

    + * Each configuration file on the command line is used to create a new XmlConfiguration instance and the + * {@link XmlConfiguration#configure()} method is used to create the configured object. If the resulting + * object is an instance of {@link LifeCycle}, then it is started. + *

    + * Any IDs created in a configuration are passed to the next configuration file on the command line using + * {@link #getIdMap()} and {@link #setIdMap(Map)}. This allows objects with IDs created in one config file to + * be referenced in subsequent config files on the command line. + * + * @param args array of property and xml configuration filenames or {@link Resource}s. + */ + public static void main(String[] args) + { + try + { + Properties properties=new Properties(); + XmlConfiguration last=null; + Object[] obj = new Object[args.length]; + for (int i = 0; i < args.length; i++) + { + if (args[i].toLowerCase().endsWith(".properties")) + { + properties.load(Resource.newResource(args[i]).getInputStream()); + } + else + { + XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL()); + if (last!=null) + configuration.getIdMap().putAll(last.getIdMap()); + if (properties.size()>0) + configuration.setProperties(properties); + obj[i] = configuration.configure(); + last=configuration; + } + } + + for (int i = 0; i < args.length; i++) + { + if (obj[i] instanceof LifeCycle) + { + LifeCycle lc = (LifeCycle)obj[i]; + if (!lc.isRunning()) + lc.start(); + } + } + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION, e); + } + + } + +} + diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java new file mode 100644 index 00000000000..a963f03321e --- /dev/null +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java @@ -0,0 +1,796 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.xml; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Stack; +import java.util.StringTokenizer; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +/*--------------------------------------------------------------*/ +/** + * XML Parser wrapper. This class wraps any standard JAXP1.1 parser with convieniant error and + * entity handlers and a mini dom-like document tree. + *

    + * By default, the parser is created as a validating parser only if xerces is present. This can be + * configured by setting the "org.eclipse.jetty.xml.XmlParser.Validating" system property. + * + * + */ +public class XmlParser +{ + private Map _redirectMap = new HashMap(); + private SAXParser _parser; + private Map _observerMap; + private Stack _observers = new Stack(); + private String _xpath; + private Object _xpaths; + private String _dtd; + + /* ------------------------------------------------------------ */ + /** + * Construct + */ + public XmlParser() + { + SAXParserFactory factory = SAXParserFactory.newInstance(); + boolean validating_dft = factory.getClass().toString().startsWith("org.apache.xerces."); + String validating_prop = System.getProperty("org.eclipse.jetty.xml.XmlParser.Validating", validating_dft ? "true" : "false"); + boolean validating = Boolean.valueOf(validating_prop).booleanValue(); + + setValidating(validating); + } + + /* ------------------------------------------------------------ */ + /** + * Constructor. + */ + public XmlParser(boolean validating) + { + setValidating(validating); + } + + /* ------------------------------------------------------------ */ + public void setValidating(boolean validating) + { + try + { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setValidating(validating); + _parser = factory.newSAXParser(); + + try + { + if (validating) + _parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/schema", validating); + } + catch (Exception e) + { + if (validating) + Log.warn("Schema validation may not be supported: ", e); + else + Log.ignore(e); + } + + _parser.getXMLReader().setFeature("http://xml.org/sax/features/validation", validating); + _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", true); + _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", false); + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION, e); + throw new Error(e.toString()); + } + } + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param entity + */ + public synchronized void redirectEntity(String name, URL entity) + { + if (entity != null) + _redirectMap.put(name, entity); + } + + /* ------------------------------------------------------------ */ + /** + * + * @return Returns the xpath. + */ + public String getXpath() + { + return _xpath; + } + + /* ------------------------------------------------------------ */ + /** + * Set an XPath A very simple subset of xpath is supported to select a partial tree. Currently + * only path like "/node1/nodeA | /node1/nodeB" are supported. + * + * @param xpath The xpath to set. + */ + public void setXpath(String xpath) + { + _xpath = xpath; + StringTokenizer tok = new StringTokenizer(xpath, "| "); + while (tok.hasMoreTokens()) + _xpaths = LazyList.add(_xpaths, tok.nextToken()); + } + + /* ------------------------------------------------------------ */ + public String getDTD() + { + return _dtd; + } + + /* ------------------------------------------------------------ */ + /** + * Add a ContentHandler. Add an additional _content handler that is triggered on a tag name. SAX + * events are passed to the ContentHandler provided from a matching start element to the + * corresponding end element. Only a single _content handler can be registered against each tag. + * + * @param trigger Tag local or q name. + * @param observer SAX ContentHandler + */ + public synchronized void addContentHandler(String trigger, ContentHandler observer) + { + if (_observerMap == null) + _observerMap = new HashMap(); + _observerMap.put(trigger, observer); + } + + /* ------------------------------------------------------------ */ + public synchronized Node parse(InputSource source) throws IOException, SAXException + { + _dtd=null; + Handler handler = new Handler(); + XMLReader reader = _parser.getXMLReader(); + reader.setContentHandler(handler); + reader.setErrorHandler(handler); + reader.setEntityResolver(handler); + if (Log.isDebugEnabled()) + Log.debug("parsing: sid=" + source.getSystemId() + ",pid=" + source.getPublicId()); + _parser.parse(source, handler); + if (handler._error != null) + throw handler._error; + Node doc = (Node) handler._top.get(0); + handler.clear(); + return doc; + } + + /* ------------------------------------------------------------ */ + /** + * Parse String URL. + */ + public synchronized Node parse(String url) throws IOException, SAXException + { + if (Log.isDebugEnabled()) + Log.debug("parse: " + url); + return parse(new InputSource(url)); + } + + /* ------------------------------------------------------------ */ + /** + * Parse File. + */ + public synchronized Node parse(File file) throws IOException, SAXException + { + if (Log.isDebugEnabled()) + Log.debug("parse: " + file); + return parse(new InputSource(file.toURL().toString())); + } + + /* ------------------------------------------------------------ */ + /** + * Parse InputStream. + */ + public synchronized Node parse(InputStream in) throws IOException, SAXException + { + _dtd=null; + Handler handler = new Handler(); + XMLReader reader = _parser.getXMLReader(); + reader.setContentHandler(handler); + reader.setErrorHandler(handler); + reader.setEntityResolver(handler); + _parser.parse(new InputSource(in), handler); + if (handler._error != null) + throw handler._error; + Node doc = (Node) handler._top.get(0); + handler.clear(); + return doc; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class NoopHandler extends DefaultHandler + { + Handler _next; + int _depth; + + NoopHandler(Handler next) + { + this._next = next; + } + + /* ------------------------------------------------------------ */ + public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException + { + _depth++; + } + + /* ------------------------------------------------------------ */ + public void endElement(String uri, String localName, String qName) throws SAXException + { + if (_depth == 0) + _parser.getXMLReader().setContentHandler(_next); + else + _depth--; + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class Handler extends DefaultHandler + { + Node _top = new Node(null, null, null); + SAXParseException _error; + private Node _context = _top; + private NoopHandler _noop; + + Handler() + { + _noop = new NoopHandler(this); + } + + /* ------------------------------------------------------------ */ + void clear() + { + _top = null; + _error = null; + _context = null; + } + + /* ------------------------------------------------------------ */ + public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException + { + String name = (uri == null || uri.equals("")) ? qName : localName; + Node node = new Node(_context, name, attrs); + + + // check if the node matches any xpaths set? + if (_xpaths != null) + { + String path = node.getPath(); + boolean match = false; + for (int i = LazyList.size(_xpaths); !match && i-- > 0;) + { + String xpath = (String) LazyList.get(_xpaths, i); + + match = path.equals(xpath) || xpath.startsWith(path) && xpath.length() > path.length() && xpath.charAt(path.length()) == '/'; + } + + if (match) + { + _context.add(node); + _context = node; + } + else + { + _parser.getXMLReader().setContentHandler(_noop); + } + } + else + { + _context.add(node); + _context = node; + } + + ContentHandler observer = null; + if (_observerMap != null) + observer = (ContentHandler) _observerMap.get(name); + _observers.push(observer); + + for (int i = 0; i < _observers.size(); i++) + if (_observers.get(i) != null) + ((ContentHandler) _observers.get(i)).startElement(uri, localName, qName, attrs); + } + + /* ------------------------------------------------------------ */ + public void endElement(String uri, String localName, String qName) throws SAXException + { + _context = _context._parent; + for (int i = 0; i < _observers.size(); i++) + if (_observers.get(i) != null) + ((ContentHandler) _observers.get(i)).endElement(uri, localName, qName); + _observers.pop(); + } + + /* ------------------------------------------------------------ */ + public void ignorableWhitespace(char buf[], int offset, int len) throws SAXException + { + for (int i = 0; i < _observers.size(); i++) + if (_observers.get(i) != null) + ((ContentHandler) _observers.get(i)).ignorableWhitespace(buf, offset, len); + } + + /* ------------------------------------------------------------ */ + public void characters(char buf[], int offset, int len) throws SAXException + { + _context.add(new String(buf, offset, len)); + for (int i = 0; i < _observers.size(); i++) + if (_observers.get(i) != null) + ((ContentHandler) _observers.get(i)).characters(buf, offset, len); + } + + /* ------------------------------------------------------------ */ + public void warning(SAXParseException ex) + { + Log.debug(Log.EXCEPTION, ex); + Log.warn("WARNING@" + getLocationString(ex) + " : " + ex.toString()); + } + + /* ------------------------------------------------------------ */ + public void error(SAXParseException ex) throws SAXException + { + // Save error and continue to report other errors + if (_error == null) + _error = ex; + Log.debug(Log.EXCEPTION, ex); + Log.warn("ERROR@" + getLocationString(ex) + " : " + ex.toString()); + } + + /* ------------------------------------------------------------ */ + public void fatalError(SAXParseException ex) throws SAXException + { + _error = ex; + Log.debug(Log.EXCEPTION, ex); + Log.warn("FATAL@" + getLocationString(ex) + " : " + ex.toString()); + throw ex; + } + + /* ------------------------------------------------------------ */ + private String getLocationString(SAXParseException ex) + { + return ex.getSystemId() + " line:" + ex.getLineNumber() + " col:" + ex.getColumnNumber(); + } + + /* ------------------------------------------------------------ */ + public InputSource resolveEntity(String pid, String sid) + { + if (Log.isDebugEnabled()) + Log.debug("resolveEntity(" + pid + ", " + sid + ")"); + + if (sid!=null && sid.endsWith(".dtd")) + _dtd=sid; + + URL entity = null; + if (pid != null) + entity = (URL) _redirectMap.get(pid); + if (entity == null) + entity = (URL) _redirectMap.get(sid); + if (entity == null) + { + String dtd = sid; + if (dtd.lastIndexOf('/') >= 0) + dtd = dtd.substring(dtd.lastIndexOf('/') + 1); + + if (Log.isDebugEnabled()) + Log.debug("Can't exact match entity in redirect map, trying " + dtd); + entity = (URL) _redirectMap.get(dtd); + } + + if (entity != null) + { + try + { + InputStream in = entity.openStream(); + if (Log.isDebugEnabled()) + Log.debug("Redirected entity " + sid + " --> " + entity); + InputSource is = new InputSource(in); + is.setSystemId(sid); + return is; + } + catch (IOException e) + { + Log.ignore(e); + } + } + return null; + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * XML Attribute. + */ + public static class Attribute + { + private String _name; + private String _value; + + Attribute(String n, String v) + { + _name = n; + _value = v; + } + + public String getName() + { + return _name; + } + + public String getValue() + { + return _value; + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * XML Node. Represents an XML element with optional attributes and ordered content. + */ + public static class Node extends AbstractList + { + Node _parent; + private ArrayList _list; + private String _tag; + private Attribute[] _attrs; + private boolean _lastString = false; + private String _path; + + /* ------------------------------------------------------------ */ + Node(Node parent, String tag, Attributes attrs) + { + _parent = parent; + _tag = tag; + + if (attrs != null) + { + _attrs = new Attribute[attrs.getLength()]; + for (int i = 0; i < attrs.getLength(); i++) + { + String name = attrs.getLocalName(i); + if (name == null || name.equals("")) + name = attrs.getQName(i); + _attrs[i] = new Attribute(name, attrs.getValue(i)); + } + } + } + + /* ------------------------------------------------------------ */ + public Node getParent() + { + return _parent; + } + + /* ------------------------------------------------------------ */ + public String getTag() + { + return _tag; + } + + /* ------------------------------------------------------------ */ + public String getPath() + { + if (_path == null) + { + if (getParent() != null && getParent().getTag() != null) + _path = getParent().getPath() + "/" + _tag; + else + _path = "/" + _tag; + } + return _path; + } + + /* ------------------------------------------------------------ */ + /** + * Get an array of element attributes. + */ + public Attribute[] getAttributes() + { + return _attrs; + } + + /* ------------------------------------------------------------ */ + /** + * Get an element attribute. + * + * @return attribute or null. + */ + public String getAttribute(String name) + { + return getAttribute(name, null); + } + + /* ------------------------------------------------------------ */ + /** + * Get an element attribute. + * + * @return attribute or null. + */ + public String getAttribute(String name, String dft) + { + if (_attrs == null || name == null) + return dft; + for (int i = 0; i < _attrs.length; i++) + if (name.equals(_attrs[i].getName())) + return _attrs[i].getValue(); + return dft; + } + + /* ------------------------------------------------------------ */ + /** + * Get the number of children nodes. + */ + public int size() + { + if (_list != null) + return _list.size(); + return 0; + } + + /* ------------------------------------------------------------ */ + /** + * Get the ith child node or content. + * + * @return Node or String. + */ + public Object get(int i) + { + if (_list != null) + return _list.get(i); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Get the first child node with the tag. + * + * @param tag + * @return Node or null. + */ + public Node get(String tag) + { + if (_list != null) + { + for (int i = 0; i < _list.size(); i++) + { + Object o = _list.get(i); + if (o instanceof Node) + { + Node n = (Node) o; + if (tag.equals(n._tag)) + return n; + } + } + } + return null; + } + + /* ------------------------------------------------------------ */ + public void add(int i, Object o) + { + if (_list == null) + _list = new ArrayList(); + if (o instanceof String) + { + if (_lastString) + { + int last = _list.size() - 1; + _list.set(last, (String) _list.get(last) + o); + } + else + _list.add(i, o); + _lastString = true; + } + else + { + _lastString = false; + _list.add(i, o); + } + } + + /* ------------------------------------------------------------ */ + public void clear() + { + if (_list != null) + _list.clear(); + _list = null; + } + + /* ------------------------------------------------------------ */ + /** + * Get a tag as a string. + * + * @param tag The tag to get + * @param tags IF true, tags are included in the value. + * @param trim If true, trim the value. + * @return results of get(tag).toString(tags). + */ + public String getString(String tag, boolean tags, boolean trim) + { + Node node = get(tag); + if (node == null) + return null; + String s = node.toString(tags); + if (s != null && trim) + s = s.trim(); + return s; + } + + /* ------------------------------------------------------------ */ + public synchronized String toString() + { + return toString(true); + } + + /* ------------------------------------------------------------ */ + /** + * Convert to a string. + * + * @param tag If false, only _content is shown. + */ + public synchronized String toString(boolean tag) + { + StringBuilder buf = new StringBuilder(); + toString(buf, tag); + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * Convert to a string. + * + * @param tag If false, only _content is shown. + */ + public synchronized String toString(boolean tag, boolean trim) + { + String s = toString(tag); + if (s != null && trim) + s = s.trim(); + return s; + } + + /* ------------------------------------------------------------ */ + private synchronized void toString(StringBuilder buf, boolean tag) + { + if (tag) + { + buf.append("<"); + buf.append(_tag); + + if (_attrs != null) + { + for (int i = 0; i < _attrs.length; i++) + { + buf.append(' '); + buf.append(_attrs[i].getName()); + buf.append("=\""); + buf.append(_attrs[i].getValue()); + buf.append("\""); + } + } + } + + if (_list != null) + { + if (tag) + buf.append(">"); + for (int i = 0; i < _list.size(); i++) + { + Object o = _list.get(i); + if (o == null) + continue; + if (o instanceof Node) + ((Node) o).toString(buf, tag); + else + buf.append(o.toString()); + } + if (tag) + { + buf.append(""); + } + } + else if (tag) + buf.append("/>"); + } + + /* ------------------------------------------------------------ */ + /** + * Iterator over named child nodes. + * + * @param tag The tag of the nodes. + * @return Iterator over all child nodes with the specified tag. + */ + public Iterator iterator(final String tag) + { + return new Iterator() + { + int c = 0; + Node _node; + + /* -------------------------------------------------- */ + public boolean hasNext() + { + if (_node != null) + return true; + while (_list != null && c < _list.size()) + { + Object o = _list.get(c); + if (o instanceof Node) + { + Node n = (Node) o; + if (tag.equals(n._tag)) + { + _node = n; + return true; + } + } + c++; + } + return false; + } + + /* -------------------------------------------------- */ + public Object next() + { + try + { + if (hasNext()) + return _node; + throw new NoSuchElementException(); + } + finally + { + _node = null; + c++; + } + } + + /* -------------------------------------------------- */ + public void remove() + { + throw new UnsupportedOperationException("Not supported"); + } + }; + } + } +} diff --git a/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd new file mode 100644 index 00000000000..f6407fd50ca --- /dev/null +++ b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java new file mode 100644 index 00000000000..3a5df4319f7 --- /dev/null +++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java @@ -0,0 +1,79 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.xml;import java.net.URL; +import java.util.HashMap; + + +public class TestConfiguration extends HashMap +{ + public static int VALUE=77; + + public TestConfiguration nested; + public Object testObject; + public int testInt; + public URL url; + public static boolean called=false; + public Object[] oa; + public int[] ia; + public int testField1; + public int testField2; + + public void setTest(Object value) + { + testObject=value; + } + + public void setTest(int value) + { + testInt=value; + } + + public void call() + { + put("Called","Yes"); + } + + public TestConfiguration call(Boolean b) + { + nested=new TestConfiguration(); + nested.put("Arg",b); + return nested; + } + + public void call(URL u,boolean b) + { + put("URL",b?"1":"0"); + url=u; + } + + public String getString() + { + return "String"; + } + + public static void callStatic() + { + called=true; + } + + public void call(Object[] oa) + { + this.oa=oa; + } + + public void call(int[] ia) + { + this.ia=ia; + } +} diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java new file mode 100644 index 00000000000..4d4a40b642e --- /dev/null +++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java @@ -0,0 +1,135 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.xml; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +public class XmlConfigurationTest extends TestCase +{ + public final static String __CRLF = "\015\012"; + + /* ------------------------------------------------------------ */ + public static void testXmlParser() throws Exception + { + XmlParser parser = new XmlParser(); + + URL configURL = XmlConfiguration.class.getClassLoader().getResource("org/eclipse/jetty/xml/configure_6_0.dtd"); + parser.redirectEntity("configure.dtd", configURL); + parser.redirectEntity("configure_1_3.dtd", configURL); + parser.redirectEntity("http://jetty.eclipse.org/configure.dtd", configURL); + parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN", configURL); + parser.redirectEntity("http://jetty.eclipse.org/configure_1_3.dtd", configURL); + parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.3//EN", configURL); + parser.redirectEntity("configure_1_2.dtd", configURL); + parser.redirectEntity("http://jetty.eclipse.org/configure_1_2.dtd", configURL); + parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.2//EN", configURL); + parser.redirectEntity("configure_1_1.dtd", configURL); + parser.redirectEntity("http://jetty.eclipse.org/configure_1_1.dtd", configURL); + parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.1//EN", configURL); + parser.redirectEntity("configure_1_0.dtd", configURL); + parser.redirectEntity("http://jetty.eclipse.org/configure_1_0.dtd", configURL); + parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.0//EN", configURL); + + URL url = XmlConfigurationTest.class.getClassLoader().getResource("org/eclipse/jetty/xml/configure.xml"); + XmlParser.Node testDoc = parser.parse(url.toString()); + String testDocStr = testDoc.toString().trim(); + + assertTrue(testDocStr.startsWith("")); + + } + + /* ------------------------------------------------------------ */ + public static void testXmlConfiguration() throws Exception + { + Map properties = new HashMap(); + properties.put("whatever", "xxx"); + + URL url = XmlConfigurationTest.class.getClassLoader().getResource("org/eclipse/jetty/xml/configure.xml"); + XmlConfiguration configuration = + new XmlConfiguration(url); + TestConfiguration tc = new TestConfiguration(); + configuration.setProperties(properties); + configuration.configure(tc); + + assertEquals("Set String","SetValue",tc.testObject); + assertEquals("Set Type",2,tc.testInt); + + assertEquals("Put","PutValue",tc.get("Test")); + assertEquals("Put dft","2",tc.get("TestDft")); + assertEquals("Put type",new Integer(2),tc.get("TestInt")); + + assertEquals("Trim","PutValue",tc.get("Trim")); + assertEquals("Null",null,tc.get("Null")); + assertEquals("NullTrim",null,tc.get("NullTrim")); + + assertEquals("ObjectTrim",new Double(1.2345),tc.get("ObjectTrim")); + assertEquals("Objects","-1String",tc.get("Objects")); + assertEquals( "ObjectsTrim", "-1String",tc.get("ObjectsTrim")); + assertEquals( "String", "\n PutValue\n ",tc.get("String")); + assertEquals( "NullString", "",tc.get("NullString")); + assertEquals( "WhateSpace", "\n ",tc.get("WhiteSpace")); + assertEquals( "ObjectString", "\n 1.2345\n ",tc.get("ObjectString")); + assertEquals( "ObjectsString", "-1String",tc.get("ObjectsString")); + assertEquals( "ObjectsWhiteString", "-1\n String",tc.get("ObjectsWhiteString")); + + assertEquals( "SystemProperty", System.getProperty("user.dir")+"/stuff",tc.get("SystemProperty")); + assertEquals( "Property", "xxx", tc.get("Property")); + + + assertEquals( "Called", "Yes",tc.get("Called")); + + assertTrue(TestConfiguration.called); + + assertEquals("oa[0]","Blah",tc.oa[0]); + assertEquals("oa[1]","1.2.3.4:5678",tc.oa[1]); + assertEquals("oa[2]",new Double(1.2345),tc.oa[2]); + assertEquals("oa[3]",null,tc.oa[3]); + + assertEquals("ia[0]",1,tc.ia[0]); + assertEquals("ia[1]",2,tc.ia[1]); + assertEquals("ia[2]",3,tc.ia[2]); + assertEquals("ia[3]",0,tc.ia[3]); + + TestConfiguration tc2=tc.nested; + assertTrue(tc2!=null); + assertEquals( "Called(bool)", new Boolean(true),tc2.get("Arg")); + + assertEquals("nested config",null,tc.get("Arg")); + assertEquals("nested config",new Boolean(true),tc2.get("Arg")); + + assertEquals("nested config","Call1",tc2.testObject); + assertEquals("nested config",4,tc2.testInt); + assertEquals( "nested call", "http://www.eclipse.com/",tc2.url.toString()); + + configuration = + new XmlConfiguration("SetValue2"); + TestConfiguration tc3 = new TestConfiguration(); + configuration.configure(tc3); + assertEquals("Set String 3","SetValue",tc3.testObject); + assertEquals("Set Type 3",2,tc3.testInt); + + assertEquals("static to field",tc.testField1,77); + assertEquals("field to field",tc.testField2,2); + assertEquals("literal to static",TestConfiguration.VALUE,42); + + } + + + +} diff --git a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml new file mode 100644 index 00000000000..110e249cd64 --- /dev/null +++ b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml @@ -0,0 +1,131 @@ + + + + + + SetValue + 2 + + PutValue + 2 + 2 + + + PutValue + + + + + + + + + 1.2345 + + + + + 1.2345 + + + + + -1 + + String + + + + + + -1 + + String + + + + + PutValue + + + + + + + + + + 1.2345 + + + + + -1 + + String + + + + + + -1 + + + String + + + /stuff + + + True + 2.3 + + + + + + false + + + + true + put + Call1 + 4 + + http://www.eclipse.com/ + false + + + + + + + + + + + + Blah + 1.2.3.4:5678 + 1.2345 + + + + + + + 1 + 2 + 3 + + + + + + + 42 + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..c98d995e264 --- /dev/null +++ b/pom.xml @@ -0,0 +1,230 @@ + + + 4.0.0 + + org.eclipse.jetty + jetty-parent + 8-SNAPSHOT + + org.eclipse.jetty + jetty-project + 7.0.0.incubation0-SNAPSHOT + Jetty :: Project + pom + + + 3.0-SNAPSHOT + 1.1 + 1.6.5 + 1.1.1 + 3.8.2 + 1.4 + [1.4,1.6) + 3.1.1 + 9.1.02.B04.p0 + 1.0.beta4 + + + scm:svn:https://svn.codehaus.org/jetty/jetty/trunk + scm:svn:https://svn.codehaus.org/jetty/jetty/trunk + scm:svn:https://svn.codehaus.org/jetty/jetty/trunk + + + install + + + maven-compiler-plugin + + 1.5 + 1.5 + false + + + + maven-release-plugin + + https://svn.codehaus.org/jetty/jetty/tags + + + + org.apache.maven.plugins + maven-jar-plugin + + + + development + ${pom.url} + ${pom.version} + org.eclipse + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.4.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.2 + true + + + + + + + jetty-assembly-descriptor + jetty-util + jetty-io + jetty-http + jetty-server + jetty-client + jetty-xml + jetty-security + jetty-servlet + jetty-webapp + jetty-servlets + jetty-deploy + jetty-ajp + jetty-annotations + jetty-jmx + jetty-jndi + jetty-plus + jetty-rewrite + jetty-servlet-tester + jetty-start + jetty-assembly + jetty-test-webapp + + + + codehaus.org + Jetty Snapshot Repository + default + http://snapshots.repository.codehaus.org + + true + + + false + + + + apache.plugin.snapshot + Apache Snapshot Repository + default + http://people.apache.org/repo/m2-snapshot-repository + + true + + + false + + + + + + + apache.plugin.snapshot + Apache Plugin Snapshot Repository + default + http://people.apache.org/repo/m2-snapshot-repository + + true + + + false + + + + + + + org.mortbay.jetty + servlet-api + ${servlet-api-version} + + + org.apache.maven + maven-plugin-tools-api + 2.0 + + + junit + junit + ${junit-version} + + + org.slf4j + jcl104-over-slf4j + ${slf4j-version} + + + org.slf4j + slf4j-simple + ${slf4j-version} + + + org.slf4j + slf4j-api + ${slf4j-version} + + + ant + ant + ${ant-version} + + + org.apache.geronimo.specs + geronimo-jta_1.1_spec + ${jta-spec-version} + + + javax.mail + mail + ${mail-version} + + + javax.activation + activation + ${activation-version} + + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.0 + + + +